ClassifyEdgeCorner.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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. """
  39. corner_regions = []
  40. num_corners = len(outer_box_corners)
  41. for i in range(num_corners):
  42. p_current = outer_box_corners[i]
  43. p_prev = outer_box_corners[(i - 1 + num_corners) % num_corners]
  44. p_next = outer_box_corners[(i + 1) % num_corners]
  45. vec1 = p_prev - p_current
  46. vec2 = p_next - p_current
  47. norm1 = np.linalg.norm(vec1)
  48. norm2 = np.linalg.norm(vec2)
  49. # 防止除以0的保护
  50. if norm1 == 0 or norm2 == 0:
  51. unit_vec1 = np.array([0, 0])
  52. unit_vec2 = np.array([0, 0])
  53. else:
  54. unit_vec1 = vec1 / norm1
  55. unit_vec2 = vec2 / norm2
  56. q0 = p_current
  57. q1 = p_current + corner_size_px * unit_vec1
  58. q2 = p_current + corner_size_px * unit_vec2
  59. q3 = q1 + corner_size_px * unit_vec2
  60. corner_poly = np.array([q0, q1, q3, q2], dtype=np.float32)
  61. corner_regions.append(corner_poly)
  62. return corner_regions
  63. def create_inner_face_polygon(self, rect_data, edge_size_px):
  64. """
  65. 根据外框旋转矩形和边宽,计算内缩后的“面”区域多边形。
  66. """
  67. (cx, cy), (w, h), angle = rect_data
  68. # 计算内框的宽和高 (两边各减去 edge_size)
  69. inner_w = w - 2 * edge_size_px
  70. inner_h = h - 2 * edge_size_px
  71. # 如果边设置得太宽,导致内框消失,则返回 None 或极小的框
  72. if inner_w <= 0 or inner_h <= 0:
  73. logger.warning("警告: EDGE_SIZE_MM 设置过大,超过了卡牌尺寸,面(Face)区域将不存在。")
  74. return None
  75. # 生成内框的旋转矩形
  76. inner_rect = ((cx, cy), (inner_w, inner_h), angle)
  77. # 获取内框的四个角点
  78. inner_box = cv2.boxPoints(inner_rect)
  79. inner_box = np.int32(inner_box) # 转换为整数点以便于 polygon test
  80. return inner_box
  81. def classify_defects_location(self, defect_data, outer_box_data):
  82. """
  83. 主函数:分类为 corner, face, edge
  84. """
  85. if not defect_data or 'defects' not in defect_data or not defect_data['defects']:
  86. # logger.warn("无缺陷数据")
  87. return defect_data
  88. # 1. 单位转换
  89. pixel_to_mm = self.PIXEL_RESOLUTION_UM / 1000.0
  90. corner_size_px = self.CORNER_SIZE_MM / pixel_to_mm
  91. edge_size_px = self.EDGE_SIZE_MM / pixel_to_mm
  92. logger.info(
  93. 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)")
  94. # 2. 获取外框几何信息
  95. try:
  96. # 获取旋转矩形完整数据 ((x,y), (w,h), angle)
  97. rect_data = self.get_rotated_rect_data(outer_box_data)
  98. # 获取外框的四个角点 (用于角区计算)
  99. outer_box_corners = cv2.boxPoints(rect_data)
  100. except ValueError as e:
  101. logger.error(f"外框数据错误: {e}")
  102. return None
  103. # 3. 定义区域
  104. # A. 定义四个角区
  105. corner_regions = self.create_corner_regions(outer_box_corners, corner_size_px)
  106. # B. 定义中间的面区 (内缩矩形)
  107. face_region = self.create_inner_face_polygon(rect_data, edge_size_px)
  108. # 4. 遍历并分类所有缺陷
  109. processed_count = 0
  110. for defect in defect_data['defects']:
  111. if 'min_rect' not in defect or not defect['min_rect']:
  112. continue
  113. # 获取缺陷中心点
  114. center_point = tuple(defect['min_rect'][0])
  115. # --- 判定逻辑优先级 ---
  116. # 优先级 1: 面 (Face)
  117. # 如果点在内缩矩形内部,且不是角,那就是面
  118. is_face = False
  119. if face_region is not None:
  120. if cv2.pointPolygonTest(face_region, center_point, False) >= 0:
  121. is_face = True
  122. if is_face:
  123. defect['defect_type'] = 'face'
  124. else:
  125. # 优先级 2: 角 (Corner)
  126. is_corner = False
  127. for region in corner_regions:
  128. # pointPolygonTest: >0 内部, =0 边界, <0 外部
  129. if cv2.pointPolygonTest(region, center_point, False) >= 0:
  130. is_corner = True
  131. break
  132. if is_corner:
  133. defect['defect_type'] = 'corner'
  134. else:
  135. # 优先级 3: 边 (Edge)
  136. # 既不在角区,也不在面区(内框)里,但还在处理范围内,那就是边
  137. # (注:这里默认缺陷是在外框内的,如果是外框外的噪声,通常也会被算作 Edge 或需要额外过滤)
  138. defect['defect_type'] = 'edge'
  139. processed_count += 1
  140. logger.info(f"分类完成: {processed_count} 个缺陷处理完毕。")
  141. return defect_data