AnalyzeCenter.py 9.8 KB

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