AnalyzeCenter.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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 get_rect_from_file(json_file):
  15. """辅助函数,从JSON文件加载点"""
  16. with open(json_file, 'r') as f:
  17. data = json.load(f)
  18. return data['shapes'][0]['rect_box']
  19. def draw_rotated_bounding_boxes(image_path, inner_box_file, outer_box_file, output_path=None):
  20. """
  21. 在图片上绘制内框和外框的旋转矩形。
  22. Args:
  23. image_path (str): 原始图片的文件路径。
  24. inner_box_file (str): 包含内框坐标的JSON文件路径。
  25. outer_box_file (str): 包含外框坐标的JSON文件路径。
  26. output_path (str, optional): 如果提供,结果图片将被保存到此路径。
  27. 默认为None,不保存。
  28. """
  29. # 1. 读取图片
  30. image = cv2.imread(image_path)
  31. if image is None:
  32. print(f"错误: 无法读取图片 -> {image_path}")
  33. return
  34. print(f"成功加载图片: {image_path}")
  35. # 2. 读取坐标点
  36. inner_points = get_points_from_file(inner_box_file)
  37. outer_points = get_points_from_file(outer_box_file)
  38. if inner_points is None or outer_points is None:
  39. print("因无法加载坐标点,程序终止。")
  40. return
  41. # 定义颜色 (BGR格式)
  42. OUTER_BOX_COLOR = (0, 255, 0) # 绿色
  43. INNER_BOX_COLOR = (0, 0, 255) # 红色
  44. THICKNESS = 10 # 可以根据你的图片分辨率调整线宽
  45. # 3. 处理并绘制外框
  46. # 将点列表转换为OpenCV需要的NumPy数组
  47. outer_contour = np.array(outer_points, dtype=np.int32)
  48. # 计算最小面积旋转矩形
  49. outer_rect = cv2.minAreaRect(outer_contour)
  50. # 获取矩形的4个角点
  51. outer_box_corners = cv2.boxPoints(outer_rect)
  52. # 转换为整数,以便绘制
  53. outer_box_corners_int = np.intp(outer_box_corners)
  54. # 在图片上绘制轮廓
  55. cv2.drawContours(image, [outer_box_corners_int], 0, OUTER_BOX_COLOR, THICKNESS)
  56. print("已绘制外框 (绿色)。")
  57. # 4. 处理并绘制内框
  58. inner_contour = np.array(inner_points, dtype=np.int32)
  59. inner_rect = cv2.minAreaRect(inner_contour)
  60. inner_box_corners = cv2.boxPoints(inner_rect)
  61. inner_box_corners_int = np.intp(inner_box_corners)
  62. cv2.drawContours(image, [inner_box_corners_int], 0, INNER_BOX_COLOR, THICKNESS)
  63. print("已绘制内框 (红色)。")
  64. # 5. 保存结果 (如果指定了路径)
  65. if output_path:
  66. cv2.imwrite(output_path, image)
  67. print(f"结果已保存到: {output_path}")
  68. # 6. 显示结果
  69. # OpenCV使用BGR,Matplotlib使用RGB,需要转换颜色通道
  70. image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
  71. # 使用Matplotlib显示,因为它在各种环境中(如Jupyter)表现更好
  72. plt.figure(figsize=(10, 15)) # 调整显示窗口大小
  73. plt.imshow(image_rgb)
  74. plt.title('旋转框可视化 (外框: 绿色, 内框: 红色)')
  75. plt.axis('off') # 不显示坐标轴
  76. plt.show()
  77. def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[str, List]):
  78. """
  79. 使用横平竖直的矩形 (Axis-Aligned Bounding Box) 进行居中分析。
  80. 适用于前端已经修正过的矩形数据。
  81. """
  82. if isinstance(inner_points, str):
  83. inner_points = get_rect_from_file(inner_points)
  84. if isinstance(outer_points, str):
  85. outer_points = get_rect_from_file(outer_points)
  86. # 1. 转换为 float32 数组,保留计算精度
  87. inner_contour = np.array(inner_points, dtype=np.float32)
  88. outer_contour = np.array(outer_points, dtype=np.float32)
  89. # --- 修改点 1: 在计算 min/max 后立即转为 Python float ---
  90. def get_float_bounding_rect(points):
  91. # np.min 返回的是 numpy.float32,必须转为 python float
  92. x_min = float(np.min(points[:, 0]))
  93. y_min = float(np.min(points[:, 1]))
  94. x_max = float(np.max(points[:, 0]))
  95. y_max = float(np.max(points[:, 1]))
  96. w = x_max - x_min
  97. h = y_max - y_min
  98. return x_min, y_min, w, h
  99. ix, iy, iw, ih = get_float_bounding_rect(inner_contour)
  100. ox, oy, ow, oh = get_float_bounding_rect(outer_contour)
  101. # 构造标准的4点格式返回
  102. # 因为 ix, iy 等已经是 python float 了,所以列表里装的也是 safe 的 float
  103. def get_box_corners(x, y, w, h):
  104. return [
  105. [x, y],
  106. [x + w, y],
  107. [x + w, y + h],
  108. [x, y + h]
  109. ]
  110. inner_box_corners_float = get_box_corners(ix, iy, iw, ih)
  111. outer_box_corners_float = get_box_corners(ox, oy, ow, oh)
  112. print("\n--- 基于修正矩形(横平竖直, Float精度)的分析 ---")
  113. print(f"内框: x={ix:.2f}, y={iy:.2f}, w={iw:.2f}, h={ih:.2f}")
  114. # --- 3. 计算边距 (Margins) ---
  115. margin_left = ix - ox
  116. margin_right = (ox + ow) - (ix + iw)
  117. margin_top = iy - oy
  118. margin_bottom = (oy + oh) - (iy + ih)
  119. # --- 4. 计算百分比 ---
  120. total_horizontal_margin = margin_left + margin_right
  121. total_vertical_margin = margin_top + margin_bottom
  122. left_percent = 50.0
  123. right_percent = 50.0
  124. top_percent = 50.0
  125. bottom_percent = 50.0
  126. # --- 修改点 2: 计算结果也要转为 float (虽然上面的 ix 是 float,但运算可能再次产生 numpy 类型) ---
  127. if total_horizontal_margin > 0.0001:
  128. left_percent = float((margin_left / total_horizontal_margin) * 100)
  129. right_percent = float((margin_right / total_horizontal_margin) * 100)
  130. print(f"水平边距分布: 左 {left_percent:.1f}% | 右 {right_percent:.1f}%")
  131. if total_vertical_margin > 0.0001:
  132. top_percent = float((margin_top / total_vertical_margin) * 100)
  133. bottom_percent = float((margin_bottom / total_vertical_margin) * 100)
  134. print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%")
  135. angle_diff = 0.0
  136. return ((left_percent, right_percent),
  137. (top_percent, bottom_percent),
  138. angle_diff), inner_box_corners_float, outer_box_corners_float
  139. def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[str, List]):
  140. """
  141. 使用横平竖直的矩形 (Axis-Aligned Bounding Box) 进行居中分析。
  142. 适用于前端已经修正过的矩形数据。
  143. """
  144. if isinstance(inner_points, str):
  145. inner_points = get_rect_from_file(inner_points)
  146. if isinstance(outer_points, str):
  147. outer_points = get_rect_from_file(outer_points)
  148. # --- 1. 转换为 float32 数组,保留精度 ---
  149. inner_contour = np.array(inner_points, dtype=np.float32)
  150. outer_contour = np.array(outer_points, dtype=np.float32)
  151. # --- 2. 手动计算 float 级别的 boundingRect ---
  152. def get_float_bounding_rect(points):
  153. # points shape: (N, 2)
  154. x_min = np.min(points[:, 0])
  155. y_min = np.min(points[:, 1])
  156. x_max = np.max(points[:, 0])
  157. y_max = np.max(points[:, 1])
  158. w = x_max - x_min
  159. h = y_max - y_min
  160. return x_min, y_min, w, h
  161. ix, iy, iw, ih = get_float_bounding_rect(inner_contour)
  162. ox, oy, ow, oh = get_float_bounding_rect(outer_contour)
  163. # 构造标准的4点格式返回 (保持和 analyze_centering_rotated 输出一致)
  164. def get_box_corners(x, y, w, h):
  165. return [[x, y], [x + w, y], [x + w, y + h], [x, y + h]]
  166. inner_box_corners_float = get_box_corners(ix, iy, iw, ih)
  167. outer_box_corners_float = get_box_corners(ox, oy, ow, oh)
  168. print("\n--- 基于修正矩形(横平竖直, Float精度)的分析 ---")
  169. print(f"内框: x={ix:.2f}, y={iy:.2f}, w={iw:.2f}, h={ih:.2f}")
  170. print(f"外框: x={ox:.2f}, y={oy:.2f}, w={ow:.2f}, h={oh:.2f}")
  171. # --- 3. 计算边距 (Margins) ---
  172. # 图像坐标系:x向右增加,y向下增加
  173. # 左边距:内框左边 - 外框左边
  174. margin_left = ix - ox
  175. # 右边距:外框右边(x+w) - 内框右边(x+w)
  176. margin_right = (ox + ow) - (ix + iw)
  177. # 上边距:内框上边 - 外框上边
  178. margin_top = iy - oy
  179. # 下边距:外框下边(y+h) - 内框下边(y+h)
  180. margin_bottom = (oy + oh) - (iy + ih)
  181. # --- 4. 计算百分比 ---
  182. total_horizontal_margin = margin_left + margin_right
  183. total_vertical_margin = margin_top + margin_bottom
  184. left_percent = 50.0
  185. right_percent = 50.0
  186. top_percent = 50.0
  187. bottom_percent = 50.0
  188. if total_horizontal_margin > 0.0001: # 避免除零
  189. left_percent = (margin_left / total_horizontal_margin) * 100
  190. right_percent = (margin_right / total_horizontal_margin) * 100
  191. print(f"水平边距分布: 左 {left_percent:.1f}% | 右 {right_percent:.1f}%")
  192. if total_vertical_margin > 0.0001:
  193. top_percent = (margin_top / total_vertical_margin) * 100
  194. bottom_percent = (margin_bottom / total_vertical_margin) * 100
  195. print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%")
  196. # --- 5. 角度差异 ---
  197. # 因为已经是横平竖直的矩形,角度差默认为 0
  198. angle_diff = 0.0
  199. # 返回格式保持与原函数 analyze_centering_rotated 一致:
  200. # ((左%, 右%), (上%, 下%), 角度差), 内框点集, 外框点集
  201. return ((left_percent, right_percent),
  202. (top_percent, bottom_percent),
  203. angle_diff), inner_box_corners_float, outer_box_corners_float
  204. def formate_center_data(center_result,
  205. inner_data: dict, outer_data: dict,
  206. inner_rect_points: List, outer_rect_points: List):
  207. data = {
  208. "box_result": {
  209. "center_inference": {
  210. "angel_diff": center_result[2],
  211. "center_left": center_result[0][0],
  212. "center_right": center_result[0][1],
  213. "center_top": center_result[1][0],
  214. "center_bottom": center_result[1][1]
  215. }
  216. }
  217. }
  218. data['box_result']['inner_box'] = inner_data
  219. data['box_result']['outer_box'] = outer_data
  220. data['box_result']['inner_box']['shapes'][0]['rect_box'] = inner_rect_points
  221. data['box_result']['outer_box']['shapes'][0]['rect_box'] = outer_rect_points
  222. return data
  223. # if __name__ == "__main__":
  224. # img_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001.jpg"
  225. # inner_file_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\inner\250805_pokemon_0001.json"
  226. # outer_file_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\outer\250805_pokemon_0001.json"
  227. #
  228. # inner_pts = get_points_from_file(inner_file_path)
  229. # print(inner_pts)
  230. # print(type(inner_pts))
  231. #
  232. # result = analyze_centering_rotated(inner_file_path, outer_file_path)
  233. # print(result)
  234. # draw_rotated_bounding_boxes(img_path, inner_file_path, outer_file_path)