AnalyzeCenter.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import cv2
  2. import numpy as np
  3. import json
  4. import matplotlib.pyplot as plt
  5. import math
  6. from typing import Union, List
  7. def get_points_from_file(json_file):
  8. """辅助函数,从JSON文件加载点"""
  9. with open(json_file, 'r') as f:
  10. data = json.load(f)
  11. return data['shapes'][0]['points']
  12. def draw_rotated_bounding_boxes(image_path, inner_box_file, outer_box_file, output_path=None):
  13. """
  14. 在图片上绘制内框和外框的旋转矩形。
  15. Args:
  16. image_path (str): 原始图片的文件路径。
  17. inner_box_file (str): 包含内框坐标的JSON文件路径。
  18. outer_box_file (str): 包含外框坐标的JSON文件路径。
  19. output_path (str, optional): 如果提供,结果图片将被保存到此路径。
  20. 默认为None,不保存。
  21. """
  22. # 1. 读取图片
  23. image = cv2.imread(image_path)
  24. if image is None:
  25. print(f"错误: 无法读取图片 -> {image_path}")
  26. return
  27. print(f"成功加载图片: {image_path}")
  28. # 2. 读取坐标点
  29. inner_points = get_points_from_file(inner_box_file)
  30. outer_points = get_points_from_file(outer_box_file)
  31. if inner_points is None or outer_points is None:
  32. print("因无法加载坐标点,程序终止。")
  33. return
  34. # 定义颜色 (BGR格式)
  35. OUTER_BOX_COLOR = (0, 255, 0) # 绿色
  36. INNER_BOX_COLOR = (0, 0, 255) # 红色
  37. THICKNESS = 10 # 可以根据你的图片分辨率调整线宽
  38. # 3. 处理并绘制外框
  39. # 将点列表转换为OpenCV需要的NumPy数组
  40. outer_contour = np.array(outer_points, dtype=np.int32)
  41. # 计算最小面积旋转矩形
  42. outer_rect = cv2.minAreaRect(outer_contour)
  43. # 获取矩形的4个角点
  44. outer_box_corners = cv2.boxPoints(outer_rect)
  45. # 转换为整数,以便绘制
  46. outer_box_corners_int = np.intp(outer_box_corners)
  47. # 在图片上绘制轮廓
  48. cv2.drawContours(image, [outer_box_corners_int], 0, OUTER_BOX_COLOR, THICKNESS)
  49. print("已绘制外框 (绿色)。")
  50. # 4. 处理并绘制内框
  51. inner_contour = np.array(inner_points, dtype=np.int32)
  52. inner_rect = cv2.minAreaRect(inner_contour)
  53. inner_box_corners = cv2.boxPoints(inner_rect)
  54. inner_box_corners_int = np.intp(inner_box_corners)
  55. cv2.drawContours(image, [inner_box_corners_int], 0, INNER_BOX_COLOR, THICKNESS)
  56. print("已绘制内框 (红色)。")
  57. # 5. 保存结果 (如果指定了路径)
  58. if output_path:
  59. cv2.imwrite(output_path, image)
  60. print(f"结果已保存到: {output_path}")
  61. # 6. 显示结果
  62. # OpenCV使用BGR,Matplotlib使用RGB,需要转换颜色通道
  63. image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
  64. # 使用Matplotlib显示,因为它在各种环境中(如Jupyter)表现更好
  65. plt.figure(figsize=(10, 15)) # 调整显示窗口大小
  66. plt.imshow(image_rgb)
  67. plt.title('旋转框可视化 (外框: 绿色, 内框: 红色)')
  68. plt.axis('off') # 不显示坐标轴
  69. plt.show()
  70. def analyze_centering_rotated(inner_points: Union[str, List], outer_points: Union[str, List], ):
  71. """
  72. 使用旋转矩形进行最精确的分析, 包括定向边距和精确的浮点数比值。
  73. """
  74. if isinstance(inner_points, str):
  75. inner_points = get_points_from_file(inner_points)
  76. if isinstance(outer_points, str):
  77. outer_points = get_points_from_file(outer_points)
  78. # 将点列表转换为OpenCV需要的NumPy数组格式
  79. inner_contour = np.array(inner_points, dtype=np.int32)
  80. outer_contour = np.array(outer_points, dtype=np.int32)
  81. # 计算最小面积的旋转矩形
  82. # 结果格式: ((中心点x, 中心点y), (宽度, 高度), 旋转角度)
  83. inner_rect = cv2.minAreaRect(inner_contour)
  84. outer_rect = cv2.minAreaRect(outer_contour)
  85. # --- 1. 标准化矩形尺寸,确保宽度总是长边 ---
  86. def standardize_rect(rect):
  87. (cx, cy), (w, h), angle = rect
  88. if w < h:
  89. # 如果宽度小于高度,交换它们,并调整角度
  90. w, h = h, w
  91. angle += 90
  92. return (cx, cy), (w, h), angle
  93. inner_center, inner_dims, inner_angle = standardize_rect(inner_rect)
  94. outer_center, outer_dims, outer_angle = standardize_rect(outer_rect)
  95. print("\n--- 基于旋转矩形的精确分析 ---")
  96. print(
  97. f"内框 (标准化后): 中心=({inner_center[0]:.1f}, {inner_center[1]:.1f}), 尺寸=({inner_dims[0]:.1f}, {inner_dims[1]:.1f}), 角度={inner_angle:.1f}°")
  98. print(
  99. f"外框 (标准化后): 中心=({outer_center[0]:.1f}, {outer_center[1]:.1f}), 尺寸=({outer_dims[0]:.1f}, {outer_dims[1]:.1f}), 角度={outer_angle:.1f}°")
  100. # --- 2. 评估居中度 (基于中心点) ---
  101. offset_x_abs = abs(inner_center[0] - outer_center[0])
  102. offset_y_abs = abs(inner_center[1] - outer_center[1])
  103. print(f"\n[居中度] 图像坐标系绝对中心偏移: 水平={offset_x_abs:.2f} px | 垂直={offset_y_abs:.2f} px")
  104. # --- 3. 评估平行度 ---
  105. angle_diff = abs(inner_angle - outer_angle)
  106. # 处理角度环绕问题 (例如 -89° 和 89° 其实只差 2°)
  107. angle_diff = min(angle_diff, 180 - angle_diff)
  108. print(f"[平行度] 角度差异: {angle_diff:.2f}° (值越小,内外框越平行)")
  109. # --- 4. 计算定向边距和比值 (核心部分) ---
  110. # 计算从外框中心指向内框中心的向量
  111. center_delta_vec = (inner_center[0] - outer_center[0], inner_center[1] - outer_center[1])
  112. # 将外框的旋转角度转换为弧度,用于三角函数计算
  113. angle_rad = math.radians(outer_angle)
  114. # 计算外框自身的坐标轴单位向量(相对于图像坐标系)
  115. # local_x_axis: 沿着外框宽度的方向向量
  116. # local_y_axis: 沿着外框高度的方向向量
  117. local_x_axis = (math.cos(angle_rad), math.sin(angle_rad))
  118. local_y_axis = (-math.sin(angle_rad), math.cos(angle_rad))
  119. # 使用点积 (dot product) 将中心偏移向量投影到外框的局部坐标轴上
  120. # 这能告诉我们,内框中心相对于外框中心,在“卡片自己的水平和垂直方向上”移动了多少
  121. offset_along_width = center_delta_vec[0] * local_x_axis[0] + center_delta_vec[1] * local_x_axis[1]
  122. offset_along_height = center_delta_vec[0] * local_y_axis[0] + center_delta_vec[1] * local_y_axis[1]
  123. # 计算水平和垂直方向上的总边距
  124. total_horizontal_margin = outer_dims[0] - inner_dims[0]
  125. total_vertical_margin = outer_dims[1] - inner_dims[1]
  126. # 根据投影的偏移量来分配总边距
  127. # 理想情况下,偏移为0,左右/上下边距各分一半
  128. # 如果有偏移,一侧增加,另一侧减少
  129. margin_left = (total_horizontal_margin / 2) - offset_along_width
  130. margin_right = (total_horizontal_margin / 2) + offset_along_width
  131. margin_top = (total_vertical_margin / 2) - offset_along_height
  132. margin_bottom = (total_vertical_margin / 2) + offset_along_height
  133. # print("\n[定向边距分析 (沿卡片方向)]")
  134. # print(f"水平边距 (像素): 左={margin_left:.1f} | 右={margin_right:.1f}")
  135. # print(f"垂直边距 (像素): 上={margin_top:.1f} | 下={margin_bottom:.1f}")
  136. #
  137. # print("\n[精确边距比值分析]")
  138. # # 直接展示浮点数比值
  139. # print(f"水平比值 (左:右) = {margin_left:.2f} : {margin_right:.2f}")
  140. # print(f"垂直比值 (上:下) = {margin_top:.2f} : {margin_bottom:.2f}")
  141. # 为了更直观,也可以计算一个百分比
  142. if total_horizontal_margin > 0:
  143. left_percent = (margin_left / total_horizontal_margin) * 100
  144. right_percent = (margin_right / total_horizontal_margin) * 100
  145. print(f"水平边距分布: 左 {left_percent:.1f}% | 右 {right_percent:.1f}%")
  146. if total_vertical_margin > 0:
  147. top_percent = (margin_top / total_vertical_margin) * 100
  148. bottom_percent = (margin_bottom / total_vertical_margin) * 100
  149. print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%")
  150. return ((left_percent, right_percent),
  151. (top_percent, bottom_percent),
  152. angle_diff)
  153. # if __name__ == "__main__":
  154. # img_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001.jpg"
  155. # inner_file_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\inner\250805_pokemon_0001.json"
  156. # outer_file_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\outer\250805_pokemon_0001.json"
  157. #
  158. # inner_pts = get_points_from_file(inner_file_path)
  159. # print(inner_pts)
  160. # print(type(inner_pts))
  161. # result = analyze_centering_rotated(inner_file_path, outer_file_path)
  162. # print(result)
  163. # draw_rotated_bounding_boxes(img_path, inner_file_path, outer_file_path)