import cv2 import numpy as np import json import matplotlib.pyplot as plt import math from typing import Union, List from app.core.logger import get_logger logger = get_logger(__name__) def get_points_from_file(json_file): """辅助函数,从JSON文件加载点""" with open(json_file, 'r') as f: data = json.load(f) return data['shapes'][0]['points'] def get_rect_from_file(json_file): """辅助函数,从JSON文件加载点""" with open(json_file, 'r') as f: data = json.load(f) return data['shapes'][0]['rect_box'] def draw_rotated_bounding_boxes(image_path, inner_box_file, outer_box_file, output_path=None): """ 在图片上绘制内框和外框的旋转矩形。 Args: image_path (str): 原始图片的文件路径。 inner_box_file (str): 包含内框坐标的JSON文件路径。 outer_box_file (str): 包含外框坐标的JSON文件路径。 output_path (str, optional): 如果提供,结果图片将被保存到此路径。 默认为None,不保存。 """ # 1. 读取图片 image = cv2.imread(image_path) if image is None: print(f"错误: 无法读取图片 -> {image_path}") return print(f"成功加载图片: {image_path}") # 2. 读取坐标点 inner_points = get_points_from_file(inner_box_file) outer_points = get_points_from_file(outer_box_file) if inner_points is None or outer_points is None: print("因无法加载坐标点,程序终止。") return # 定义颜色 (BGR格式) OUTER_BOX_COLOR = (0, 255, 0) # 绿色 INNER_BOX_COLOR = (0, 0, 255) # 红色 THICKNESS = 10 # 可以根据你的图片分辨率调整线宽 # 3. 处理并绘制外框 # 将点列表转换为OpenCV需要的NumPy数组 outer_contour = np.array(outer_points, dtype=np.int32) # 计算最小面积旋转矩形 outer_rect = cv2.minAreaRect(outer_contour) # 获取矩形的4个角点 outer_box_corners = cv2.boxPoints(outer_rect) # 转换为整数,以便绘制 outer_box_corners_int = np.intp(outer_box_corners) # 在图片上绘制轮廓 cv2.drawContours(image, [outer_box_corners_int], 0, OUTER_BOX_COLOR, THICKNESS) print("已绘制外框 (绿色)。") # 4. 处理并绘制内框 inner_contour = np.array(inner_points, dtype=np.int32) inner_rect = cv2.minAreaRect(inner_contour) inner_box_corners = cv2.boxPoints(inner_rect) inner_box_corners_int = np.intp(inner_box_corners) cv2.drawContours(image, [inner_box_corners_int], 0, INNER_BOX_COLOR, THICKNESS) print("已绘制内框 (红色)。") # 5. 保存结果 (如果指定了路径) if output_path: cv2.imwrite(output_path, image) print(f"结果已保存到: {output_path}") # 6. 显示结果 # OpenCV使用BGR,Matplotlib使用RGB,需要转换颜色通道 image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 使用Matplotlib显示,因为它在各种环境中(如Jupyter)表现更好 plt.figure(figsize=(10, 15)) # 调整显示窗口大小 plt.imshow(image_rgb) plt.title('旋转框可视化 (外框: 绿色, 内框: 红色)') plt.axis('off') # 不显示坐标轴 plt.show() def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[str, List]): """ 使用横平竖直的矩形 (Axis-Aligned Bounding Box) 进行居中分析。 适用于前端已经修正过的矩形数据。 """ if isinstance(inner_points, str): inner_points = get_rect_from_file(inner_points) if isinstance(outer_points, str): outer_points = get_rect_from_file(outer_points) # 1. 转换为 float32 数组,保留计算精度 inner_contour = np.array(inner_points, dtype=np.float32) outer_contour = np.array(outer_points, dtype=np.float32) # --- 修改点 1: 在计算 min/max 后立即转为 Python float --- def get_float_bounding_rect(points): # np.min 返回的是 numpy.float32,必须转为 python float x_min = float(np.min(points[:, 0])) y_min = float(np.min(points[:, 1])) x_max = float(np.max(points[:, 0])) y_max = float(np.max(points[:, 1])) w = x_max - x_min h = y_max - y_min return x_min, y_min, w, h ix, iy, iw, ih = get_float_bounding_rect(inner_contour) ox, oy, ow, oh = get_float_bounding_rect(outer_contour) # 构造标准的4点格式返回 # 因为 ix, iy 等已经是 python float 了,所以列表里装的也是 safe 的 float def get_box_corners(x, y, w, h): return [ [x, y], [x + w, y], [x + w, y + h], [x, y + h] ] inner_box_corners_float = get_box_corners(ix, iy, iw, ih) outer_box_corners_float = get_box_corners(ox, oy, ow, oh) print("\n--- 基于修正矩形(横平竖直, Float精度)的分析 ---") print(f"内框: x={ix:.2f}, y={iy:.2f}, w={iw:.2f}, h={ih:.2f}") # --- 3. 计算边距 (Margins) --- margin_left = ix - ox margin_right = (ox + ow) - (ix + iw) margin_top = iy - oy margin_bottom = (oy + oh) - (iy + ih) # --- 4. 计算百分比 --- total_horizontal_margin = margin_left + margin_right total_vertical_margin = margin_top + margin_bottom left_percent = 50.0 right_percent = 50.0 top_percent = 50.0 bottom_percent = 50.0 # --- 修改点 2: 计算结果也要转为 float (虽然上面的 ix 是 float,但运算可能再次产生 numpy 类型) --- if total_horizontal_margin > 0.0001: left_percent = float((margin_left / total_horizontal_margin) * 100) right_percent = float((margin_right / total_horizontal_margin) * 100) print(f"水平边距分布: 左 {left_percent:.1f}% | 右 {right_percent:.1f}%") if total_vertical_margin > 0.0001: top_percent = float((margin_top / total_vertical_margin) * 100) bottom_percent = float((margin_bottom / total_vertical_margin) * 100) print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%") angle_diff = 0.0 return ((left_percent, right_percent), (top_percent, bottom_percent), angle_diff), inner_box_corners_float, outer_box_corners_float def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[str, List]): """ 使用横平竖直的矩形 (Axis-Aligned Bounding Box) 进行居中分析。 适用于前端已经修正过的矩形数据。 """ if isinstance(inner_points, str): inner_points = get_rect_from_file(inner_points) if isinstance(outer_points, str): outer_points = get_rect_from_file(outer_points) # --- 1. 转换为 float32 数组,保留精度 --- inner_contour = np.array(inner_points, dtype=np.float32) outer_contour = np.array(outer_points, dtype=np.float32) # --- 2. 手动计算 float 级别的 boundingRect --- def get_float_bounding_rect(points): # points shape: (N, 2) x_min = np.min(points[:, 0]) y_min = np.min(points[:, 1]) x_max = np.max(points[:, 0]) y_max = np.max(points[:, 1]) w = x_max - x_min h = y_max - y_min return x_min, y_min, w, h ix, iy, iw, ih = get_float_bounding_rect(inner_contour) ox, oy, ow, oh = get_float_bounding_rect(outer_contour) # 构造标准的4点格式返回 (保持和 analyze_centering_rotated 输出一致) def get_box_corners(x, y, w, h): return [[x, y], [x + w, y], [x + w, y + h], [x, y + h]] inner_box_corners_float = get_box_corners(ix, iy, iw, ih) outer_box_corners_float = get_box_corners(ox, oy, ow, oh) print("\n--- 基于修正矩形(横平竖直, Float精度)的分析 ---") print(f"内框: x={ix:.2f}, y={iy:.2f}, w={iw:.2f}, h={ih:.2f}") print(f"外框: x={ox:.2f}, y={oy:.2f}, w={ow:.2f}, h={oh:.2f}") # --- 3. 计算边距 (Margins) --- # 图像坐标系:x向右增加,y向下增加 # 左边距:内框左边 - 外框左边 margin_left = ix - ox # 右边距:外框右边(x+w) - 内框右边(x+w) margin_right = (ox + ow) - (ix + iw) # 上边距:内框上边 - 外框上边 margin_top = iy - oy # 下边距:外框下边(y+h) - 内框下边(y+h) margin_bottom = (oy + oh) - (iy + ih) # --- 4. 计算百分比 --- total_horizontal_margin = margin_left + margin_right total_vertical_margin = margin_top + margin_bottom left_percent = 50.0 right_percent = 50.0 top_percent = 50.0 bottom_percent = 50.0 if total_horizontal_margin > 0.0001: # 避免除零 left_percent = (margin_left / total_horizontal_margin) * 100 right_percent = (margin_right / total_horizontal_margin) * 100 print(f"水平边距分布: 左 {left_percent:.1f}% | 右 {right_percent:.1f}%") if total_vertical_margin > 0.0001: top_percent = (margin_top / total_vertical_margin) * 100 bottom_percent = (margin_bottom / total_vertical_margin) * 100 print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%") # --- 5. 角度差异 --- # 因为已经是横平竖直的矩形,角度差默认为 0 angle_diff = 0.0 # 返回格式保持与原函数 analyze_centering_rotated 一致: # ((左%, 右%), (上%, 下%), 角度差), 内框点集, 外框点集 return ((left_percent, right_percent), (top_percent, bottom_percent), angle_diff), inner_box_corners_float, outer_box_corners_float def formate_center_data(center_result, inner_data: dict, outer_data: dict, inner_rect_points: List, outer_rect_points: List): data = { "box_result": { "center_inference": { "angel_diff": center_result[2], "center_left": center_result[0][0], "center_right": center_result[0][1], "center_top": center_result[1][0], "center_bottom": center_result[1][1] } } } data['box_result']['inner_box'] = inner_data data['box_result']['outer_box'] = outer_data data['box_result']['inner_box']['shapes'][0]['rect_box'] = inner_rect_points data['box_result']['outer_box']['shapes'][0]['rect_box'] = outer_rect_points return data # if __name__ == "__main__": # img_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001.jpg" # inner_file_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\inner\250805_pokemon_0001.json" # outer_file_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\outer\250805_pokemon_0001.json" # # inner_pts = get_points_from_file(inner_file_path) # print(inner_pts) # print(type(inner_pts)) # # result = analyze_centering_rotated(inner_file_path, outer_file_path) # print(result) # draw_rotated_bounding_boxes(img_path, inner_file_path, outer_file_path)