Spaces:
Paused
Paused
| from detect_compo.lib_ip.Bbox import Bbox | |
| import detect_compo.lib_ip.ip_draw as draw | |
| import cv2 | |
| def cvt_compos_relative_pos(compos, col_min_base, row_min_base): | |
| for compo in compos: | |
| compo.compo_relative_position(col_min_base, row_min_base) | |
| def compos_containment(compos): | |
| for i in range(len(compos) - 1): | |
| for j in range(i + 1, len(compos)): | |
| relation = compos[i].compo_relation(compos[j]) | |
| if relation == -1: | |
| compos[j].contain.append(i) | |
| if relation == 1: | |
| compos[i].contain.append(j) | |
| def compos_update(compos, org_shape): | |
| for i, compo in enumerate(compos): | |
| # start from 1, id 0 is background | |
| compo.compo_update(i + 1, org_shape) | |
| class Component: | |
| def __init__(self, region, image_shape): | |
| self.id = None | |
| self.region = region | |
| self.boundary = self.compo_get_boundary() | |
| self.bbox = self.compo_get_bbox() | |
| self.bbox_area = self.bbox.box_area | |
| self.region_area = len(region) | |
| self.width = len(self.boundary[0]) | |
| self.height = len(self.boundary[2]) | |
| self.image_shape = image_shape | |
| self.area = self.width * self.height | |
| self.category = 'Compo' | |
| self.contain = [] | |
| self.rect_ = None | |
| self.line_ = None | |
| self.redundant = False | |
| def compo_update(self, id, org_shape): | |
| self.id = id | |
| self.image_shape = org_shape | |
| self.width = self.bbox.width | |
| self.height = self.bbox.height | |
| self.bbox_area = self.bbox.box_area | |
| self.area = self.width * self.height | |
| def put_bbox(self): | |
| return self.bbox.put_bbox() | |
| def compo_update_bbox_area(self): | |
| self.bbox_area = self.bbox.bbox_cal_area() | |
| def compo_get_boundary(self): | |
| ''' | |
| get the bounding boundary of an object(region) | |
| boundary: [top, bottom, left, right] | |
| -> up, bottom: (column_index, min/max row border) | |
| -> left, right: (row_index, min/max column border) detect range of each row | |
| ''' | |
| border_up, border_bottom, border_left, border_right = {}, {}, {}, {} | |
| for point in self.region: | |
| # point: (row_index, column_index) | |
| # up, bottom: (column_index, min/max row border) detect range of each column | |
| if point[1] not in border_up or border_up[point[1]] > point[0]: | |
| border_up[point[1]] = point[0] | |
| if point[1] not in border_bottom or border_bottom[point[1]] < point[0]: | |
| border_bottom[point[1]] = point[0] | |
| # left, right: (row_index, min/max column border) detect range of each row | |
| if point[0] not in border_left or border_left[point[0]] > point[1]: | |
| border_left[point[0]] = point[1] | |
| if point[0] not in border_right or border_right[point[0]] < point[1]: | |
| border_right[point[0]] = point[1] | |
| boundary = [border_up, border_bottom, border_left, border_right] | |
| # descending sort | |
| for i in range(len(boundary)): | |
| boundary[i] = [[k, boundary[i][k]] for k in boundary[i].keys()] | |
| boundary[i] = sorted(boundary[i], key=lambda x: x[0]) | |
| return boundary | |
| def compo_get_bbox(self): | |
| """ | |
| Get the top left and bottom right points of boundary | |
| :param boundaries: boundary: [top, bottom, left, right] | |
| -> up, bottom: (column_index, min/max row border) | |
| -> left, right: (row_index, min/max column border) detect range of each row | |
| :return: corners: [(top_left, bottom_right)] | |
| -> top_left: (column_min, row_min) | |
| -> bottom_right: (column_max, row_max) | |
| """ | |
| col_min, row_min = (int(min(self.boundary[0][0][0], self.boundary[1][-1][0])), int(min(self.boundary[2][0][0], self.boundary[3][-1][0]))) | |
| col_max, row_max = (int(max(self.boundary[0][0][0], self.boundary[1][-1][0])), int(max(self.boundary[2][0][0], self.boundary[3][-1][0]))) | |
| bbox = Bbox(col_min, row_min, col_max, row_max) | |
| return bbox | |
| def compo_is_rectangle(self, min_rec_evenness, max_dent_ratio, test=False): | |
| ''' | |
| detect if an object is rectangle by evenness and dent of each border | |
| ''' | |
| dent_direction = [1, -1, 1, -1] # direction for convex | |
| flat = 0 | |
| parameter = 0 | |
| for n, border in enumerate(self.boundary): | |
| parameter += len(border) | |
| # dent detection | |
| pit = 0 # length of pit | |
| depth = 0 # the degree of surface changing | |
| if n <= 1: | |
| adj_side = max(len(self.boundary[2]), len(self.boundary[3])) # get maximum length of adjacent side | |
| else: | |
| adj_side = max(len(self.boundary[0]), len(self.boundary[1])) | |
| # -> up, bottom: (column_index, min/max row border) | |
| # -> left, right: (row_index, min/max column border) detect range of each row | |
| abnm = 0 | |
| for i in range(int(3 + len(border) * 0.02), len(border) - 1): | |
| # calculate gradient | |
| difference = border[i][1] - border[i + 1][1] | |
| # the degree of surface changing | |
| depth += difference | |
| # ignore noise at the start of each direction | |
| if i / len(border) < 0.08 and (dent_direction[n] * difference) / adj_side > 0.5: | |
| depth = 0 # reset | |
| # print(border[i][1], i / len(border), depth, (dent_direction[n] * difference) / adj_side) | |
| # if the change of the surface is too large, count it as part of abnormal change | |
| if abs(depth) / adj_side > 0.3: | |
| abnm += 1 # count the size of the abnm | |
| # if the abnm is too big, the shape should not be a rectangle | |
| if abnm / len(border) > 0.1: | |
| if test: | |
| print('abnms', abnm, abnm / len(border)) | |
| draw.draw_boundary([self], self.image_shape, show=True) | |
| self.rect_ = False | |
| return False | |
| continue | |
| else: | |
| # reset the abnm if the depth back to normal | |
| abnm = 0 | |
| # if sunken and the surface changing is large, then counted as pit | |
| if dent_direction[n] * depth < 0 and abs(depth) / adj_side > 0.15: | |
| pit += 1 | |
| continue | |
| # if the surface is not changing to a pit and the gradient is zero, then count it as flat | |
| if abs(depth) < 1 + adj_side * 0.015: | |
| flat += 1 | |
| if test: | |
| print(depth, adj_side, flat) | |
| # if the pit is too big, the shape should not be a rectangle | |
| if pit / len(border) > max_dent_ratio: | |
| if test: | |
| print('pit', pit, pit / len(border)) | |
| draw.draw_boundary([self], self.image_shape, show=True) | |
| self.rect_ = False | |
| return False | |
| if test: | |
| print(flat / parameter, '\n') | |
| draw.draw_boundary([self], self.image_shape, show=True) | |
| # ignore text and irregular shape | |
| if self.height / self.image_shape[0] > 0.3: | |
| min_rec_evenness = 0.85 | |
| if (flat / parameter) < min_rec_evenness: | |
| self.rect_ = False | |
| return False | |
| self.rect_ = True | |
| return True | |
| def compo_is_line(self, min_line_thickness): | |
| """ | |
| Check this object is line by checking its boundary | |
| :param boundary: boundary: [border_top, border_bottom, border_left, border_right] | |
| -> top, bottom: list of (column_index, min/max row border) | |
| -> left, right: list of (row_index, min/max column border) detect range of each row | |
| :param min_line_thickness: | |
| :return: Boolean | |
| """ | |
| # horizontally | |
| slim = 0 | |
| for i in range(self.width): | |
| if abs(self.boundary[1][i][1] - self.boundary[0][i][1]) <= min_line_thickness: | |
| slim += 1 | |
| if slim / len(self.boundary[0]) > 0.93: | |
| self.line_ = True | |
| return True | |
| # vertically | |
| slim = 0 | |
| for i in range(self.height): | |
| if abs(self.boundary[2][i][1] - self.boundary[3][i][1]) <= min_line_thickness: | |
| slim += 1 | |
| if slim / len(self.boundary[2]) > 0.93: | |
| self.line_ = True | |
| return True | |
| self.line_ = False | |
| return False | |
| def compo_relation(self, compo_b, bias=(0, 0)): | |
| """ | |
| :return: -1 : a in b | |
| 0 : a, b are not intersected | |
| 1 : b in a | |
| 2 : a, b are identical or intersected | |
| """ | |
| return self.bbox.bbox_relation_nms(compo_b.bbox, bias) | |
| def compo_relative_position(self, col_min_base, row_min_base): | |
| ''' | |
| Convert to relative position based on base coordinator | |
| ''' | |
| self.bbox.bbox_cvt_relative_position(col_min_base, row_min_base) | |
| def compo_merge(self, compo_b): | |
| self.bbox = self.bbox.bbox_merge(compo_b.bbox) | |
| self.compo_update(self.id, self.image_shape) | |
| def compo_clipping(self, img, pad=0, show=False): | |
| (column_min, row_min, column_max, row_max) = self.put_bbox() | |
| column_min = max(column_min - pad, 0) | |
| column_max = min(column_max + pad, img.shape[1]) | |
| row_min = max(row_min - pad, 0) | |
| row_max = min(row_max + pad, img.shape[0]) | |
| clip = img[row_min:row_max, column_min:column_max] | |
| if show: | |
| cv2.imshow('clipping', clip) | |
| cv2.waitKey() | |
| return clip | |