ClassifyEdgeCorner.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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):
  9. self.PIXEL_RESOLUTION_UM = PIXEL_RESOLUTION_UM # 单位: μm/pixel
  10. # 角区的定义尺寸
  11. self.CORNER_SIZE_MM = PIXEL_CENTER_UM
  12. def get_outer_box_corners(self, outer_box_data):
  13. """从外框数据中计算并返回精确的四个角点坐标。"""
  14. if not outer_box_data or 'shapes' not in outer_box_data or not outer_box_data['shapes']:
  15. raise ValueError("无效的外框数据格式。")
  16. points = outer_box_data['shapes'][0]['points']
  17. contour = np.array(points, dtype=np.int32)
  18. # 计算最小面积旋转矩形
  19. rotated_rect = cv2.minAreaRect(contour)
  20. # 获取矩形的4个角点
  21. # boxPoints返回的角点顺序是可预测的,通常是顺时针或逆时针
  22. box_corners = cv2.boxPoints(rotated_rect)
  23. return box_corners
  24. def create_corner_regions(self, outer_box_corners, corner_size_px):
  25. """
  26. 根据外框的角点和指定的尺寸(像素),创建四个角区的多边形。
  27. Args:
  28. outer_box_corners (np.array): 外框的4个角点坐标。
  29. corner_size_px (float): 角区尺寸(像素单位)。
  30. Returns:
  31. list: 包含四个角区(每个角区都是一个4x2的np.array)的列表。
  32. """
  33. corner_regions = []
  34. num_corners = len(outer_box_corners)
  35. for i in range(num_corners):
  36. # 获取当前角点 p_i 和它的两个相邻角点 p_{i-1} 和 p_{i+1}
  37. p_current = outer_box_corners[i]
  38. p_prev = outer_box_corners[(i - 1 + num_corners) % num_corners]
  39. p_next = outer_box_corners[(i + 1) % num_corners]
  40. # 计算从当前角点指向相邻角点的向量
  41. vec1 = p_prev - p_current
  42. vec2 = p_next - p_current
  43. # 将向量归一化为单位向量
  44. unit_vec1 = vec1 / np.linalg.norm(vec1)
  45. unit_vec2 = vec2 / np.linalg.norm(vec2)
  46. # 计算角区正方形的四个顶点
  47. # q0: 当前角点
  48. # q1: 沿一个边移动指定距离
  49. # q2: 沿另一个边移动指定距离
  50. # q3: 两个向量相加得到的内部点
  51. q0 = p_current
  52. q1 = p_current + corner_size_px * unit_vec1
  53. q2 = p_current + corner_size_px * unit_vec2
  54. q3 = q1 + corner_size_px * unit_vec2
  55. # 将角区作为一个多边形(轮廓)添加到列表中
  56. corner_poly = np.array([q0, q1, q3, q2], dtype=np.float32)
  57. corner_regions.append(corner_poly)
  58. return corner_regions
  59. def classify_defects_location(self, defect_data, outer_box_data):
  60. """
  61. 主函数,对缺陷数据进行分类并添加 "defect_type" 标签。
  62. """
  63. if not defect_data or 'defects' not in defect_data:
  64. logger.warn("警告: 缺陷数据为空或格式不正确,跳过处理。")
  65. return defect_data
  66. if not defect_data['defects']:
  67. logger.warn("没有缺陷数据")
  68. return defect_data
  69. # 1. 单位转换
  70. pixel_to_mm = self.PIXEL_RESOLUTION_UM / 1000.0
  71. corner_size_px = self.CORNER_SIZE_MM / pixel_to_mm
  72. logger.info(f"3mm角区尺寸已转换为 {corner_size_px:.2f} 像素。")
  73. # 2. 获取外框几何信息
  74. try:
  75. outer_box_corners = self.get_outer_box_corners(outer_box_data)
  76. except ValueError as e:
  77. logger.info(f"错误: {e}")
  78. return None
  79. # 3. 定义四个角区
  80. corner_regions = self.create_corner_regions(outer_box_corners, corner_size_px)
  81. logger.info(f"已成功定义 {len(corner_regions)} 个角区。")
  82. # 4. 遍历并分类所有缺陷
  83. processed_count = 0
  84. for defect in defect_data['defects']:
  85. # 使用JSON中预先计算好的min_rect中心点,更高效
  86. if 'min_rect' not in defect or not defect['min_rect']:
  87. logger.warn(f"警告: 缺陷 '{defect.get('label')}' 缺少 'min_rect' 信息,跳过。")
  88. continue
  89. center_point = tuple(defect['min_rect'][0])
  90. is_in_corner = False
  91. # 5. 判断缺陷中心点是否在任何一个角区内
  92. for region in corner_regions:
  93. # pointPolygonTest 返回值: >0 (内部), 0 (边上), <0 (外部)
  94. if cv2.pointPolygonTest(region, center_point, False) >= 0:
  95. is_in_corner = True
  96. break
  97. # 6. 添加新标签
  98. if is_in_corner:
  99. defect['defect_type'] = 'corner'
  100. else:
  101. defect['defect_type'] = 'edge'
  102. processed_count += 1
  103. logger.info(f"处理完成!共为 {processed_count} 个缺陷添加了 'defect_type' 标签。")
  104. return defect_data