def extract_features(self, hull): (x, y), (w, h), angle = cv2.minAreaRect(hull) if w < h: angle -= 90 w, h = h, w angle = angle % 180 h_im = min(self._buff_fg.shape) w_im = max(self._buff_fg.shape) max_h = 2 * h_im if w > max_h or h > max_h: raise NoPositionError self.ellipse = self.object_mask(**{"x": x, "y": y, "w": w, "h": h, "angle": angle}, roi=self._buff_fg) # cv2.ellipse(self._buff_fg, ((x, y), (int(w * 1.5), int(h * 1.5)), angle), 255, -1) # TODO center mass just on the ellipse area cv2.bitwise_and(self._buff_fg_backup, self._buff_fg, self._buff_fg_backup) cv2.bitwise_and(self._buff_fg_backup, self.ellipse, self._buff_fg_backup) y, x = ndimage.measurements.center_of_mass(self._buff_fg_backup) pos = x +1.0j * y pos /= w_im xy_dist = round(log10(1. / float(w_im) + abs(pos - self._old_pos)) * 1000) # cv2.bitwise_and(self._buff_fg_diff,self._buff_fg,dst=self._buff_fg_diff) # sum_diff = cv2.countNonZero(self._buff_fg_diff) # xor_dist = (sum_fg + self._old_sum_fg - 2*sum_diff) / float(sum_fg + self._old_sum_fg) # xor_dist *=1000. # self._old_sum_fg = sum_fg self._old_pos = pos x_var = XPosVariable(int(round(x))) y_var = YPosVariable(int(round(y))) distance = XYDistance(int(xy_dist)) #xor_dist = XorDistance(int(xor_dist)) w_var = WidthVariable(int(round(w))) h_var = HeightVariable(int(round(h))) phi_var = PhiVariable(int(round(angle))) # mlogl = mLogLik(int(distance*1000)) out = DataPoint([ x_var, y_var, w_var, h_var, phi_var, #mlogl, distance, #xor_dist #Label(0) ]) self._previous_shape = np.copy(hull) return [out]
def _run(self, cursor=None): try: for index, (t_ms, img) in self.camera_iterator: if t_ms > self.end_fragment: break if cursor: template = "SELECT x, y, w, h, phi, xy_dist_log10x1000 FROM ROI_%d WHERE t = %d" command = template % (self._region_id, t_ms) cursor.execute(command) try: X = next(iter(cursor)) except Exception: # an exception will happen when the t queried # is not available in the dbfile # even though it is in the video # this happens if the dbfile is generated # passing a drop-each argument != 1 # i.e. the dbfile is subsampled if self._all_frames: self.add(img) continue xpos, ypos, width, height, phi, xy_dist = X x_var = XPosVariable(xpos) y_var = YPosVariable(ypos) h_var = HeightVariable(height) w_var = WidthVariable(width) phi_var = PhiVariable(phi) distance = XYDistance(xy_dist) point = DataPoint( [x_var, y_var, w_var, h_var, phi_var, distance]) self.positions.append(point) abs_pos = self.get_last_positions(absolute=True) self._last_positions[self.roi.idx] = abs_pos out = self.drawer.draw(img, tracking_units=[self], positions=self._last_positions, roi=True) else: out = img self.add(out) except Exception as error: logging.error(traceback.print_exc()) raise error
def _track(self, img, grey, mask,t): if self._bg_model.bg_img is None: self._buff_fg = np.empty_like(grey) self._buff_object= np.empty_like(grey) self._buff_fg_backup = np.empty_like(grey) # self._buff_fg_diff = np.empty_like(grey) self._old_pos = 0.0 +0.0j # self._old_sum_fg = 0 raise NoPositionError bg = self._bg_model.bg_img.astype(np.uint8) cv2.subtract(grey, bg, self._buff_fg) cv2.threshold(self._buff_fg,20,255,cv2.THRESH_TOZERO, dst=self._buff_fg) # cv2.bitwise_and(self._buff_fg_backup,self._buff_fg,dst=self._buff_fg_diff) # sum_fg = cv2.countNonZero(self._buff_fg) self._buff_fg_backup = np.copy(self._buff_fg) n_fg_pix = np.count_nonzero(self._buff_fg) prop_fg_pix = n_fg_pix / (1.0 * grey.shape[0] * grey.shape[1]) is_ambiguous = False if prop_fg_pix > self._max_area: self._bg_model.increase_learning_rate() raise NoPositionError if prop_fg_pix == 0: self._bg_model.increase_learning_rate() raise NoPositionError if CV_VERSION == 3: _, contours,hierarchy = cv2.findContours(self._buff_fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) else: contours,hierarchy = cv2.findContours(self._buff_fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = [cv2.approxPolyDP(c,1.2,True) for c in contours] if len(contours) == 0: self._bg_model.increase_learning_rate() raise NoPositionError elif len(contours) > 1: if not self.fg_model.is_ready: raise NoPositionError # hulls = [cv2.convexHull( c) for c in contours] hulls = contours #hulls = merge_blobs(hulls) hulls = [h for h in hulls if h.shape[0] >= 3] if len(hulls) < 1: raise NoPositionError elif len(hulls) > 1: is_ambiguous = True cluster_features = [self.fg_model.compute_features(img, h) for h in hulls] all_distances = [self.fg_model.distance(cf,t) for cf in cluster_features] good_clust = np.argmin(all_distances) hull = hulls[good_clust] distance = all_distances[good_clust] else: hull = contours[0] if hull.shape[0] < 3: self._bg_model.increase_learning_rate() raise NoPositionError features = self.fg_model.compute_features(img, hull) distance = self.fg_model.distance(features,t) if distance > self._max_m_log_lik: self._bg_model.increase_learning_rate() raise NoPositionError (x,y) ,(w,h), angle = cv2.minAreaRect(hull) if w < h: angle -= 90 w,h = h,w angle = angle % 180 h_im = min(grey.shape) w_im = max(grey.shape) max_h = 2*h_im if w>max_h or h>max_h: raise NoPositionError cv2.ellipse(self._buff_fg ,((x,y), (int(w*1.5),int(h*1.5)),angle),255,-1) #todo center mass just on the ellipse area cv2.bitwise_and(self._buff_fg_backup, self._buff_fg,self._buff_fg_backup) y,x = ndimage.measurements.center_of_mass(self._buff_fg_backup) pos = x +1.0j*y pos /= w_im xy_dist = round(log10(1./float(w_im) + abs(pos - self._old_pos))*1000) # cv2.bitwise_and(self._buff_fg_diff,self._buff_fg,dst=self._buff_fg_diff) # sum_diff = cv2.countNonZero(self._buff_fg_diff) # xor_dist = (sum_fg + self._old_sum_fg - 2*sum_diff) / float(sum_fg + self._old_sum_fg) # xor_dist *=1000. # self._old_sum_fg = sum_fg self._old_pos = pos if mask is not None: cv2.bitwise_and(self._buff_fg, mask, self._buff_fg) if is_ambiguous: self._bg_model.increase_learning_rate() self._bg_model.update(grey, t) else: self._bg_model.decrease_learning_rate() self._bg_model.update(grey, t, self._buff_fg) self.fg_model.update(img, hull,t) x_var = XPosVariable(int(round(x))) y_var = YPosVariable(int(round(y))) distance = XYDistance(int(xy_dist)) #xor_dist = XorDistance(int(xor_dist)) w_var = WidthVariable(int(round(w))) h_var = HeightVariable(int(round(h))) phi_var = PhiVariable(int(round(angle))) # mlogl = mLogLik(int(distance*1000)) out = DataPoint([x_var, y_var, w_var, h_var, phi_var, #mlogl, distance, #xor_dist #Label(0) ]) self._previous_shape=np.copy(hull) return [out]
def _track(self, img, grey, mask, t): if self._bg_model.bg_img is None: self._buff_fg = np.empty_like(grey) self._buff_object = np.empty_like(grey) self._buff_fg_backup = np.empty_like(grey) # self._buff_fg_diff = np.empty_like(grey) self._old_pos = 0.0 + 0.0j # self._old_sum_fg = 0 raise NoPositionError bg = self._bg_model.bg_img.astype(np.uint8) cv2.subtract(grey, bg, self._buff_fg) cv2.threshold(self._buff_fg, 20, 255, cv2.THRESH_TOZERO, dst=self._buff_fg) # L.Zi. make a copy of extracted foreground to illustrate detection for debugging fg_cpy = np.copy(self._buff_fg) # cv2.bitwise_and(self._buff_fg_backup,self._buff_fg,dst=self._buff_fg_diff) # sum_fg = cv2.countNonZero(self._buff_fg) self._buff_fg_backup = np.copy(self._buff_fg) n_fg_pix = np.count_nonzero(self._buff_fg) prop_fg_pix = n_fg_pix / (1.0 * grey.shape[0] * grey.shape[1]) is_ambiguous = False if prop_fg_pix > self._max_area: # if non-black pixel count in foreground is bigger than the allowed maximum # (five times the expected animal size) self._bg_model.increase_learning_rate() raise NoPositionError if prop_fg_pix == 0: # if no non-black pixel in foreground is found self._bg_model.increase_learning_rate() raise NoPositionError if CV_VERSION == 3: _, contours, hierarchy = cv2.findContours(self._buff_fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) else: contours, hierarchy = cv2.findContours(self._buff_fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = [cv2.approxPolyDP(c, 1.2, True) for c in contours] if len(contours) == 0: # if no contour around the non-black foreground pixels may be detected self._bg_model.increase_learning_rate() raise NoPositionError elif len(contours) > 1: # if more than a single contour is found if not self.fg_model.is_ready: raise NoPositionError # hulls = [cv2.convexHull( c) for c in contours] hulls = contours #hulls = merge_blobs(hulls) hulls = [h for h in hulls if h.shape[0] >= 3] if len(hulls) < 1: raise NoPositionError elif len(hulls) > 1: is_ambiguous = True cluster_features = [ self.fg_model.compute_features(img, h) for h in hulls ] all_distances = [ self.fg_model.distance(cf, t) for cf in cluster_features ] good_clust = np.argmin(all_distances) hull = hulls[good_clust] distance = all_distances[good_clust] else: hull = contours[0] if hull.shape[0] < 3: self._bg_model.increase_learning_rate() raise NoPositionError features = self.fg_model.compute_features(img, hull) distance = self.fg_model.distance(features, t) if distance > self._max_m_log_lik: self._bg_model.increase_learning_rate() raise NoPositionError (x, y), (w, h), angle = cv2.minAreaRect(hull) if w < h: angle -= 90 w, h = h, w angle = angle % 180 h_im = min(grey.shape) w_im = max(grey.shape) max_h = 2 * h_im if w > max_h or h > max_h: raise NoPositionError #todo center mass just on the ellipse area cv2.bitwise_and(self._buff_fg_backup, self._buff_fg, self._buff_fg_backup) y, x = ndimage.measurements.center_of_mass(self._buff_fg_backup) pos = x + 1.0j * y # L. Zi.: #pos /= w_im #xy_dist = round(log10(1./float(w_im) + abs(pos - self._old_pos))*1000) # L. Zi.: want linear motion distance xy_dist = round(abs(pos - self._old_pos)) # cv2.bitwise_and(self._buff_fg_diff,self._buff_fg,dst=self._buff_fg_diff) # sum_diff = cv2.countNonZero(self._buff_fg_diff) # xor_dist = (sum_fg + self._old_sum_fg - 2*sum_diff) / float(sum_fg + self._old_sum_fg) # xor_dist *=1000. # self._old_sum_fg = sum_fg self._old_pos = pos if mask is not None: cv2.bitwise_and(self._buff_fg, mask, self._buff_fg) if is_ambiguous: self._bg_model.increase_learning_rate() self._bg_model.update(grey, t) else: self._bg_model.decrease_learning_rate() self._bg_model.update(grey, t, self._buff_fg) self.fg_model.update(img, hull, t) x_var = XPosVariable(int(round(x))) y_var = YPosVariable(int(round(y))) distance = XYDistance(int(xy_dist)) #xor_dist = XorDistance(int(xor_dist)) w_var = WidthVariable(int(round(w))) h_var = HeightVariable(int(round(h))) phi_var = PhiVariable(int(round(angle))) # mlogl = mLogLik(int(distance*1000)) # L. Zi.: produce and show adaptive background processing results if self._dbg_single_roi_do_rec: if self._roi._value == self._dbg_single_roi_value: animal_colour = (255, 255, 255) cv2.ellipse(fg_cpy, ((x, y), (int(w * 1.5), int(h * 1.5)), angle), animal_colour, 1, cv2.LINE_AA) grey_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) h = round(self._roi.rectangle[3]) w = round(self._roi.rectangle[2]) # draw roi value cv2.putText(fg_cpy, str(self._roi._value), (5, h - 10), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255)) # draw motion distance of detected animal since last frame cv2.putText(fg_cpy, str(int(xy_dist)), (5, 25), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255)) txt = str(int(t)) (txt_w, txt_h), _ = cv2.getTextSize(txt, cv2.FONT_HERSHEY_DUPLEX, 1, 1) cv2.putText(fg_cpy, txt, (w - txt_w - 5, 25), cv2.FONT_HERSHEY_DUPLEX, 1, (255, 255, 255)) vis = np.concatenate((grey_img, bg, fg_cpy), axis=1) #vis = np.concatenate((grey_img, grey, fg_cpy), axis=1) cv2.namedWindow("Processing", cv2.WINDOW_NORMAL) cv2.resizeWindow("Processing", 1800, 600) cv2.imshow("Processing", vis) cv2.waitKey(100) # record a video if self._dbg_roi_video_writer is None: fourcc_string = 'DIVX' fourcc = cv2.VideoWriter_fourcc(*fourcc_string) self._dbg_roi_video_writer = cv2.VideoWriter( self._dbg_single_roi_video_filename, fourcc, 2.0, (vis.shape[1], vis.shape[0]), isColor=False) self._dbg_roi_video_writer.write(vis) out = DataPoint([ x_var, y_var, w_var, h_var, phi_var, #mlogl, distance, #xor_dist #Label(0) ]) self._previous_shape = np.copy(hull) return [out]
def run(self, result_writer = None, drawer = None): """ Runs the monitor indefinitely. :param result_writer: A result writer used to control how data are saved. `None` means no results will be saved. :type result_writer: :class:`~ethoscope.utils.io.ResultWriter` :param drawer: A drawer to plot the data on frames, display frames and/or save videos. `None` means none of the aforementioned actions will performed. :type drawer: :class:`~ethoscope.drawers.drawers.BaseDrawer` """ try: logging.info("Monitor starting a run") self._is_running = True for i, (t, frame) in enumerate(self._camera): #logging.info("Monitor: frame: %d, time: %d" % (i, t)) if self._force_stop: logging.info("Monitor object stopped from external request") break self._last_frame_idx = i self._last_time_stamp = t self._frame_buffer = frame #logStr = "Monitor: frame: %d, time: %d" % (i, t) empty_cnt = 0 for j, track_u in enumerate(self._unit_trackers): data_rows = track_u.track(t, frame) if len(data_rows) == 0: self._last_positions[track_u.roi.idx] = [] empty_cnt += 1 # L. Zi do not skip trackers not returning a detection # but fill in a data row with zero values #continue data_rows = [DataPoint([XPosVariable(0), YPosVariable(0), WidthVariable(0), HeightVariable(0), PhiVariable(0), XYDistance(0), IsInferredVariable(0), HasInteractedVariable(0)]) ] abs_pos = track_u.get_last_positions(absolute=True) # if abs_pos is not None: self._last_positions[track_u.roi.idx] = abs_pos if not result_writer is None: #logging.info(logStr + ", Roi: %d, %s" % (track_u.roi.idx, data_rows)) result_writer.write(t, track_u.roi, data_rows) #logging.info(logStr + " empty data cnt: %d" % (empty_cnt)) if result_writer is not None: result_writer.flush(t, frame) if drawer is not None: drawer.draw(frame, t, self._last_positions, self._unit_trackers) self._last_t = t except Exception as e: logging.error("Monitor closing with an exception: '%s'" % traceback.format_exc(e)) raise e finally: self._is_running = False logging.info("Monitor closing")