def draw_props(properties): global img, img_src img = img_src.copy() for props in properties: if props['Ellipse'] != None: cv2.circle(img, props['Centroid'], 5, BLUE, 1) cv2.ellipse(img, props['Ellipse'], CYAN) cv2.drawContours(img, [props['ConvexHull']], 0, RED, 1) major_axis = ft.angled_line(props['Centroid'], props['Orientation'], props['MajorAxisLength']/2) cv2.line(img, tuple(major_axis[0]), tuple(major_axis[1]), RED) minor_axis = ft.angled_line(props['Centroid'], props['Orientation'] + 90, props['MinorAxisLength']/2) cv2.line(img, tuple(minor_axis[0]), tuple(minor_axis[1]), BLUE) box = cv2.cv.BoxPoints(props['BoundingBox']) box = np.int0(box) cv2.drawContours(img, [box], 0, CYAN, 1) for p in props['Extrema']: cv2.circle(img, p, 5, CYAN, 1) cv2.imshow('image', img)
def draw_angle_shape(angle): global rotation, img, center, intersects # Draw the angle. line = ft.extreme_points(intersects[angle] + intersects[angle + 180]) if line == None: line = ft.angled_line(center, angle + rotation, 100) cv2.line(img, tuple(line[0]), tuple(line[1]), GREEN) # Draw main intersections. for x, y in intersects[angle]: cv2.circle(img, (x, y), 5, RED) for x, y in intersects[angle + 180]: cv2.circle(img, (x, y), 5, BLUE) cv2.imshow('image', img)
def draw_angle_shape(angle): global rotation, img, center, intersects # Draw the angle. line = ft.extreme_points(intersects[angle] + intersects[angle + 180]) if line == None: line = ft.angled_line(center, angle + rotation, 100) cv2.line(img, tuple(line[0]), tuple(line[1]), GREEN) # Draw main intersections. for x, y in intersects[angle]: cv2.circle(img, (x, y), 5, RED) for x, y in intersects[angle + 180]: cv2.circle(img, (x, y), 5, BLUE) cv2.imshow("image", img)
def shape_360_v1(contour, rotation=0, step=1, t=8): """Returns a shape feature from a contour. The rotation in degrees of the contour can be set with `rotation`, which must be a value between 0 and 179 inclusive. A rotation above 90 degrees is interpreted as a rotation to the left (e.g. a rotation of 120 is interpreted as 60 degrees to the left). The returned shape is shifted by the rotation. The step size for the angle can be set with `step`. Argument `t` is passed to :meth:`weighted_points_nearest`. The returned shape is rotation invariant provided that the rotation argument is set properly and the rotation is no more than 90 degrees left or right. Shape is returned as a tuple ``(intersects, center)``, where ``intersects`` is a dict where the keys are 0 based angles and the values are lists of intersecting points. If `step` is set to 1, then the dict will contain the intersections for 360 degrees. ``center`` specifies the center of the contour and can be used to calculate distances from center to contour. """ if len(contour) < 6: raise ValueError("Contour must have at least 6 points, found %d" % len(contour)) if not 0 <= rotation <= 179: raise ValueError("The rotation must be between 0 and 179 inclusive, found %d" % rotation) # Get the center. center, _ = cv2.minEnclosingCircle(contour) center = np.int32(center) # If the rotation is more than 90 degrees, assume the object is rotated to # the left. if rotation <= 90: a = 0 b = 180 else: a = 180 b = 0 # Get the intersecting points with the contour for every degree # from the symmetry axis. intersects = {} for angle in range(0, 180, step): # Get the slope for the linear function of this angle and account for # the rotation of the object. slope = ft.slope_from_angle(angle + rotation, inverse=True) # Since pixel points can only be defined with natural numbers, # resulting in gaps in between two points, means the maximum # gap is equal to the slope of the linear function. gap_max = math.ceil(abs(slope)) # Find al contour points that closely fit the angle's linear function. # Only save points for which the distance to the expected point is # no more than the maximum gap. Save each point with a weight value, # which is used for clustering. weighted_points = [] for p in contour: p = p[0] x = p[0] - center[0] y = p[1] - center[1] if math.isinf(slope): if x == 0: # Save points that are on the vertical axis. weighted_points.append( (1, tuple(p)) ) else: y_exp = slope * x d = abs(y - y_exp) if d <= gap_max: w = 1 / (d+1) weighted_points.append( (w, tuple(p)) ) assert len(weighted_points) != 0, "No intersections found" # Cluster the points. weighted_points = ft.weighted_points_nearest(weighted_points, t) _, points = zip(*weighted_points) # Figure out on which side of the symmetry the points lie. # Create a line that separates points on the left from points on # the right. if (angle + rotation) != 0: division_line = ft.angled_line(center, angle + rotation + 90, 100) else: # Points cannot be separated when the division line is horizontal. # So make a division line that is rotated 45 degrees to the left # instead, so that the points are properly separated. division_line = ft.angled_line(center, angle + rotation - 45, 100) intersects[angle] = [] intersects[angle+180] = [] for p in points: side = ft.side_of_line(division_line, p) if side < 0: intersects[angle+a].append(p) elif side > 0: intersects[angle+b].append(p) else: assert side != 0, "A point cannot be on the division line" return (intersects, center)
def shape_360_v1(contour, rotation=0, step=1, t=8): """Returns a shape feature from a contour. The rotation in degrees of the contour can be set with `rotation`, which must be a value between 0 and 179 inclusive. A rotation above 90 degrees is interpreted as a rotation to the left (e.g. a rotation of 120 is interpreted as 60 degrees to the left). The returned shape is shifted by the rotation. The step size for the angle can be set with `step`. Argument `t` is passed to :meth:`weighted_points_nearest`. The returned shape is rotation invariant provided that the rotation argument is set properly and the rotation is no more than 90 degrees left or right. Shape is returned as a tuple ``(intersects, center)``, where ``intersects`` is a dict where the keys are 0 based angles and the values are lists of intersecting points. If `step` is set to 1, then the dict will contain the intersections for 360 degrees. ``center`` specifies the center of the contour and can be used to calculate distances from center to contour. """ if len(contour) < 6: raise ValueError("Contour must have at least 6 points, found %d" % len(contour)) if not 0 <= rotation <= 179: raise ValueError( "The rotation must be between 0 and 179 inclusive, found %d" % rotation) # Get the center. center, _ = cv2.minEnclosingCircle(contour) center = np.int32(center) # If the rotation is more than 90 degrees, assume the object is rotated to # the left. if rotation <= 90: a = 0 b = 180 else: a = 180 b = 0 # Get the intersecting points with the contour for every degree # from the symmetry axis. intersects = {} for angle in range(0, 180, step): # Get the slope for the linear function of this angle and account for # the rotation of the object. slope = ft.slope_from_angle(angle + rotation, inverse=True) # Since pixel points can only be defined with natural numbers, # resulting in gaps in between two points, means the maximum # gap is equal to the slope of the linear function. gap_max = math.ceil(abs(slope)) # Find al contour points that closely fit the angle's linear function. # Only save points for which the distance to the expected point is # no more than the maximum gap. Save each point with a weight value, # which is used for clustering. weighted_points = [] for p in contour: p = p[0] x = p[0] - center[0] y = p[1] - center[1] if math.isinf(slope): if x == 0: # Save points that are on the vertical axis. weighted_points.append((1, tuple(p))) else: y_exp = slope * x d = abs(y - y_exp) if d <= gap_max: w = 1 / (d + 1) weighted_points.append((w, tuple(p))) assert len(weighted_points) != 0, "No intersections found" # Cluster the points. weighted_points = ft.weighted_points_nearest(weighted_points, t) _, points = zip(*weighted_points) # Figure out on which side of the symmetry the points lie. # Create a line that separates points on the left from points on # the right. if (angle + rotation) != 0: division_line = ft.angled_line(center, angle + rotation + 90, 100) else: # Points cannot be separated when the division line is horizontal. # So make a division line that is rotated 45 degrees to the left # instead, so that the points are properly separated. division_line = ft.angled_line(center, angle + rotation - 45, 100) intersects[angle] = [] intersects[angle + 180] = [] for p in points: side = ft.side_of_line(division_line, p) if side < 0: intersects[angle + a].append(p) elif side > 0: intersects[angle + b].append(p) else: assert side != 0, "A point cannot be on the division line" return (intersects, center)