ClassifyEdgeCorner.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import json
  2. import cv2
  3. import numpy as np
  4. import math
  5. import logging
  6. logger = logging.getLogger('ClassifyEdgeCorner')
  7. class ClassifyEdgeCorner:
  8. def __init__(self, PIXEL_RESOLUTION_UM: float, PIXEL_CENTER_UM: float, EDGE_SIZE_MM: float):
  9. """
  10. Args:
  11. PIXEL_RESOLUTION_UM: 像素分辨率 (um/pixel)
  12. PIXEL_CENTER_UM: 角区定义的尺寸 (mm) - 对应原本的 CORNER_SIZE_MM
  13. EDGE_SIZE_MM: 边区向内缩进的尺寸 (mm)
  14. """
  15. self.PIXEL_RESOLUTION_UM = PIXEL_RESOLUTION_UM
  16. self.CORNER_SIZE_MM = PIXEL_CENTER_UM
  17. self.EDGE_SIZE_MM = EDGE_SIZE_MM
  18. def get_rotated_rect_data(self, outer_box_data):
  19. """
  20. 从外框数据中提取最小外接矩形的原始数据 ((x,y), (w,h), angle)。
  21. """
  22. if not outer_box_data or 'shapes' not in outer_box_data or not outer_box_data['shapes']:
  23. raise ValueError("无效的外框数据格式。")
  24. get_point = outer_box_data['shapes'][0].get('points', None)
  25. if get_point is None:
  26. logger.info("外框计算非第一次, 取 rect_box")
  27. get_point = outer_box_data['shapes'][0].get('rect_box', None)
  28. if get_point is None:
  29. raise ValueError("外框数据缺失: rect_box")
  30. points = get_point
  31. contour = np.array(points, dtype=np.int32)
  32. # 返回 ((cx, cy), (w, h), angle)
  33. return cv2.minAreaRect(contour)
  34. def create_corner_regions(self, outer_box_corners, corner_size_px):
  35. """
  36. 根据外框的角点和指定的尺寸(像素),创建四个角区的多边形。
  37. """
  38. corner_regions = []
  39. num_corners = len(outer_box_corners)
  40. for i in range(num_corners):
  41. p_current = outer_box_corners[i]
  42. p_prev = outer_box_corners[(i - 1 + num_corners) % num_corners]
  43. p_next = outer_box_corners[(i + 1) % num_corners]
  44. vec1 = p_prev - p_current
  45. vec2 = p_next - p_current
  46. norm1 = np.linalg.norm(vec1)
  47. norm2 = np.linalg.norm(vec2)
  48. if norm1 == 0 or norm2 == 0:
  49. unit_vec1 = np.array([0, 0])
  50. unit_vec2 = np.array([0, 0])
  51. else:
  52. unit_vec1 = vec1 / norm1
  53. unit_vec2 = vec2 / norm2
  54. q0 = p_current
  55. q1 = p_current + corner_size_px * unit_vec1
  56. q2 = p_current + corner_size_px * unit_vec2
  57. q3 = q1 + corner_size_px * unit_vec2
  58. # 确保生成的点是 float32,用于相交计算
  59. corner_poly = np.array([q0, q1, q3, q2], dtype=np.float32)
  60. corner_regions.append(corner_poly)
  61. return corner_regions
  62. def create_inner_face_polygon(self, rect_data, edge_size_px):
  63. """
  64. 根据外框旋转矩形和边宽,计算内缩后的“面”区域多边形。
  65. """
  66. (cx, cy), (w, h), angle = rect_data
  67. # 计算内框的宽和高 (两边各减去 edge_size)
  68. inner_w = w - 2 * edge_size_px
  69. inner_h = h - 2 * edge_size_px
  70. if inner_w <= 0 or inner_h <= 0:
  71. logger.warning("警告: EDGE_SIZE_MM 设置过大,超过了卡牌尺寸,面(Face)区域将不存在。")
  72. return None
  73. # 生成内框的旋转矩形
  74. inner_rect = ((cx, cy), (inner_w, inner_h), angle)
  75. # 获取内框的四个角点
  76. inner_box = cv2.boxPoints(inner_rect)
  77. # 修改:为了支持 intersectConvexConvex,这里转换为 float32 而不是 int32
  78. inner_box = np.array(inner_box, dtype=np.float32)
  79. return inner_box
  80. def check_intersection(self, poly1, poly2):
  81. """
  82. 检测两个凸多边形是否相交(重合面积 > 0)
  83. poly1, poly2: np.float32 类型的多边形点集
  84. """
  85. if poly1 is None or poly2 is None:
  86. return False
  87. # intersectConvexConvex 返回 (intersection_area, intersection_polygon)
  88. # 我们只需要面积即可。如果面积 > 0 (考虑到浮点误差,可以用 > 0.01) 即视为相交
  89. try:
  90. intersection_area, _ = cv2.intersectConvexConvex(poly1, poly2)
  91. return intersection_area > 0.1 # 设置一个微小的阈值防止浮点噪声
  92. except Exception as e:
  93. # 极少数情况下如果多边形退化可能报错,视为不相交
  94. logger.debug(f"Intersection check failed: {e}")
  95. return False
  96. def classify_defects_location(self, defect_data, outer_box_data):
  97. """
  98. 主函数:分类为 corner, face, edge (基于重合逻辑)
  99. 优先级:Face (只要碰到面就算面) > Corner (没碰到面但碰到角) > Edge (其余)
  100. """
  101. if not defect_data or 'defects' not in defect_data or not defect_data['defects']:
  102. return defect_data
  103. # 1. 单位转换
  104. pixel_to_mm = self.PIXEL_RESOLUTION_UM / 1000.0
  105. corner_size_px = self.CORNER_SIZE_MM / pixel_to_mm
  106. edge_size_px = self.EDGE_SIZE_MM / pixel_to_mm
  107. logger.info(
  108. f"Param Info: Corner={self.CORNER_SIZE_MM}mm ({corner_size_px:.1f}px), Edge={self.EDGE_SIZE_MM}mm ({edge_size_px:.1f}px)")
  109. # 2. 获取外框几何信息
  110. try:
  111. rect_data = self.get_rotated_rect_data(outer_box_data)
  112. outer_box_corners = cv2.boxPoints(rect_data)
  113. except ValueError as e:
  114. logger.error(f"外框数据错误: {e}")
  115. return None
  116. # 3. 定义区域 (多边形)
  117. # A. 定义四个角区
  118. corner_regions = self.create_corner_regions(outer_box_corners, corner_size_px)
  119. # B. 定义中间的面区 (内缩矩形)
  120. face_region = self.create_inner_face_polygon(rect_data, edge_size_px)
  121. # 4. 遍历并分类所有缺陷
  122. processed_count = 0
  123. for defect in defect_data['defects']:
  124. if 'min_rect' not in defect or not defect['min_rect']:
  125. continue
  126. # --- 步骤变更:获取缺陷的多边形轮廓 ---
  127. # defect['min_rect'] 结构通常为 ((x,y), (w,h), angle)
  128. defect_rect = defect['min_rect']
  129. defect_box = cv2.boxPoints(defect_rect)
  130. defect_poly = np.array(defect_box, dtype=np.float32)
  131. # --- 判定逻辑优先级 ---
  132. # 优先级 1: 面 (Face) - 只要碰到 Face 区域就算 Face
  133. is_face = False
  134. if face_region is not None:
  135. if self.check_intersection(face_region, defect_poly):
  136. is_face = True
  137. if is_face:
  138. defect['defect_type'] = 'face'
  139. else:
  140. # 优先级 2: 角 (Corner) - 没碰到 Face,但碰到了 Corner 区域
  141. is_corner = False
  142. for region in corner_regions:
  143. if self.check_intersection(region, defect_poly):
  144. is_corner = True
  145. break
  146. if is_corner:
  147. defect['defect_type'] = 'corner'
  148. else:
  149. # 优先级 3: 边 (Edge)
  150. # 既没碰到面,也没碰到角,说明完全处于边区域内(或者在外框外)
  151. defect['defect_type'] = 'edge'
  152. processed_count += 1
  153. logger.info(f"分类完成: {processed_count} 个缺陷处理完毕。")
  154. return defect_data