ClassifyEdgeCorner.py 5.4 KB

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