Exemplo n.º 1
0
    def calculate_bounds(self):
        """ Calculates the bounding box of points of the polygon. """

        bounds_min_x = min(self.x_points)
        bounds_min_y = min(self.y_points)

        bounds_max_x = max(self.x_points)
        bounds_max_y = max(self.y_points)

        self.bounds = Rectangle(bounds_min_x, bounds_min_y, width=bounds_max_x - bounds_min_x,
                                height=bounds_max_y - bounds_min_y)
Exemplo n.º 2
0
    def get_bounding_box(self):
        """ Get the bounding box of this polygon (= smallest rectangle including this polygon).

        :return: (Rectangle) rectangle defining the bounds of this polygon
        """
        if self.n_points == 0:
            return Rectangle()

        if self.bounds is None:
            self.calculate_bounds()

        return self.bounds.get_bounds()
Exemplo n.º 3
0
 def test_dist_fast(self):
     bb = Rectangle(0, 0, 10, 10)
     p_list = [[x, y] for x in [-1, 5, 11] for y in [-1, 5, 11]]
     dist_list = []
     for p in p_list:
         dist_list += [get_dist_fast(p, bb)]
     self.assertEqual(2, dist_list[0])
     self.assertEqual(1, dist_list[1])
     self.assertEqual(2, dist_list[2])
     self.assertEqual(1, dist_list[3])
     self.assertEqual(0, dist_list[4])
     self.assertEqual(1, dist_list[5])
     self.assertEqual(2, dist_list[6])
     self.assertEqual(1, dist_list[7])
     self.assertEqual(2, dist_list[8])
Exemplo n.º 4
0
def stretch_rectangle_until_whitespace(binarized_image, rectangle, whitespace_height=1, stretch_limit=250):
    """

    :type rectangle: Rectangle
    """
    new_rectangle = copy.deepcopy(rectangle)
    # whitespace_rectangle = Rectangle(x=rectangle.x, y=rectangle.y - whitespace_height, width=rectangle.width,
    #                                  height=whitespace_height)
    whitespace_rectangle = Rectangle(x=rectangle.x + rectangle.width // 5, y=rectangle.y - whitespace_height,
                                     width=3 * rectangle.width // 5,
                                     height=whitespace_height)

    if whitespace_rectangle.y < 0 or whitespace_rectangle.y + whitespace_rectangle.height > binarized_image.shape[1]:
        return new_rectangle

    for i in range(stretch_limit):
        if is_whitespace(binarized_image, whitespace_rectangle, threshold=0.04) or whitespace_rectangle.y == 0:
            new_rectangle.set_bounds(rectangle.x, whitespace_rectangle.y, rectangle.width,
                                     rectangle.height + i + 1)
            break
        else:
            whitespace_rectangle.translate(0, -1)

    return new_rectangle
Exemplo n.º 5
0
    def run(self):
        rotation_angle = round(self.get_best_rotation_angle(), 4)
        self.rotate_images(rotation_angle)

        region_rectangle_image = Rectangle(0, 0, self.image_width,
                                           self.image_height)
        self.run_recursion(region_rectangle_image, threshold=0.9)

        plt.set_cmap('gray')
        plt.subplot(1, 3, 1)
        plt.imshow(self.images["empty_image"])
        plt.subplot(1, 3, 2)
        plt.imshow(self.images["text_block"])
        plt.subplot(1, 3, 3)
        plt.imshow(self.images["original_image"])

        plt.show()
Exemplo n.º 6
0
def merge_rectangles(rectangle_list):
    """

    :param rectangle_list:
    :type rectangle_list: list of Rectangle
    :return: minimal Rectangle object that holds all rectangles in rectangle_list
    """

    min_x = min(rectangle_list, key=lambda rectangle: rectangle.x).x
    max_x = max(rectangle_list,
                key=lambda rectangle: rectangle.x + rectangle.width
                ).get_vertices()[1][0]
    min_y = min(rectangle_list, key=lambda rectangle: rectangle.y).y
    max_y = max(rectangle_list,
                key=lambda rectangle: rectangle.y + rectangle.height
                ).get_vertices()[2][1]

    return Rectangle(min_x, min_y, max_x - min_x, max_y - min_y)
Exemplo n.º 7
0
    def run_recursion(self,
                      region_rectangle: Rectangle,
                      max_recursion_depth=MAX_RECURSION_DEPTH,
                      mode="horizontal",
                      threshold=0.9):
        """ Run recursion to determine the text regions. Make sure to alternate between horizontal and vertical
        separator detection. The `mode` parameter determines with which subdivision to start, defaults to 'horizontal'.

        :param region_rectangle: determines the region in the original text block image
        :param threshold: relative number of white pixels that should be reached to be defined as a white run.
        :param mode: same parameter as in method `get_separators`, 'horizontal' or 'vertical'.
        :param max_recursion_depth: maximal number of times to run the recursion
        :return: a mask that can be applied to the baseline detection output to get a division into text regions
        """
        print(MAX_RECURSION_DEPTH - max_recursion_depth)

        if max_recursion_depth == 0:
            return

        image = self.images["text_block"]

        image = image[region_rectangle.x:region_rectangle.x +
                      region_rectangle.width][region_rectangle.
                                              y:region_rectangle.y +
                                              region_rectangle.height]

        # The min_pixel_separator_distance determines up to which (pixel)distance neighboring white runs get merged!
        min_pixel_separator_distance = int(self.image_height *
                                           MIN_PIXEL_SEPARATOR_DISTANCE_FACTOR)
        print(f"min_pixel_separator_distance = {min_pixel_separator_distance}")

        # profile_list = self.get_separators(255 - self.images['text_block'], mode, threshold)
        profile_list = self.get_separators(255 - image, mode, threshold)
        index_separators = [i for i, _ in profile_list]
        if not index_separators:
            return

        index_separators_new = []
        if index_separators[0] > min_pixel_separator_distance:
            index_separators_new.append((0, index_separators[0]))

        for i in range(len(index_separators) - 1):
            if index_separators[i + 1] - index_separators[
                    i] > min_pixel_separator_distance:
                index_separators_new.append(
                    (index_separators[i] + 1, index_separators[i + 1]))
        if mode == 'horizontal':
            if (self.image_height -
                    1) - index_separators[-1] > min_pixel_separator_distance:
                index_separators_new.append(
                    (index_separators[-1], self.image_height - 1))
        elif mode == 'vertical':
            if (self.image_width -
                    1) - index_separators[-1] > min_pixel_separator_distance:
                index_separators_new.append(
                    (index_separators[-1], self.image_width - 1))
        # print(index_separators)
        # print(index_separators_new)

        new_mode = None
        if mode == "horizontal":
            new_mode = "vertical"
        elif mode == "vertical":
            new_mode = "horizontal"

        new_region_rectangle = None
        for image_range in index_separators_new:
            # image_range is a tuple with x coordinate from
            # new_region_rectangle = None
            if mode == "horizontal":
                # update the y-coordinates and keep the x-coordinates
                new_y = image_range[0] + region_rectangle.y
                new_height = image_range[1] - image_range[0]
                new_region_rectangle = Rectangle(region_rectangle.x, new_y,
                                                 region_rectangle.width,
                                                 new_height)
            elif mode == "vertical":
                # update the x-coordinates and keep the y-coordinates
                new_x = image_range[0] + region_rectangle.x
                new_width = image_range[1] - image_range[0]
                new_region_rectangle = Rectangle(new_x, region_rectangle.y,
                                                 new_width,
                                                 region_rectangle.height)
            print("REGION RECTANGLE COORD: ",
                  new_region_rectangle.get_vertices())
            cv2.rectangle(self.images["empty_image"],
                          new_region_rectangle.get_vertices()[0],
                          new_region_rectangle.get_vertices()[2], (255, 0, 0),
                          1)
            # self.get_separators(self.images["text_block"][image_range[0]:image_range[1]], new_mode, threshold)
            self.run_recursion(new_region_rectangle, max_recursion_depth - 1,
                               new_mode, max(0.9 * threshold, 0.65))

        return new_region_rectangle
Exemplo n.º 8
0
    orig_image = cv2.imread(path_to_orig_image, cv2.IMREAD_UNCHANGED)
    tb_outline_image = cv2.imread(path_to_tb_outline, cv2.IMREAD_UNCHANGED)
    tb_image = cv2.imread(path_to_tb, cv2.IMREAD_UNCHANGED)
    separator_image = cv2.imread(path_to_separator, cv2.IMREAD_UNCHANGED)

    orig_image = cv2.resize(orig_image, None, fx=0.4, fy=0.4)
    # orig_image_gb = cv2.GaussianBlur(orig_image, (5, 5), 0)
    orig_image_gb = orig_image
    _, orig_image_gb_bin = cv2.threshold(orig_image_gb, 0, 255,
                                         cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    tb_pp = TextBlockNetPostProcessor(orig_image_gb_bin, tb_outline_image,
                                      tb_image, separator_image)

    region_rectangle_image = Rectangle(0, 0, orig_image.shape[1],
                                       orig_image.shape[0])
    # tb_pp.run_recursion(region_rectangle_image)
    #
    # text_block_rgb = cv2.cvtColor(tb_pp.images["text_block"], cv2.COLOR_BGR2RGB)
    # # text_block_rgb = tb_pp.images["text_block"]
    # plt.imshow(text_block_rgb)
    # plt.show()

    tb_pp.run()

    # # CONTOURS TEST
    # original_image_rgb = cv2.cvtColor(tb_pp.images["original_image"], cv2.COLOR_BGR2RGB)
    # text_block_image_rgb = cv2.cvtColor(tb_pp.images["text_block"], cv2.COLOR_BGR2RGB)
    # plt.subplot(1, 2, 1)
    # plt.imshow(text_block_image_rgb)
    # contours, _ = cv2.findContours(tb_pp.images["text_block"], cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
Exemplo n.º 9
0
def get_orientation_rectangles(point, dims=(600, 300, 600, 300), offset=0):
    # Verticals are North and South
    height_v = dims[0]
    width_v = dims[1]
    # Horizontals are East and West
    height_h = dims[2]
    width_h = dims[3]
    pt_x = point[0]
    pt_y = point[1]
    rect_n = Rectangle(pt_x - width_v // 2, pt_y - height_v, width_v, height_v)
    rect_n.translate(dx=0, dy=offset)
    rect_s = Rectangle(pt_x - width_v // 2, pt_y, width_v, height_v)
    rect_s.translate(dx=0, dy=-offset)
    rect_e = Rectangle(pt_x, pt_y - height_h // 2, width_h, height_h)
    rect_e.translate(dx=-offset, dy=0)
    rect_w = Rectangle(pt_x - width_h, pt_y - height_h // 2, width_h, height_h)
    rect_w.translate(dx=offset, dy=0)
    return {'n': rect_n, 'e': rect_e, 's': rect_s, 'w': rect_w}
Exemplo n.º 10
0
class Polygon(object):

    def __init__(self, x_points=None, y_points=None, n_points=0):
        """ Constructs a new polygon.

        :param x_points: list of x coordinates of the polygon
        :type x_points: list of int
        :param y_points: list of y coordinates of the polygon
        :type y_points: list of int
        :param n_points: total number of points in the polygon
        :type n_points: int
        :param bounds: bounding box of the polygon in shape of a rectangle
        :type bounds: Rectangle
        """
        if x_points is not None:
            assert type(x_points) == list, "x_points has to be a list of ints"
            assert all(type(x) == int for x in x_points), "x_points has to be a list of ints"
            if n_points > len(x_points) or n_points > len(y_points):
                raise Exception("Bounds Error: n_points > len(x_points) or n_points > len(y_points)")

            self.x_points = x_points
        else:
            self.x_points = []

        if y_points is not None:
            assert type(y_points) == list, "y_points has to be a list of ints"
            assert all(type(y) == int for y in y_points), "y_points has to be a list of ints"
            if n_points > len(x_points) or n_points > len(y_points):
                raise Exception("Bounds Error: n_points > len(x_points) or n_points > len(y_points)")

            self.y_points = y_points
        else:
            self.y_points = []

        assert type(n_points) == int, "n_points has to be int"
        if n_points < 0:
            raise Exception("Negative Size: n_points < 0")

        self.n_points = n_points
        self.bounds = None  # bounds of this polygon (Rectangle type !!!)

    def as_list(self):
        return list(zip(self.x_points, self.y_points))

    def rescale(self, scale):
        points_rescaled = rescale_points(self.as_list(), scale)
        x, y = zip(*points_rescaled)
        self.x_points = list(x)
        self.y_points = list(y)

        if self.bounds:
            self.calculate_bounds()

    def translate(self, delta_x, delta_y):
        """ Translates the vertices of this polygon by delta_x along the x axis and by delta_y along the y axis.

        :param delta_x: (int) amount to translate along the x axis
        :param delta_y: (int) amount to translate along the y axis
        """
        assert type(delta_x) == int, "delta_x has to be int"
        assert type(delta_y) == int, "delta_y has to be int"

        for i in range(self.n_points):
            self.x_points[i] += delta_x
            self.y_points[i] += delta_y

        if self.bounds is not None:
            self.bounds.translate(delta_x, delta_y)

    def calculate_bounds(self):
        """ Calculates the bounding box of points of the polygon. """

        bounds_min_x = min(self.x_points)
        bounds_min_y = min(self.y_points)

        bounds_max_x = max(self.x_points)
        bounds_max_y = max(self.y_points)

        self.bounds = Rectangle(bounds_min_x, bounds_min_y, width=bounds_max_x - bounds_min_x,
                                height=bounds_max_y - bounds_min_y)

    def update_bounds(self, x, y):
        """ Resizes the bounding box to accommodate the specified coordinates.

        :param x: (int) x coordinate
        :param y: (int) y coordinate
        """
        assert type(x) == int, "x has to be int"
        assert type(y) == int, "y has to be int"

        if x < self.bounds.x:
            self.bounds.width = self.bounds.width + (self.bounds.x - x)
            self.bounds.x = x
        else:
            self.bounds.width = max(self.bounds.width, x - self.bounds.x)

        if y < self.bounds.y:
            self.bounds.height = self.bounds.height + (self.bounds.y - y)
            self.bounds.y = y
        else:
            self.bounds.height = max(self.bounds.height, y - self.bounds.y)

    def add_point(self, x, y):
        """ Appends the specified coordinates to this polygon.

        :param x: (int) x coordinate of the added point
        :param y: (int) y coordinate of the added point
        """
        assert type(x) == int, "x has to be int"
        assert type(y) == int, "y has to be int"

        self.x_points.append(x)
        self.y_points.append(y)
        self.n_points += 1

        if self.bounds is not None:
            self.update_bounds(x, y)

    def get_bounding_box(self):
        """ Get the bounding box of this polygon (= smallest rectangle including this polygon).

        :return: (Rectangle) rectangle defining the bounds of this polygon
        """
        if self.n_points == 0:
            return Rectangle()

        if self.bounds is None:
            self.calculate_bounds()

        return self.bounds.get_bounds()

    def contains_point(self, point):
        """
        Check if point is contained in polygon.
        Run a semi-infinite ray horizontally (increasing x, fixed y) out from the test point,
        and count how many edges it crosses. At each crossing, the ray switches between inside and outside.
        This is called the Jordan curve theorem.
        :param point: tuple with x- and y-coordinates
        :return: bool, whether or not the point is contained in the polygon
        """
        # simple boundary check
        if not self.get_bounding_box().contains_point(point):
            return False

        is_inside = False
        point_x = point[0]
        point_y = point[1]
        for i in range(self.n_points):
            if (self.y_points[i] > point_y) is not (self.y_points[i - 1] > point_y):
                if point_x < (self.x_points[i - 1] - self.x_points[i]) * (point_y - self.y_points[i]) / \
                        (self.y_points[i - 1] - self.y_points[i]) + self.x_points[i]:
                    is_inside = not is_inside
        return is_inside
    def create_subregions_from_surrounding_polygon(self, ar_list=None, des_dist=5, max_d=50, max_rect_size=0):

        # width1 equals width2 if width is even, else width2 = width1 + 1
        # same for height1 and height2
        if ar_list is None:
            ar_list = []
        width1 = self.width // 2
        width2 = self.width - width1
        height1 = self.height // 2
        height2 = self.height - height1

        #########################
        #           #           #
        #     I     #     II    #
        #           #           #
        #########################
        #           #           #
        #    III    #     IV    #
        #           #           #
        #########################

        # determine textlines for each subregion
        tl1 = []
        tl2 = []
        tl3 = []
        tl4 = []
        bounds1 = []
        bounds2 = []
        bounds3 = []
        bounds4 = []
        a_ids1 = set()
        a_ids2 = set()
        a_ids3 = set()
        a_ids4 = set()

        # Get the non-overlapping bounding boxes for the algorithm
        tl_list = self.initialize_gt_generation(des_dist, max_d)

        for tl, tl_bound, tl_id in tl_list:

            intersection_rect = tl_bound.intersection(Rectangle(self.x, self.y, width1, height1))
            if intersection_rect.width > 0 and intersection_rect.height > 0:
                tl1 += [tl]
                bounds1 += [tl_bound]
                a_ids1.add(tl_id)

            intersection_rect = tl_bound.intersection(Rectangle(self.x + width1, self.y, width2, height1))
            if intersection_rect.width > 0 and intersection_rect.height > 0:
                tl2 += [tl]
                bounds2 += [tl_bound]
                a_ids2.add(tl_id)

            intersection_rect = tl_bound.intersection(Rectangle(self.x, self.y + height1, width1, height2))
            if intersection_rect.width > 0 and intersection_rect.height > 0:
                tl3 += [tl]
                bounds3 += [tl_bound]
                a_ids3.add(tl_id)

            intersection_rect = tl_bound.intersection(Rectangle(self.x + width1, self.y + height1, width2, height2))
            if intersection_rect.width > 0 and intersection_rect.height > 0:
                tl4 += [tl]
                bounds4 += [tl_bound]
                a_ids4.add(tl_id)

        a_rect1 = ArticleRectangle(self.x, self.y, width1, height1, tl1, a_ids1)
        a_rect2 = ArticleRectangle(self.x + width1, self.y, width2, height1, tl2, a_ids2)
        a_rect3 = ArticleRectangle(self.x, self.y + height1, width1, height2, tl3, a_ids3)
        a_rect4 = ArticleRectangle(self.x + width1, self.y + height1, width2, height2, tl4, a_ids4)

        # run create_subregions_from_surrounding_polygon on Rectangles that contain more than one TextLine object
        for a_rect in [a_rect1, a_rect2, a_rect3, a_rect4]:
            if len(a_rect.a_ids) > 1:
                a_rect.create_subregions_from_surrounding_polygon(ar_list, max_rect_size=max_rect_size)
            # TODO: height or width?
            elif 0 < max_rect_size < a_rect.height:
                a_rect.create_subregions_from_surrounding_polygon(ar_list, max_rect_size=max_rect_size)
            else:
                ar_list.append(a_rect)

        return ar_list
    print("Len(Blank) = ", len(ars_dict["blank"]))

    # Convert blank article rectangles (by rectangles)
    # ... over bounding boxes
    ars_dict = convert_blank_article_rects_by_rects(ars_dict, method="bb")
    print("Len(Blank) = ", len(ars_dict["blank"]))
    # ... over convex hulls
    ars_dict = convert_blank_article_rects_by_rects(ars_dict, method="ch")
    print("Len(Blank) = ", len(ars_dict["blank"]))

    # Convert the article rectangles to surrounding polygons
    surr_polys_dict = {}
    for a_id, ars_sub in ars_dict.items():
        # if a_id == 'blank':
        #     continue
        rs = [Rectangle(ar.x, ar.y, ar.width, ar.height) for ar in ars_sub]
        surr_polys = ortho_connect(rs)
        surr_polys_dict[a_id] = surr_polys

    # # # Print it
    # plot_dict = {}
    # for k in surr_polys_dict:
    #     plot_dict[k] = []
    #     for poly in surr_polys_dict[k]:
    #         plot_dict[k].append(poly.as_list())
    # plot_gt_data(path_to_img, plot_dict, None, show=False)

    # TODO: Check blank rectangles for correct intersections
    # TODO: More should get converted here imo?!
    # Convert blank article rectangles (by polygons)
    # ... over bounding boxes