def test_contains_point_returns_true_if_the_specified_point_is_within_radius( self): circle = Circle(Point(0, 0), 10) point_center = Point(0, 0) self.assertTrue(circle.contains_point(point_center)) point_middle = Point(5, 5) self.assertTrue(circle.contains_point(point_middle))
def _calculate_square_and_writing_metric(self, binary_image, transform, size): """ Similar to the _calculate_square_metric function above, but also includes the regions above and below that barcode that contain a text version of the information contained in the barcode. This may do a better job of correctly fitting the square in certain circumstances, but may also get trapped if there are dark/shadow regions around the edge of the image. """ center = transform.trans angle = transform.rot rotated = binary_image.rotate(angle, center) brightness = rotated.calculate_brightness(center, size, size) * size**2 txt_height = self.TXT_HEIGHT * size txt_width = self.TXT_WIDTH * size area = txt_width * txt_height rect1_center = center + Point(0, self.TXT_OFFSET*size) rect2_center = center - Point(0, self.TXT_OFFSET*size) brightness += rotated.calculate_brightness(rect1_center, txt_width, txt_height) * area brightness += rotated.calculate_brightness(rect2_center, txt_width, txt_height) * area brightness /= (size**2 + 2*area) self.count += 1 return brightness
def test_scale_returns_new_circle_which_is_a_scaled_version_of_input_one( self): circle = Circle(Point(3, 0), 10) new_circle = circle.offset(Point(3, 3)) self.assertEqual(new_circle.x(), 6) self.assertEqual(new_circle.y(), 3) self.assertEqual(new_circle.radius(), 10)
def find_location(self): # find center, radius and location of the puck # use feature detection to identify the orientation of the puck self.unipuck_contours = self._find_puck_contours() (x, y), radius = self._find_enclosing_circle_of_largest_contour() features_cnt = self._find_contours_of_features(x, y, radius) match_factor, match_cnt = self._find_feature(features_cnt) uni = None if (match_factor < FEATURE_MATCH_FACTOR): # take only the very good feature matches hull = cv2.convexHull(match_cnt) hull_area = cv2.contourArea(hull) feature_area = cv2.contourArea(match_cnt) #puck_area = math.pi * math.sqrt(radius) area_factor = feature_area / hull_area #puck_area_factor = feature_area / puck_area if round(area_factor, 2) > FEATURE_HULL_MATCH_FACTOR:# and puck_area_factor > PUCK_FEATURE_AREA_FACTOR_MIN: # take the features which have only small convexity defects (cx, cy) = self._find_contour_momentum(match_cnt) feature_orientation = math.atan2(cy - y, cx - x) # feature orientation puck_orientation = feature_orientation - math.pi / 2 # puck orientation shifted -pi/2 rad from feature orientation uni = Unipuck(Point(x, y), radius) uni.set_rotation(puck_orientation) uni.set_feature_center(Point(cx, cy)) uni.set_feature_boarder(match_cnt) return uni
def TRIANGLE_DEMO(): """ Draw a set of axes and a triangle. Perform a series of random transformations on the triangle and display the results. """ A = Point(143, 52) B = Point(17, 96.5) C = Point(0, 0) for i in range(10): # Create random transformation angle = random.random() * 2 * math.pi scale = random.random() * 3 delX = (random.random() - 0.5) * 200 delY = (random.random() - 0.5) * 200 translate = Point(delX, delY) transform = Transform(translate, angle, scale) # Transform the triangle A_ = transform.transform(A) B_ = transform.transform(B) C_ = transform.transform(C) # From the line A-B and the transformed line A'-B', determine what the transformation was # This should be the same as the original transformation trans_calc = Transform.line_mapping(A, B, A_, B_) print("Angle: {:.2f}; {:.2f}".format(rad_to_deg(angle), rad_to_deg(trans_calc.rot))) print("Trans: ({}); ({})".format(translate, trans_calc.trans)) print("Zoom: {:.2f}; {:.2f}".format(scale, trans_calc.zoom)) # Display on image image = Image.blank(1000, 800) image.draw_offset = IMG_CENTER draw_axes(image, 300) # Draw original triangle image.draw_line(A, B, Color.Red(), 5) image.draw_line(C, B, Color.Red(), 5) image.draw_line(A, C, Color.Red(), 5) #Draw transformed triangle image.draw_line(A_, B_, Color.Green()) image.draw_line(A_, C_, Color.Green()) image.draw_line(C_, B_, Color.Green()) # Check that the reverse transformation works properly A__ = transform.reverse(A_) B__ = transform.reverse(B_) C__ = transform.reverse(C_) # Draw the reverse transformation - this should overlap the origianl triangle image.draw_line(A__, B__, Color.Green(), 1) image.draw_line(A__, C__, Color.Green(), 1) image.draw_line(C__, B__, Color.Green(), 1) # Write the transformation on the image image.draw_text(transform.__str__(), Point(-450, 350), Color.White(), centered=False, scale=0.5, thickness=1) # Show the image image.popup()
def edges_image(edge_sets): blank = Image.blank(image_original.width, image_original.height, 3, 255) for shape in edge_sets: for edge in shape: print(edge[1]) blank.draw_line(Point.from_array(edge[0]), Point.from_array(edge[1]), Color.Green(), 1) return blank
def draw_axes(img, length): # Draw axes x_neg = Point(-length, 0) x_pos = Point(length, 0) y_neg = Point(0, -length) y_pos = Point(0, length) img.draw_line(y_neg, y_pos, Color.White(), 5) img.draw_line(x_neg, x_pos, Color.White(), 5)
def draw_rectangle(self, roi, color, thickness=2): """ Draw the specified rectangle on the image (in place). """ top_left = self._format_point(Point(roi[0], roi[1])) bottom_right = self._format_point(Point(roi[2], roi[3])) cv2.rectangle(self.img, top_left.tuple(), bottom_right.tuple(), color.bgra(), thickness=thickness)
def _find_puck_center(pin_centers): """ Calculate approximate center point of the puck from positions of the center points of the pin slots. Within each layer there may be some missing points, so if we calculate the center position of the puck by averaging the center positions of the slots, the results will be a bit out. Instead, we use the average center position (the centroid) as a starting point and divide the slots into two groups based on how close they are to the centroid. As long as not too many slots are missing, the division into groups should work well. We then iterate over different values for the puck center position, attempting to find a location that is equidistant from all of the slot centers. """ centroid = calculate_centroid(pin_centers) # Calculate distance from center to each pin-center distances = [[point, point.distance_to(centroid)] for point in pin_centers] distances = sorted(distances, key=lambda distance: distance[1]) # Sort the points into two layers based on their distance from the centroid layer_break = _partition([d for p, d in distances]) first_layer = [p for p, d in distances[:layer_break]] second_layer = [p for p, d in distances[layer_break:]] # Optimise for the puck center by finding the point that is equidistant from every point in each layer center = fmin(func=_center_minimiser, x0=centroid.tuple(), args=tuple([[first_layer, second_layer]]), xtol=1, disp=False) center = Point(center[0], center[1]).intify() return center
def test_draw_circle_thickness_equals_one(self): img = Image.blank(40, 40) circle = Circle(Point(20, 20), 10) img.draw_circle(circle, Color(200, 200, 200), 1) self.assertEquals(img.img[10][20][1], 200) self.assertEquals(img.img[9][20][1], 0) self.assertEquals(img.img[11][20][1], 0)
def _sanitize_circles(raw_circles): circles = [] if raw_circles is not None: for raw in raw_circles[0]: center = Point(int(raw[0]), int(raw[1])) radius = int(raw[2]) circles.append(Circle(center, radius)) return circles
def _rotate_around_point(point, angle, center): """ Rotate the point about the center position """ x = point.x - center.x y = point.y - center.y cos = math.cos(angle) sin = math.sin(angle) x_ = x * cos - y * sin y_ = x * sin + y * cos return Point(x_, y_) + center
def draw_text(self, text, position, color, centered=False, scale=1.5, thickness=3): """ Draw the specified text on the image (in place). """ if centered: text_size = opencv.getTextSize(text, opencv.FONT_HERSHEY_SIMPLEX, fontScale=scale, thickness=thickness)[0] text_size = Point(-text_size[0]/2.0, text_size[1]/2.0) position = (position + text_size) position = self._format_point(position) opencv.putText(self.img, text, position.tuple(), opencv.FONT_HERSHEY_SIMPLEX, fontScale=scale, color=color.bgra(), thickness=thickness)
def deserialize(string): """ Generate a Unipuck object from a string representation. """ tokens = string.split(Unipuck._SERIAL_DELIM) center = Point(int(tokens[0]), int(tokens[1])) radius = int(tokens[2]) angle = float(tokens[3]) return Unipuck(center, radius, angle)
def CIRCLES_DEMO(): """ Draw a set of axes and a random set of circles. Perform a series of random transformations on the circles. """ # Create a set of random circles points = [] for i in range(10): X = (random.random()) * 200 Y = (random.random()) * 200 points.append(Point(X, Y)) for i in range(10): # Create random transformation angle = random.random() * 2 * math.pi scale = random.random() * 3 delX = (random.random() - 0.5) * 200 delY = (random.random() - 0.5) * 200 translate = Point(delX, delY) trs = Transform(translate, angle, scale) # Display on image image = Image.blank(1000, 800) image.draw_offset = IMG_CENTER draw_axes(image, 300) # Draw the circles and transformed circles on the image radius = 10 for p in points: circle = Circle(p, radius) trans_circle = Circle(trs.transform(p), radius * trs.zoom) image.draw_circle(circle, Color.Red()) image.draw_circle(trans_circle, Color.Blue()) # Write the transformation on the image image.draw_text(trs.__str__(), Point(-450, 350), Color.White(), centered=False, scale=0.5, thickness=1) # Show the image image.popup()
def test_sub_image_is_created_if_the_center_is_outside_but_the_radius_ovelaps_with_input_image( self): image = Image.blank(9, 9, 3, 0) x_center = 10 y_center = 10 radius = 2 sub_image, roi = image.sub_image(Point(x_center, y_center), radius) height = sub_image.height width = sub_image.width self.assertEquals(width, 1) self.assertEquals(height, 1)
def test_sub_image_has_size_of_input_image_if_2xradius_covers_the_whole_image( self): image = Image.blank(5, 6, 3, 0) x_center = 2 y_center = 2 radius = 7 sub_image, roi = image.sub_image(Point(x_center, y_center), radius) height = sub_image.height width = sub_image.width self.assertEquals(width, image.width) self.assertEquals(height, image.height)
def test_sub_image_has_size_0x0_if_center_and_radius_outside_the_input_image( self): image = Image.blank(5, 6, 3, 0) x_center = 10 y_center = 10 radius = 2 sub_image, roi = image.sub_image(Point(x_center, y_center), radius) height = sub_image.height width = sub_image.width self.assertEquals(width, 0) self.assertEquals(height, 0)
def test_circle_is_correctly_detected_when_there_there_are_two_circles_in_the_image_not_intersecting( self): img = Image.blank(100, 100) circle_a = Circle(Point(20, 20), 10) circle_b = Circle(Point(50, 50), 10) img.draw_circle(circle_a, Color(10, 50, 100), 2) img.draw_circle(circle_b, Color(10, 50, 100), 2) grey = img.to_grayscale() decorator = CircleDetector() decorator.set_maximum_radius(20) decorator.set_minimum_radius(5) decorator.set_accumulator_threshold(30) decorator.set_canny_threshold(30) decorator.set_minimum_separation(10) list = decorator.find_circles(grey) self.assertEqual(list.__len__(), 2)
def test_sub_image_is_not_square_if_center_x_is_too_close_to_the_edge( self): image = Image.blank(10, 10, 3, 0) x_center = 9 y_center = 5 radius = 2 sub_image, roi = image.sub_image(Point(x_center, y_center), radius) height = sub_image.height width = sub_image.width self.assertEquals(width, 3) self.assertEquals(height, 2 * radius) self.assertNotEquals(width, height)
def _center_minimiser(center, dist): """ Used as the cost function in an optimisation routine. for a trial center point, we calculate an error that is the sum of the squares of the deviation of the distance of each slot from the mean distance of all the slots. """ errors = [] center = Point.from_array(center) distances = [p.distance_to_sq(center) for p in dist] mean = np.mean(distances) layer_errors = [(d - mean)**2 for d in distances] errors.extend(layer_errors) return sum(errors)
def _draw_square(image, transform, size): radius = size/2 center = Point(transform.x, transform.y) rotated = image.rotate(transform.rot, center) x1, y1 = center.x-radius, center.y-radius x2, y2 = x1 + size, y1 + size roi = (x1, y1, x2, y2) marked_img = rotated.to_alpha() marked_img.draw_rectangle(roi, Color.Green(), 1) return marked_img
def _make_transforms(transform, grid_points, angle_points): transforms = [] for x in grid_points: for y in grid_points: for degrees in angle_points: radians = degrees * math.pi / 180 offset = Point(x, y) trs = transform.by_offset(offset) trs = trs.by_rotation(radians) transforms.append(trs) return transforms
def test_draw_circle_thickness_equals_four(self): img = Image.blank(40, 40) circle = Circle(Point(20, 20), 10) img.draw_circle(circle, Color(200, 200, 200), 4) self.assertEquals(img.img[6][20][1], 0) self.assertEquals(img.img[7][20][1], 0) #!! self.assertEquals(img.img[8][20][1], 200) self.assertEquals(img.img[10][20][1], 200) self.assertEquals(img.img[9][20][1], 200) self.assertEquals(img.img[11][20][1], 200) self.assertEquals(img.img[12][20][1], 200) self.assertEquals(img.img[13][20][1], 0) #!! self.assertEquals(img.img[14][20][1], 0)
def test_calculate_slot_bounds_returns_x0_for_slots1and6_if_rotation_and_center_are_0( self): #again the assumption that slot bounds have a particular sequence - slot bound[0] is slot number 1 center = Point(0, 0) rotation = 0 radius = 10 slot_bounds = Unipuck.calculate_slot_bounds(center, radius, rotation) self.assertTrue(len(slot_bounds) == 16) slot1 = slot_bounds[0] self.assertTrue(slot1.center().x == 0) slot6 = slot_bounds[5] self.assertTrue(slot6.center().x == 0)
def __init__(self, img): self.img = img size = self.img.shape self.width = size[1] self.height = size[0] if len(size) > 2: self.channels = size[2] else: self.channels = 1 # All draw requests will be offset by this amount self.draw_offset = Point(0, 0)
def _draw_finder_pattern(image, transform, fp): center = Point(transform.x, transform.y) angle = transform.rot rotated = image.rotate(transform.rot, center) c1 = _rotate_around_point(fp.c1, -angle, center) c2 = _rotate_around_point(fp.c2, -angle, center) c3 = _rotate_around_point(fp.c3, -angle, center) img = rotated.to_alpha() img.draw_line(c1, c2, Color.Green(), 1) img.draw_line(c1, c3, Color.Green(), 1) return img
def _get_finder_pattern(edges): """Return information about the "main" corner from a set of edges. This function finds the corner between the longest two edges, which should be spatially adjacent (it is up to the caller to make sure of this). It returns the position of the corner, and vectors corresponding to the said two edges, pointing away from the corner. These two vectors are returned in an order such that their cross product is positive, i.e. (see diagram) the base vector (a) comes before the side vector (b). ^side | | base X---> corner This provides a convenient way to refer to the position of a datamatrix. """ self = ContourLocator i, j = self._longest_pair_indices(edges) pair_longest_edges = [edges[x] for x in (i, j)] x_corner = self._get_shared_vertex(*pair_longest_edges) c, d = map(partial(self._get_other_vertex, x_corner), pair_longest_edges) vec_c, vec_d = map(partial(np.add, -x_corner), (c, d)) # FIXME: There seems to be a sign error here... if vec_c[0] * vec_d[1] - vec_c[1] * vec_d[0] < 0: vec_base, vec_side = vec_c, vec_d else: vec_base, vec_side = vec_d, vec_c x_corner = Point(x_corner[0], x_corner[1]).intify() vec_base = Point(vec_base[0], vec_base[1]).intify() vec_side = Point(vec_side[0], vec_side[1]).intify() return FinderPattern(x_corner, vec_base, vec_side)
def test_draw_circle_centre_is_kept(self): img = Image.blank(40, 40) circle = Circle(Point(20, 20), 1) img.draw_circle(circle, Color(200, 200, 200), 1) self.assertEquals(img.img[18][20][1], 0) self.assertEquals(img.img[19][20][1], 200) self.assertEquals(img.img[20][20][1], 0) self.assertEquals(img.img[21][20][1], 200) self.assertEquals(img.img[22][20][1], 0) self.assertEquals(img.img[20][18][1], 0) self.assertEquals(img.img[20][19][1], 200) self.assertEquals(img.img[20][20][1], 0) self.assertEquals(img.img[20][21][1], 200) self.assertEquals(img.img[20][22][1], 0)
def test_sub_image_is_square_with_side_length_2_radius_if_enough_space_to_cut_from( self): image = Image.blank(10, 10, 3, 0) x_center = 5 y_center = 5 radius = 2 sub_image, roi = image.sub_image(Point( x_center, y_center), radius) #sub_image returns a raw cv image height = sub_image.height width = sub_image.width self.assertEquals(width, 2 * radius) self.assertEquals(height, 2 * radius) self.assertEquals(width, height)
def _center_minimiser(center, layers): """ Used as the cost function in an optimisation routine. The puck consists of 2 layers of slots. Within a given layer, each slot is the same distance from the center point of the puck. Therefore for a trial center point, we calculate an error that is the sum of the squares of the deviation of the distance of each slot from the mean distance of all the slots in the layer. """ errors = [] center = Point.from_array(center) for layer in layers: distances = [p.distance_to_sq(center) for p in layer] mean = np.mean(distances) layer_errors = [(d-mean)**2 for d in distances] errors.extend(layer_errors) return sum(errors)
def _locate_finder_in_square(image, transform, size): """ For the located barcode in the image, identify which of the sides make up the finder pattern. """ radius = int(round(size/2)) center = transform.trans angle = transform.rot rotated = image.rotate(angle, center) sx1, sy1 = center.x-radius, center.y-radius sx2, sy2 = center.x+radius, center.y+radius thick = int(round(size / 14)) # Top x1, y1 = sx1, sy1 x2, y2 = sx2, sy1 + thick top = np.sum(rotated.img[y1:y2, x1:x2]) / (size * thick) # Left x1, y1 = sx1, sy1 x2, y2 = sx1 + thick, sy2 left = np.sum(rotated.img[y1:y2, x1:x2]) / (size * thick) # Bottom x1, y1 = sx1, sy2 - thick x2, y2 = sx2, sy2 bottom = np.sum(rotated.img[y1:y2, x1:x2]) / (size * thick) # Right x1, y1 = sx2 - thick, sy1 x2, y2 = sx2, sy2 right = np.sum(rotated.img[y1:y2, x1:x2]) / (size * thick) # Identify finder edges if top < bottom and left < right: c1 = [sx1, sy1] c2 = [sx1, sy2] c3 = [sx2, sy1] elif top < bottom and right < left: c1 = [sx2, sy1] c2 = [sx1, sy1] c3 = [sx2, sy2] elif bottom < top and left < right: c1 = [sx1, sy2] c2 = [sx2, sy2] c3 = [sx1, sy1] elif bottom < top and right < left: c1 = [sx2, sy2] c2 = [sx2, sy1] c3 = [sx1, sy2] else: return None # rotate points around center of square c1 = _rotate_around_point(Point.from_array(c1), angle, center) c2 = _rotate_around_point(Point.from_array(c2), angle, center) c3 = _rotate_around_point(Point.from_array(c3), angle, center) # Create finder pattern c1 = c1.intify() side1 = (c2 - c1).intify() side2 = (c3 - c1).intify() fp = FinderPattern(c1, side1, side2) return fp