# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from enum import Enum import cv2 import numpy as np import math from .regularization import boundary_regularization class Instructions(Enum): No_Instruction = 0 Polygon_Instruction = 1 def get_polygon(label, sample="Dynamic", img_size=None, building=False): results = cv2.findContours( image=label, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_TC89_KCOS) # 获取内外边界,用RETR_TREE更好表示 cv2_v = cv2.__version__.split(".")[0] contours = results[1] if cv2_v == "3" else results[0] # 边界 hierarchys = results[2] if cv2_v == "3" else results[1] # 隶属信息 if len(contours) != 0: # 可能出现没有边界的情况 polygons = [] relas = [] img_shape = label.shape for idx, (contour, hierarchy) in enumerate(zip(contours, hierarchys[0])): # print(hierarchy) # opencv实现边界简化 epsilon = (0.005 * cv2.arcLength(contour, True) if sample == "Dynamic" else sample) if not isinstance(epsilon, float) and not isinstance(epsilon, int): epsilon = 0 # print("epsilon:", epsilon) if building is False: # -- Douglas-Peucker算法边界简化 contour = cv2.approxPolyDP(contour, epsilon / 10, True) else: # -- 建筑边界简化(https://github.com/niecongchong/RS-building-regularization) if contour.shape[0] >= 2: contour = boundary_regularization(contour, img_shape, epsilon) # -- 自定义(角度和距离)边界简化 out = approx_poly_DIY(contour) # 给出关系 rela = ( idx, # own hierarchy[-1] if hierarchy[-1] != -1 else None, ) # parent polygon = [] for p in out: polygon.append(p[0]) polygons.append(polygon) # 边界 relas.append(rela) # 关系 for i in range(len(relas)): if relas[i][1] != None: # 有父圈 for j in range(len(relas)): if relas[j][0] == relas[i][1]: # i的父圈就是j(i是j的子圈) if polygons[i] is not None and polygons[j] is not None: min_i, min_o = __find_min_point(polygons[i], polygons[j]) # 改变顺序 polygons[i] = __change_list(polygons[i], min_i) polygons[j] = __change_list(polygons[j], min_o) # 连接 if min_i != -1 and len(polygons[i]) > 0: polygons[j].extend(polygons[i]) # 连接内圈 polygons[i] = None polygons = list(filter(None, polygons)) # 清除加到外圈的内圈多边形 if img_size is not None: polygons = check_size_minmax(polygons, img_size) return polygons else: print("没有标签范围,无法生成边界") return None def __change_list(polygons, idx): if idx == -1: return polygons s_p = polygons[:idx] polygons = polygons[idx:] polygons.extend(s_p) polygons.append(polygons[0]) # 闭合圈 return polygons def __find_min_point(i_list, o_list): min_dis = 1e7 idx_i = -1 idx_o = -1 for i in range(len(i_list)): for o in range(len(o_list)): dis = math.sqrt((i_list[i][0] - o_list[o][0])**2 + (i_list[i][ 1] - o_list[o][1])**2) if dis <= min_dis: min_dis = dis idx_i = i idx_o = o return idx_i, idx_o # 根据三点坐标计算夹角 def __cal_ang(p1, p2, p3): eps = 1e-12 a = math.sqrt((p2[0] - p3[0]) * (p2[0] - p3[0]) + (p2[1] - p3[1]) * (p2[1] - p3[1])) b = math.sqrt((p1[0] - p3[0]) * (p1[0] - p3[0]) + (p1[1] - p3[1]) * (p1[1] - p3[1])) c = math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])) ang = math.degrees(math.acos( (b**2 - a**2 - c**2) / (-2 * a * c + eps))) # p2对应 return ang # 计算两点距离 def __cal_dist(p1, p2): return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) # 边界点简化 def approx_poly_DIY(contour, min_dist=10, ang_err=5): # print(contour.shape) # N, 1, 2 cs = [contour[i][0] for i in range(contour.shape[0])] ## 1. 先删除两个相近点与前后两个点角度接近的点 i = 0 while i < len(cs): try: j = (i + 1) if (i != len(cs) - 1) else 0 if __cal_dist(cs[i], cs[j]) < min_dist: last = (i - 1) if (i != 0) else (len(cs) - 1) next = (j + 1) if (j != len(cs) - 1) else 0 ang_i = __cal_ang(cs[last], cs[i], cs[next]) ang_j = __cal_ang(cs[last], cs[j], cs[next]) # print(ang_i, ang_j) # 角度值为-180到+180 if abs(ang_i - ang_j) < ang_err: # 删除距离两点小的 dist_i = __cal_dist(cs[last], cs[i]) + __cal_dist(cs[i], cs[next]) dist_j = __cal_dist(cs[last], cs[j]) + __cal_dist(cs[j], cs[next]) if dist_j < dist_i: del cs[j] else: del cs[i] else: i += 1 else: i += 1 except: i += 1 ## 2. 再删除夹角接近180度的点 i = 0 while i < len(cs): try: last = (i - 1) if (i != 0) else (len(cs) - 1) next = (i + 1) if (i != len(cs) - 1) else 0 ang_i = __cal_ang(cs[last], cs[i], cs[next]) if abs(ang_i) > (180 - ang_err): del cs[i] else: i += 1 except: # i += 1 del cs[i] res = np.array(cs).reshape([-1, 1, 2]) return res def check_size_minmax(polygons, img_size): h_max, w_max = img_size for ps in polygons: for j in range(len(ps)): x, y = ps[j] if x < 0: x = 0 elif x > w_max: x = w_max if y < 0: y = 0 elif y > h_max: y = h_max ps[j] = np.array([x, y]) return polygons