ClassifyEdgeCorner.py 4.8 KB

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