def _save_parameters(self): """finds the important parameters of the fitted ellipse Theory taken form http://mathworld.wolfram Args ----- coef (list): list of the coefficients describing an ellipse [a,b,c,d,f,g] corresponding to ax**2+2bxy+cy**2+2dx+2fy+g Returns _______ center (List): of the form [x0, y0] width (float): major axis height (float): minor axis phi (float): rotation of major axis form the x-axis in radians """ #eigenvectors are the coefficients of an ellipse in general form #a*x^2 + 2*b*x*y + c*y^2 + 2*d*x + 2*f*y + g = 0 [eqn. 15) from (**) or (***) a = self.coef[0, 0] b = self.coef[1, 0] / 2. c = self.coef[2, 0] d = self.coef[3, 0] / 2. f = self.coef[4, 0] / 2. g = self.coef[5, 0] if (a - c) == 0: return True #finding center of ellipse [eqn.19 and 20] from (**) x0 = (c * d - b * f) / (b**2. - a * c) y0 = (a * f - b * d) / (b**2. - a * c) #Find the semi-axes lengths [eqn. 21 and 22] from (**) numerator = 2 * (a * f * f + c * d * d + g * b * b - 2 * b * d * f - a * c * g) denominator1 = (b * b - a * c) * ((c - a) * np.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a)) denominator2 = (b * b - a * c) * ((a - c) * np.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a)) width = np.sqrt(numerator / denominator1) height = np.sqrt(numerator / denominator2) phi = .5 * np.arctan((2. * b) / (a - c)) self.center = [ self.shape_processor.corners[0][0] + x0, self.shape_processor.corners[0][1] + y0 ] self.width = width self.height = height self.dimensions_int = tuple_int((width, height)) self.angle = np.rad2deg(phi) % 360 return False
def arm(self, width, height, image): self.frame = 0 self.dimensions = tuple_int((width * self.scale, height * self.scale)) width, height = self.dimensions self.center = (width // 2, height // 2) self.resize(image) #image = self.rotate(image, self.ENGINE.angle) self.ENGINE.importer = self self.ENGINE.arm(width, height, image)
def arm(self, width, height, image): self.dimensions = tuple_int((width * self.scale, height * self.scale)) width, height = self.dimensions self.center = (width // 2, height // 2) if self.scale == 1: self.resize = lambda x: x else: self.resize = self.resize_image self.resize(image) #image = self.rotate(image, self.ENGINE.angle) config.engine.arm(width, height, image)
def fit(self, x: np.ndarray, y: np.ndarray) -> bool: try: x_coord, y_coord, radius, v = self.hyper_fit(x, y) self.center = [ self.shape_processor.corners[0][0] + x_coord, self.shape_processor.corners[0][1] + y_coord ] self.width = self.height = radius self.dimensions_int = tuple_int((radius, radius)) except: return False return True
def track(self, last: bool = False): try: if config.engine.blink_i == 1: self.corners = self.standard_corners.copy() self.walkout_offset = 0 self.refresh_source(self.source) contours, hierarchy = cv2.findContours(self.area, 1, 2) if len(contours) > 0: dists = np.zeros(len(contours)) for i, cnt in enumerate(contours): M = cv2.moments(cnt) try: cx = int(M['m10'] / M['m00']) cy = int(M['m01'] / M['m00']) if self.type == 1: dists[i] = np.mean(self.source[cy - 2:cy + 2, cx - 2:cx + 2]) else: dists[i] = np.sqrt((cx - self.center[0])**2 + (cy - self.center[1])**2) except: dists[i] = 255 from operator import itemgetter #todo: score these based on 1) color and 2) distance to center M = cv2.moments(contours[min(enumerate(dists), key=itemgetter(1))[0]]) try: cx = round(M['m10'] / M['m00']) cy = round(M['m01'] / M['m00']) except: return False #print(cx,cy) self.center = (cx, cy) #self.source[cy,cx]=250 #cv2.imshow("JJ", self.source) #cv2.waitKey(0) center = [ self.center[0] - self.corners[0][0], self.center[1] - self.corners[0][1] ] walkout = self.walkout walkout.reset(center) fit_product = 1 if walkout.walkout(): fit_product = self.fit_model if fit_product.fit(walkout.rx, walkout.ry): ellipse = fit_product else: ellipse = 0 else: ellipse = 0 except Exception as e: print(e) return False if ellipse == fit_product: center, width, height = ellipse.center, ellipse.width, ellipse.height if width * height > 4: # if self.type == 2: # if distance(np.array(center), np.array(self.center)) > 6: #normalize qqqq # self.center = self.original_center[self.center_index] # self.corners = self.standard_corners.copy() # return False self.ellipse = ellipse self.center = center #"walkout_offset" defines the walkout offset for the next frame. #The multiplication factor, here .4, returns slightly below the average. self.walkout_offset = int(.4 * (width + height)) self.margin = np.amax(ellipse.dimensions_int) * 2 center_int = tuple_int(center) self.corners[0] = (max(center_int[0] - self.margin, 0), max(center_int[1] - self.margin, 0)) self.corners[1] = (min(center_int[0] + self.margin, config.engine.width), min(center_int[1] + self.margin, config.engine.height)) self.center_index = 0 margin = to_int(np.amax(ellipse.dimensions_int) * 1) #self.bbox = (max(center_int[0] - margin, 0), max(center_int[1] - margin, 0), margin * 2, margin * 2) # try: # if self.bbox: # (success, box) = self.tracker.update(self.source) # if success: # box=np.array(box, dtype=int) # x,y,w,h = box # # cv2.rectangle(self.source,(x,y),(x+w,y+h),(0,255,0),1) # #cv2.rectangle(self.source, box, (0,255,0),1) # cv2.imshow("Kk", self.source) # #cv2.waitKey(0) # #print(box) # except Exception as e: # print(e) # pass return True if last: return False # Shape detection failed, try contour detection self.corners = self.standard_corners.copy() self.walkout_offset = 0 self.refresh_source(self.source) contours, hierarchy = cv2.findContours(self.area, 1, 2) if len(contours) > 0: dists = np.zeros(len(contours)) for i, cnt in enumerate(contours): M = cv2.moments(cnt) try: cx = int(M['m10'] / M['m00']) cy = int(M['m01'] / M['m00']) if self.type == 1: dists[i] = np.mean(self.source[cy - 2:cy + 2, cx - 2:cx + 2]) else: dists[i] = np.sqrt((cx - self.center[0])**2 + (cy - self.center[1])**2) except: dists[i] = 255 from operator import itemgetter #todo: score these based on 1) color and 2) distance to center M = cv2.moments(contours[min(enumerate(dists), key=itemgetter(1))[0]]) try: cx = round(M['m10'] / M['m00']) cy = round(M['m01'] / M['m00']) except: return False #print(cx,cy) self.center = (cx, cy) return self.track(True) return False
def track(self): try: center = [ self.center[0] - self.corners[0][0], self.center[1] - self.corners[0][1] ] walkout = self.walkout walkout.reset(center) fit_product = 1 if walkout.walkout(): fit_product = self.fit_model if fit_product.fit(walkout.rx, walkout.ry): ellipse = fit_product else: ellipse = 0 else: ellipse = 0 except Exception as e: return False if ellipse == fit_product: center, width, height = ellipse.center, ellipse.width, ellipse.height # if self.type == 2: # if distance(np.array(center), np.array(self.center)) > 6: #normalize qqqq # self.center = self.original_center[self.center_index] # self.corners = self.standard_corners.copy() # return False self.ellipse = ellipse self.center = center #"walkout_offset" defines the walkout offset for the next frame. #The multiplication factor, here .4, returns slightly below the average. self.walkout_offset = int(.4 * (width + height)) self.margin = np.amax(ellipse.dimensions_int) * 2 center_int = tuple_int(center) self.corners[0] = (max(center_int[0] - self.margin, 0), max(center_int[1] - self.margin, 0)) self.corners[1] = (min(center_int[0] + self.margin, self.ENGINE.width), min(center_int[1] + self.margin, self.ENGINE.height)) self.center_index = 0 return True else: # Shape detection failed. We reset the crop area and iterate through a list of alternative center points. print("B") self.center = self.original_center[ self. center_index] # This loops through a list with centers surrounding the origin. self.corners = self.standard_corners.copy() self.walkout_offset = 0 if self.center_index < 8: self.center_index += 1 self.track() else: self.center_index = 0 return False
def cr_artifacts(self, cr_processor, offsetx: int, offsety: int, pupil_area) -> None: """ Computes pupillary overlaps and acts to remove these artifacts. """ cr_center, cr_width, cr_height, cr_angle, cr_dimensions_int = cr_processor.ellipse.parameters( ) cr_center_int = tuple_int(cr_center) larger_width, larger_height = larger_radius = tuple( int(1.2 * element) for element in cr_dimensions_int) cr_width_norm = larger_width * self.norm cr_height_norm = larger_height * self.norm dimensional_product = larger_width * larger_height arc = [ dimensional_product / np.sqrt((cr_width_norm * anglesteps_cos[i])**2 + (cr_width_norm * anglesteps_sin[i])**2) for i in angular_range ] cos_sin_arc = [(to_int(anglesteps_cos[i] * arc[i]), to_int(anglesteps_sin[i] * arc[i])) for i in angular_range] hit_list = zeros.copy() for i, arc_element in enumerate(cos_sin_arc): cos, sin = arc_element x = cr_center_int[0] + offsetx + cos y = cr_center_int[1] + offsety + sin n = 1 strike = 0 while n < self.norm_cr_artefact: #normalize qqqq n += 1 try: if pupil_area[y, x] != 0: hit_list[i] = i + 1 break else: x += cos y += sin except: break if np.any(hit_list): delta = np.count_nonzero(number_row - hit_list) if delta < self.norm_cr_artefact: cv2.ellipse(self.pupil_source, cr_center_int, larger_radius, cr_angle, 0, 360, 0, -1) else: for element in hit_list: if element != 0: cos, sin = cos_sin_arc[element - 1] x = cr_center_int[0] + cos y = cr_center_int[1] + sin cv2.ellipse(self.pupil_source, cr_center_int, larger_radius, cr_angle, element * 40 - 40, element * 40, 0, 4) #normalize qqqq
def update_track(self, blink: int) -> None: frame_preview = cv2.cvtColor(config.engine.source, cv2.COLOR_GRAY2BGR) frame_source = frame_preview.copy() cr_width = pupil_width = -1 Processor = self.pupil_processor if blink == 0: self.rplace_markers(frame_preview) for index, cr_processor in enumerate(config.engine.cr_processors): if cr_processor.active: cr_corners = cr_processor.corners cr_center, cr_width, cr_height, cr_angle, cr_dimensions_int = cr_processor.ellipse.parameters( ) if self._state == "adjustment": if cr_processor == self.current_cr_processor: color = bluish else: color = green try: cv2.ellipse(frame_preview, tuple_int(cr_center), cr_dimensions_int, cr_angle, 0, 360, color, 1) self.place_cross(frame_preview, cr_center, color) cv2.rectangle(frame_preview, cr_corners[0], cr_corners[1], color) cv2.putText( frame_preview, "{}".format(index + 2), (int((cr_corners[1][0] + cr_corners[0][0]) * .5 - 3), cr_corners[0][1] - 3), font, .7, color, 0, cv2.LINE_4) except Exception as e: cr_processor.active = False else: self.place_cross(frame_preview, cr_center, bluish) try: pupil_corners = Processor.corners pupil_center, pupil_width, pupil_height, pupil_angle, pupil_dimensions_int = Processor.ellipse.parameters( ) cv2.ellipse(frame_preview, tuple_int(pupil_center), pupil_dimensions_int, pupil_angle, 0, 360, red, 1) self.place_cross(frame_preview, pupil_center, red) cv2.rectangle(frame_preview, pupil_corners[0], pupil_corners[1], red) except: pass if self._state == "adjustment": stock_P = self.PStock.copy() stock_CR = self.CRStock.copy() if cr_width != -1: cr_area = self.current_cr_processor.area offset_y = int((self.binary_height - cr_area.shape[0]) / 2) offset_x = int((self.binary_width - cr_area.shape[1]) / 2) stock_CR[offset_y:min(offset_y + cr_area.shape[0], self.binary_height), offset_x:min(offset_x + cr_area.shape[1], self. binary_width)] = cr_area stock_CR[0:20, 0:self.binary_width] = self.crstock_txt_selected else: stock_CR[0:20, 0:self.binary_width] = self.crstock_txt if pupil_width != -1: # stock_P[pcorners[0][1]:pcorners[0][1]+Processor.area.shape[0], # pcorners[0][0]:pcorners[0][0]+Processor.area.shape[1]] = Processor.area stock_P[0:20, 0:self.binary_width] = self.pstock_txt_selected pupil_area = Processor.area offset_y = int((self.binary_height - pupil_area.shape[0]) / 2) offset_x = int((self.binary_width - pupil_area.shape[1]) / 2) stock_P[offset_y:min(offset_y + pupil_area.shape[0], self.binary_height), offset_x:min(offset_x + pupil_area.shape[1], self. binary_width)] = pupil_area else: stock_P[0:20, 0:self.binary_width] = self.pstock_txt cv2.putText( stock_P, "{} || {}".format(round(Processor.binarythreshold, 1), Processor.blur), (10, self.binary_height - 10), font, .7, 255, 0, cv2.LINE_4) cv2.putText( stock_CR, "{} || {}".format( round(self.current_cr_processor.binarythreshold, 1), self.current_cr_processor.blur), (10, self.binary_height - 10), font, .7, 255, 0, cv2.LINE_4) frame_source[0:20, 0:self.width] = self.src_txt frame_preview[0:20, 0:self.width] = self.prev_txt frame_preview[0:self.height, 0:1] = 0 cv2.putText(frame_source, "#" + str(config.importer.frame), (to_int(self.width / 2), 12), font, .7, (255, 255, 255), 0, cv2.LINE_4) i = 0 while i < 5: frame_source[to_int(self.height * i / 5) - 1:to_int(self.height * i / 5) + 1, 0:self.width] = (100, 100, 100) i += 1 cv2.imshow("CONFIGURATION", np.hstack( (frame_source, frame_preview))) cv2.imshow("BINARY", np.vstack((stock_P, stock_CR))) self.out.write(frame_preview) self.key = cv2.waitKey(50) if self.key == ord("-"): cv2.imwrite( "screen_cap_fr{}.jpg".format(config.importer.frame), frame_preview) self.key_listener(self.key) else: # real tracking self.out.write(frame_preview) cv2.imshow("TRACKING", frame_preview) key = cv2.waitKey(1) if key == ord("q"): config.engine.release()