def adapt_tail_contours_initially(self, tails): """ adapt tail contour to _frame, assuming that they could be quite far away """ # setup active contour algorithm ac = ActiveContour(blur_radius=self.params['contour/blur_radius_initial'], closed_loop=True, alpha=self.params['contour/line_tension'], beta=self.params['contour/bending_stiffness'], gamma=self.params['contour/adaptation_rate']) ac.max_iterations = self.params['contour/max_iterations'] ac.set_potential(self.contour_potential) # get rectangle describing the interior of the _frame height, width = self._frame.shape region_center = shapes.Rectangle(0, 0, width, height) region_center.buffer(-self.params['contour/border_anchor_distance']) # adapt all the tails for tail in tails: # find points not at the center anchor_idx = ~region_center.points_inside(tail.contour) tail.contour = ac.find_contour(tail.contour, anchor_idx, anchor_idx) logging.debug('Active contour took %d iterations.', ac.info['iteration_count'])
def adapt_tail_contours(self, tails): """ adapt tail contour to _frame, assuming that they are already close """ # get the tails that we want to adapt if self.params['detection/every_frame']: tails_estimate = self.locate_tails_roughly(tails) else: tails_estimate = tails[:] #< copy list if len(tails_estimate) != len(tails): raise RuntimeError('Found %d instead of %d tails in this frame.' % (len(tails), len(tails_estimate))) # setup active contour algorithm ac = ActiveContour(blur_radius=self.params['contour/blur_radius'], closed_loop=True, alpha=self.params['contour/line_tension'], beta=self.params['contour/bending_stiffness'], gamma=self.params['contour/adaptation_rate']) ac.max_iterations = self.params['contour/max_iterations'] #potential_approx = self.threshold_gradient_strength(gradient_mag) ac.set_potential(self.contour_potential) # debug.show_shape(*[t.contour for t in tails], # background=self.contour_potential) # get rectangle describing the interior of the _frame height, width = self._frame.shape region_center = shapes.Rectangle(0, 0, width, height) region_center.buffer(-self.params['contour/border_anchor_distance']) # iterate through all previous tails for k, tail_prev in enumerate(tails): # find the tail that is closest center = tail_prev.centroid idx = np.argmin([curves.point_distance(center, t.centroid) for t in tails_estimate]) # adapt this contour to the potential tail_estimate = tails_estimate.pop(idx) # determine the points close to the boundary that will be anchored # at their position, because there are no features to track at the # boundary anchor_idx = ~region_center.points_inside(tail_estimate.contour) # disable anchoring for points at the posterior end of the tail ps = tail_estimate.contour[anchor_idx] dists = spatial.distance.cdist(ps, [tail_estimate.endpoints[0]]) dist_threshold = self.params['contour/typical_width'] anchor_idx[anchor_idx] = (dists.flat > dist_threshold) # use an active contour algorithm to adapt the contour points contour = ac.find_contour(tail_estimate.contour, anchor_idx, anchor_idx) logging.debug('Active contour took %d iterations.', ac.info['iteration_count']) # update the old tail to keep the identity of sides tails[k] = Tail.create_similar(contour, tail_prev) return tails
def get_features(self, tails=None, use_annotations=False, ret_raw=False): """ calculates a feature mask based on the image statistics """ # calculate image statistics ksize = self.params['detection/statistics_window'] _, var = image.get_image_statistics(self._frame, kernel='circle', ksize=ksize) # threshold the variance to locate features threshold = self.params['detection/statistics_threshold']*np.median(var) bw = (var > threshold).astype(np.uint8) if ret_raw: return bw if tails is None: tails = tuple() # add features from the previous frames if present for tail in tails: # fill the features with the interior of the former tail polys = tail.polygon.buffer(-self.params['detection/shape_max_speed']) if not isinstance(polys, geometry.MultiPolygon): polys = [polys] for poly in polys: cv2.fillPoly(bw, [np.array(poly.exterior.coords, np.int)], color=1) # calculate the distance to other tails to bound the current one buffer_dist = self.params['detection/shape_max_speed'] poly_outer = tail.polygon.buffer(buffer_dist) for tail_other in tails: if tail is not tail_other: dist = tail.polygon.distance(tail_other.polygon) if dist < buffer_dist: # shrink poly_outer poly_other = tail_other.polygon.buffer(dist/2) poly_outer = poly_outer.difference(poly_other) # make sure that this tail is separated from all the others try: coords = np.array(poly_outer.exterior.coords, np.int) except AttributeError: # can happen when the poly_outer is complex pass else: cv2.polylines(bw, [coords], isClosed=True, color=0, thickness=2) # debug.show_image(self._frame, bw, wait_for_key=False) if use_annotations: lines = self.annotations['segmentation_dividers'] if lines: logging.debug('Found %d annotation lines for segmenting', len(lines)) for line in lines: cv2.line(bw, tuple(line[0]), tuple(line[1]), 0, thickness=3) else: logging.debug('Found no annotations for segmenting') # remove features at the edge of the image border = self.params['detection/border_distance'] image.set_image_border(bw, size=border, color=0) # remove very thin features cv2.morphologyEx(bw, cv2.MORPH_OPEN, np.ones((3, 3)), dst=bw) # find features in the binary image contours = cv2.findContours(bw.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] # determine the rectangle where objects can lie in h, w = self._frame.shape rect = shapes.Rectangle(x=0, y=0, width=w, height=h) rect.buffer(-2*border) rect = geometry.Polygon(rect.contour) boundary_length_max = self.params['detection/boundary_length_max'] bw[:] = 0 num_features = 0 for contour in contours: if cv2.contourArea(contour) > self.params['detection/area_min']: # check whether the object touches the border feature = geometry.Polygon(np.squeeze(contour)) if rect.exterior.intersects(feature): # fill the hole in the feature difference = rect.difference(feature) if isinstance(difference, geometry.Polygon): difference = [difference] #< make sure we handle a list for diff in difference: # check the length along the rectangle boundary_length = diff.intersection(rect.exterior).length if boundary_length < boundary_length_max: feature = feature.union(diff) # reduce feature, since detection typically overshoots features = feature.buffer(-0.5*self.params['detection/statistics_window']) if not isinstance(features, geometry.MultiPolygon): features = [features] for feature in features: if feature.area > self.params['detection/area_min']: #debug.show_shape(feature, background=self._frame) # extract the contour of the feature contour = regions.get_enclosing_outline(feature) contour = np.array(contour.coords, np.int) # fill holes inside the objects num_features += 1 cv2.fillPoly(bw, [contour], num_features) # debug.show_image(self._frame, var, bw, wait_for_key=False) return bw, num_features
def get_burrows_from_image(self, mask, ground_line): """ load burrow polygons from an image """ # turn image into gray scale height, width = mask.shape # get a polygon for cutting away the sky above_ground = ground_line.get_polygon(0, left=0, right=width) # determine contours in the mask contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] # iterate through the contours burrows = [] for contour in contours: points = contour[:, 0, :] if len(points) <= 2: continue # get the burrow area area = cv2.contourArea(contour) if area < self.params['scale_bar/area_max']: # object could be a scale bar rect = shapes.Rectangle(*cv2.boundingRect(contour)) at_left = (rect.left < self.params['scale_bar/dist_left'] * width) max_dist_bottom = self.params['scale_bar/dist_bottom'] at_bottom = (rect.bottom > (1 - max_dist_bottom) * height) hull = cv2.convexHull(contour) hull_area = cv2.contourArea(hull) is_simple = (hull_area < 2 * area) if at_left and at_bottom and is_simple: # the current polygon is the scale bar _, (w, h), _ = cv2.minAreaRect(contour) if max(w, h) > self.params['scale_bar/length_min']: raise RuntimeError('Found something that looks like a ' 'scale bar') if area > self.params['burrow/area_min']: # build polygon out of the contour points burrow_poly = geometry.Polygon(points) # regularize the points to remove potential problems burrow_poly = regions.regularize_polygon(burrow_poly) # debug.show_shape(geometry.Polygon(points), above_ground, # background=mask) # build the burrow polygon by removing the sky burrow_poly = burrow_poly.difference(above_ground) # create a burrow from the outline boundary = regions.get_enclosing_outline(burrow_poly) burrow = Burrow(boundary.coords, parameters=self.params['burrow_parameters']) burrows.append(burrow) logging.info('Found %d polygons' % len(burrows)) return burrows