def test_contains_with_overlapping_rectangles(self): rect1 = Rectangle(0, 0, 50, 50) rect2 = Rectangle(10, 40, 20, 20) border_rect = Rectangle(10, 40, 10, 10) self.assertFalse(rect2 in rect1) self.assertTrue(border_rect in rect1)
def find_bubbles(labelnum, labels, rectangles, img, show_image=False): height, width = img.shape colors = dict() colors[0] = 0 for label in range(1, labelnum): labels[labels == label] = random.randint(0, 255) labels = labels.astype(np.uint8) img1 = cv2.bitwise_not(img) _, contours, hierarchy = cv2.findContours( img1, cv2.cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) n = 0 area = float(labels.shape[0] * labels.shape[1]) medium_tree = Tree() large_tree = Tree() for elem in hierarchy[0]: contour = contours[n] x, y, w, h = cv2.boundingRect(contour) bounding_box = Rectangle(x, y, w, h) box_area = bounding_box.area() node = Node(elem[3], n, contour, bounding_box) if box_area > area / 10: large_tree.add(node) elif box_area > area / 1000: medium_tree.add(node) n += 1 possible_bubbles = [(node, level) for node, level in medium_tree.level_order_traversal()] img2 = np.zeros((height, width, 3), np.uint8) if show_image: for node, level1 in medium_tree.level_order_traversal(): if node.n != -1: cv2.drawContours(img2, contours, node.n, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)), -1) for rect in rectangles: print("Drawing ", rect) img2 = cv2.rectangle(img2, (rect.x, rect.y), (rect.r_bot.x, rect.r_bot.y), (255, 255, 0), 1) """ font = cv2.FONT_HERSHEY_SIMPLEX bottomLeftCornerOfText = (rect.l_bot.x, rect.l_bot.y) fontScale = 0.4 fontColor = (255, 255, 255) cv2.putText(img2, str(rect.area()), bottomLeftCornerOfText, font, fontScale, fontColor) """ show(img2) return possible_bubbles
def is_first_letter(rect, quadtree, draw = None): left_rec = Rectangle(rect.x - rect.height, rect.y, rect.height, rect.height) if draw: left_rec.draw(draw, outline="yellow") neighbours = [] quadtree.retrieve(neighbours, rect) for rect1 in neighbours: if rect1 is not rect and rect1.overlaps_with(left_rec): return False return True
def test_get_index(self): bounds = Rectangle(0, 0, 400, 400) quadtree = Quadtree(0, bounds) rect1 = Rectangle(100, 10, 10, 10) rect2 = Rectangle(190, 10, 20, 20) quadtree.insert(rect1) quadtree.insert(rect2) self.assertEqual(1, quadtree.get_index(rect1)) self.assertEqual(-1, quadtree.get_index(rect2))
def test_retrieve(self): bounds = Rectangle(0, 0, 1000, 1000) quadtree = Quadtree(0, bounds) for x in range(10): for y in range(10): quadtree.insert(Rectangle(x * 20, y * 20, 10, 10)) rect3 = Rectangle(150, 10, 10, 10) rectangles = [] quadtree.retrieve(rectangles, rect3) self.assertEqual(len(rectangles), 33)
def split(self): x = self.bounds.x y = self.bounds.y subWidth = self.bounds.width / 2 subHeight = self.bounds.height / 2 self.nodes[0] = Quadtree( self.level + 1, Rectangle(x + subWidth, y, subWidth, subHeight)) self.nodes[1] = Quadtree(self.level + 1, Rectangle(x, y, subWidth, subHeight)) self.nodes[2] = Quadtree( self.level + 1, Rectangle(x, y + subHeight, subWidth, subHeight)) self.nodes[3] = Quadtree( self.level + 1, Rectangle(x + subWidth, y + subHeight, subWidth, subHeight))
def cv2_connected_components(img1, min_size=50, max_size=100000): img1 = cv2.bitwise_not(img1) labelnum, labels, stats, centroids = cv2.connectedComponentsWithStats(img1) rectangles = [] for label in range(1, labelnum): x, y, w, h, size = stats[label] rect = Rectangle(x,y,w,h) if min_size < rect.area() < max_size: rectangles.append(rect) return labelnum, labels, rectangles
def mask_groups(img, groups, possible_bubbles): """ Returns list of masked images :param img: image to mask :type img: Image :param groups: group of rectangles to use as masks :return: list of masked images and their top left corner position on the original image :rtype: list[int, int, Image, Rectangle, list[Rectangles], list[Rectangle]] """ masks = [] width, height = img.size used_bubbles = [] for label in groups: lines = groups[label] (x_max, x_min, y_max, y_min) = crop_size_rectangles(groups[label]) bounding_box = Rectangle(x_min, y_min, x_max - x_min, y_max - y_min) line_length = len(lines) if line_length <= 1: continue """ if line_length > 1 or \ (line_length == 1 and lines[0].width/lines[0].height > 2 and lines[0].width * lines[0].height > 500): """ highest_level = 0 index = -1 for i, bubble in enumerate(possible_bubbles): if i != 0 and \ bounding_box in bubble[0].box: if i in used_bubbles: break if highest_level < bubble[1]: highest_level=bubble[1] index = i if index > 0 and index not in used_bubbles: used_bubbles.append(index) bubble = possible_bubbles[index][0] cv2_img = np.full((height, width), 255, dtype=np.uint8) cv2.drawContours(cv2_img, [bubble.contour], 0, 0, -1) masked_img = Image.fromarray(cv2_img) draw = ImageDraw.Draw(masked_img) for rect in lines: rect.draw(draw, fill=True) temp_img = img.copy() temp_img.paste(masked_img, mask=masked_img) bounding_box = bubble.box temp_img = temp_img.crop((bounding_box.x, bounding_box.y, bounding_box.x + bounding_box.width, bounding_box.y + bounding_box.height)) masks.append((bounding_box.x, bounding_box.y, temp_img, bounding_box, lines, bubble)) continue return masks
def iterate_rectangles(rectangles1, rectangles2, width, height): quadtree = Quadtree(0, Rectangle(0, 0, width, height)) for rect in rectangles2: if rect: quadtree.insert(rect) for rect in rectangles1: neighbours = [] quadtree.retrieve(neighbours, rect) for rect1 in neighbours: yield rect, rect1
def get_lines(rectangles, width, height): """ Finds all rectangles that are aligned with each other and have similar height and groups them :param rectangles: :type: Rectangle :return: groups of rectangles :rtype: dict[int, list] """ n = 0 lines = dict() quadtree = Quadtree(0, Rectangle(0,0,width, height)) for rect in rectangles: quadtree.insert(rect) for rect in rectangles: is_first = is_first_letter(rect, quadtree) if is_first: last_rect = rect lines[n] = list() lines[n].append(last_rect) neighbours = [] quadtree.retrieve(neighbours, rect) for rect1 in sorted(neighbours, key=lambda rec: rec.x): right_last_rect = Rectangle(last_rect.x + last_rect.width, last_rect.y, last_rect.height * 1.5, last_rect.height) if rect is not rect1 and \ rect.inline(rect1) and \ right_last_rect.intersects(rect1) and \ math.sqrt(pow(rect.height-rect1.height, 2)) < 0.5*rect.height: last_rect = rect1 lines[n].append(last_rect) n += 1 result = [] for key in lines: line = line_bounding_box(lines[key]) result.append(line) return result
def group_lines(lines): """ Groups lines together :param lines: dictionary that contains all the black pixels in a line :type lines: dict[int, list] :return: :rtype: dict[int, list[Rectangle]] """ bounding_boxes = dict() uf_arr = UFarray() n = 0 for line in lines: bounding_boxes[n] = line n += 1 groups = dict() uf_arr.setLabel(len(bounding_boxes)) for n in bounding_boxes: rect = bounding_boxes[n] top_rect = Rectangle(rect.x, rect.y + rect.height, rect.width, rect.height) bottom_rect = Rectangle(rect.x, rect.y - rect.height, rect.width, rect.height) for k in bounding_boxes: rect1 = bounding_boxes[k] if rect is not rect1: if (rect1.intersects(bottom_rect) or rect1.intersects(top_rect)) and \ abs(rect.height - rect1.height) < 0.3 * rect.height: uf_arr.setLabel(max(n, k)) uf_arr.union(n, k) uf_arr.flatten() for n in bounding_boxes: index = uf_arr.find(n) line_list = groups.get(index, list()) line_list.append(bounding_boxes[n]) groups[index] = line_list return groups
def third_pass(rectangles): possible_letters = list() for rec in rectangles: top_rec = Rectangle(rec.x, rec.y - rec.height, rec.height, rec.height) bottom_rec = Rectangle(rec.x, rec.y + rec.height, rec.height, rec.height) right_rec = Rectangle(rec.x + rec.width, rec.y, rec.height, rec.height) left_rec = Rectangle(rec.x - rec.width, rec.y - rec.height, rec.height, rec.height) for rect2 in rectangles: if (rect2.intersects(top_rec) or rect2.intersects(right_rec) or rect2.intersects(bottom_rec) or rect2.intersects(left_rec) or rect2.intersects(rec)): if rec not in possible_letters: possible_letters.append(rec) logger.info("found " + str(len(possible_letters)) + " possible letters") return possible_letters
def line_bounding_box(line): left = min([v.l_top.x for v in line]) right = max([v.r_bot.x for v in line]) top = min([v.l_top.y for v in line]) bottom = max([v.r_bot.y for v in line]) return Rectangle(left, top, (right - left), (bottom - top))
def test_contains(self): rect1 = Rectangle(0, 0, 50, 50) rect2 = Rectangle(10, 10, 20, 20) self.assertTrue(rect2 in rect1) self.assertFalse(rect1 in rect2)
def test_overlap(self): rect1 = Rectangle(0, 0, 50, 50) rect2 = Rectangle(10, 10, 100, 20) self.assertTrue(rect2.overlaps_with(rect1))