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