From f4a46d40f98da20e395e28c4010f8a3d694cc22d Mon Sep 17 00:00:00 2001 From: Hobe Date: Thu, 21 Nov 2019 02:39:50 +0000 Subject: [PATCH] add gridbox code git-svn-id: http://newslabx.csie.ntu.edu.tw/svn/Ginger@46 5747cdd2-2146-426f-b2b0-0570f90b98ed --- trunk/gridEyeBox/dataFuser.py | 69 ++++++++++++++++++++++++++++++++++++++ trunk/gridEyeBox/gridEyeReader.py | 24 +++++++++++++ trunk/gridEyeBox/main.py | 27 +++++++++++++++ trunk/gridEyeBox/shot_sample.png | Bin 0 -> 15656 bytes 4 files changed, 120 insertions(+) create mode 100644 trunk/gridEyeBox/dataFuser.py create mode 100644 trunk/gridEyeBox/gridEyeReader.py create mode 100644 trunk/gridEyeBox/main.py create mode 100644 trunk/gridEyeBox/shot_sample.png diff --git a/trunk/gridEyeBox/dataFuser.py b/trunk/gridEyeBox/dataFuser.py new file mode 100644 index 0000000..adebedb --- /dev/null +++ b/trunk/gridEyeBox/dataFuser.py @@ -0,0 +1,69 @@ +import cv2 +import math +import numpy as np + +kBACKGROUND_NUM = 10 +kSIZE = 128 +kEXPONENTAL_VALUE = 0.7 + +gSENSOR_FOV = 60.0 / 180.0 * math.pi + + +def exponential(img, value): + tmp = cv2.pow(img.astype(np.double), value)*(255.0/(255.0**value)) + return tmp.astype(np.uint8) + + +def mergeFrames(imgs, SIZE, overlap): + tmp = np.zeros((SIZE, SIZE*2-overlap), dtype=np.uint16) + tmp[:, :SIZE] = imgs[0] + tmp[:, -SIZE:] += imgs[1] + tmp[:, (SIZE-overlap): SIZE] = tmp[:, (SIZE-overlap): SIZE]/2 + + tmp2 = np.zeros((SIZE, SIZE*2-overlap), dtype=np.uint16) + tmp2[:, :SIZE] = imgs[2] + tmp2[:, -SIZE:] += imgs[3] + tmp2[:, (SIZE-overlap): SIZE] = tmp2[:, (SIZE-overlap): SIZE]/2 + + merge = np.zeros((SIZE*2-overlap, SIZE*2-overlap), dtype=np.uint16) + merge[:SIZE, :] = tmp + merge[-SIZE:, :] += tmp2 + merge[(SIZE-overlap):SIZE, :] = merge[(SIZE-overlap):SIZE, :]/2 + #merge = exponential(merge, kEXPONENTAL_VALUE) + + return merge.astype(np.uint8) + + +class DataFuser(object): + def __init__(self, sensor_dist): + self.background_cnt = 0 + self.background_frame = np.zeros((4, 64)) + self.sensor_dist = sensor_dist + + def mergeFrame(self, frame, dist = None): + if self.background_cnt < kBACKGROUND_NUM: + self.background_frame += frame / kBACKGROUND_NUM + self.background_cnt += 1 + return False + + frame = exponential(cv2.subtract(exponential(frame, kEXPONENTAL_VALUE), + exponential(self.background_frame, kEXPONENTAL_VALUE)), + 0.3) + print (([max(x) for x in frame])) + imgs = [np.reshape(img, (8, 8)) for img in frame] + imgs = [cv2.resize(img.astype(np.uint8), (kSIZE, kSIZE), + interpolation = cv2.INTER_LINEAR) for img in imgs] + try: + overlap = int(kSIZE - + kSIZE * (self.sensor_dist / (2 * dist * math.tan(gSENSOR_FOV / 2)))) + except: + overlap = 0 + if overlap < 0: + overlap = 0 + overlap = 0 + + return mergeFrames(imgs, kSIZE, overlap) + + if not dist: + pass + diff --git a/trunk/gridEyeBox/gridEyeReader.py b/trunk/gridEyeBox/gridEyeReader.py new file mode 100644 index 0000000..675fb98 --- /dev/null +++ b/trunk/gridEyeBox/gridEyeReader.py @@ -0,0 +1,24 @@ +import serial + +class GridEye(object): + def __init__(self, port, baudrate=115200): + self.serial = serial.Serial(port, baudrate, timeout=0.1) + + def readData(self): + datas = [] + for i in range(4): + retry = True + while retry: + self.serial.reset_input_buffer() + self.serial.reset_output_buffer() + self.serial.write(bytes([ord('0')+i])) + raw_data = self.serial.read(64*5+3) + if raw_data.split(b':')[0] != bytes([ord('0')+i]): + continue + data = [int(x, 16)*0.25 for x in raw_data.split()[1:]] + #data = self.serial.read(64*5+3) + if b'FFFF' not in data: + retry = False + datas.append(data) + + return datas diff --git a/trunk/gridEyeBox/main.py b/trunk/gridEyeBox/main.py new file mode 100644 index 0000000..0fc79ff --- /dev/null +++ b/trunk/gridEyeBox/main.py @@ -0,0 +1,27 @@ +from gridEyeReader import GridEye +from dataFuser import DataFuser + +import cv2 +import logging +import time +import numpy as np + +logging.basicConfig(level=logging.DEBUG) + +g = GridEye('/dev/ttyACM0') +d = DataFuser(10) # distance between sensors. +cv2.namedWindow("Image", cv2.WINDOW_NORMAL) +for _ in range(1000000): + frame = g.readData() + out = d.mergeFrame(np.array(frame), 100) + if type(out) is np.ndarray: + cv2.imshow('Image', cv2.cvtColor(out, cv2.COLOR_GRAY2BGR)) + k = cv2.waitKey(1) + if k == ord('q'): + break + elif k == ord('c'): + cv2.imwrite('shot.png', out) + + +cv2.destroyAllWindows() + diff --git a/trunk/gridEyeBox/shot_sample.png b/trunk/gridEyeBox/shot_sample.png new file mode 100644 index 0000000000000000000000000000000000000000..f8450e2830c742f51efebf0c4685234f30cccfbd GIT binary patch literal 15656 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 literal 0 HcmV?d00001