|
@@ -91,82 +91,127 @@ def draw_rotated_bounding_boxes(image_path, inner_box_file, outer_box_file, outp
|
|
|
plt.show()
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
|
|
|
-def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[str, List]):
|
|
|
|
|
|
|
+def analyze_centering_rotated(inner_points: Union[str, List], outer_points: Union[str, List], ):
|
|
|
"""
|
|
"""
|
|
|
- 使用横平竖直的矩形 (Axis-Aligned Bounding Box) 进行居中分析。
|
|
|
|
|
- 适用于前端已经修正过的矩形数据。
|
|
|
|
|
|
|
+ 使用旋转矩形进行最精确的分析, 包括定向边距和精确的浮点数比值。
|
|
|
"""
|
|
"""
|
|
|
if isinstance(inner_points, str):
|
|
if isinstance(inner_points, str):
|
|
|
- inner_points = get_rect_from_file(inner_points)
|
|
|
|
|
|
|
+ inner_points = get_points_from_file(inner_points)
|
|
|
if isinstance(outer_points, str):
|
|
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)
|
|
|
|
|
|
|
+ outer_points = get_points_from_file(outer_points)
|
|
|
|
|
|
|
|
- # --- 修改点 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
|
|
|
|
|
|
|
+ # 将点列表转换为OpenCV需要的NumPy数组格式
|
|
|
|
|
+ inner_contour = np.array(inner_points, dtype=np.int32)
|
|
|
|
|
+ outer_contour = np.array(outer_points, dtype=np.int32)
|
|
|
|
|
|
|
|
- left_percent = 50.0
|
|
|
|
|
- right_percent = 50.0
|
|
|
|
|
- top_percent = 50.0
|
|
|
|
|
- bottom_percent = 50.0
|
|
|
|
|
|
|
+ # 计算最小面积的旋转矩形
|
|
|
|
|
+ # 结果格式: ((中心点x, 中心点y), (宽度, 高度), 旋转角度)
|
|
|
|
|
+ inner_rect = cv2.minAreaRect(inner_contour)
|
|
|
|
|
+ outer_rect = cv2.minAreaRect(outer_contour)
|
|
|
|
|
|
|
|
- # --- 修改点 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)
|
|
|
|
|
|
|
+ # 获取矩形的4个角点, 并转化为坐标
|
|
|
|
|
+ # outer_box_corners = cv2.boxPoints(outer_rect)
|
|
|
|
|
+ # outer_box_corners_int = np.intp(outer_box_corners).tolist()
|
|
|
|
|
+ #
|
|
|
|
|
+ # inner_box_corners = cv2.boxPoints(inner_rect)
|
|
|
|
|
+ # inner_box_corners_int = np.intp(inner_box_corners).tolist()
|
|
|
|
|
+ def get_rect(contour):
|
|
|
|
|
+ # 获取矩形框
|
|
|
|
|
+ x, y, w, h = cv2.boundingRect(contour)
|
|
|
|
|
+ # 2. 构造 4 个顶点坐标
|
|
|
|
|
+ box_corners_int = np.array([
|
|
|
|
|
+ [x, y], # 左上
|
|
|
|
|
+ [x + w, y], # 右上
|
|
|
|
|
+ [x + w, y + h], # 右下
|
|
|
|
|
+ [x, y + h] # 左下
|
|
|
|
|
+ ], dtype=np.int32).tolist()
|
|
|
|
|
+ return box_corners_int
|
|
|
|
|
+
|
|
|
|
|
+ inner_box_corners_int = get_rect(inner_contour)
|
|
|
|
|
+ outer_box_corners_int = get_rect(outer_contour)
|
|
|
|
|
+
|
|
|
|
|
+ # --- 1. 标准化矩形尺寸,确保宽度总是长边 ---
|
|
|
|
|
+ def standardize_rect(rect):
|
|
|
|
|
+ (cx, cy), (w, h), angle = rect
|
|
|
|
|
+ if w < h:
|
|
|
|
|
+ # 如果宽度小于高度,交换它们,并调整角度
|
|
|
|
|
+ w, h = h, w
|
|
|
|
|
+ angle += 90
|
|
|
|
|
+ return (cx, cy), (w, h), angle
|
|
|
|
|
+
|
|
|
|
|
+ inner_center, inner_dims, inner_angle = standardize_rect(inner_rect)
|
|
|
|
|
+ outer_center, outer_dims, outer_angle = standardize_rect(outer_rect)
|
|
|
|
|
+
|
|
|
|
|
+ print("\n--- 基于旋转矩形的精确分析 ---")
|
|
|
|
|
+ print(
|
|
|
|
|
+ f"内框 (标准化后): 中心=({inner_center[0]:.1f}, {inner_center[1]:.1f}), 尺寸=({inner_dims[0]:.1f}, {inner_dims[1]:.1f}), 角度={inner_angle:.1f}°")
|
|
|
|
|
+ print(
|
|
|
|
|
+ f"外框 (标准化后): 中心=({outer_center[0]:.1f}, {outer_center[1]:.1f}), 尺寸=({outer_dims[0]:.1f}, {outer_dims[1]:.1f}), 角度={outer_angle:.1f}°")
|
|
|
|
|
+
|
|
|
|
|
+ # --- 2. 评估居中度 (基于中心点) ---
|
|
|
|
|
+ offset_x_abs = abs(inner_center[0] - outer_center[0])
|
|
|
|
|
+ offset_y_abs = abs(inner_center[1] - outer_center[1])
|
|
|
|
|
+ print(f"\n[居中度] 图像坐标系绝对中心偏移: 水平={offset_x_abs:.2f} px | 垂直={offset_y_abs:.2f} px")
|
|
|
|
|
+
|
|
|
|
|
+ # --- 3. 评估平行度 ---
|
|
|
|
|
+ angle_diff = abs(inner_angle - outer_angle)
|
|
|
|
|
+ # 处理角度环绕问题 (例如 -89° 和 89° 其实只差 2°)
|
|
|
|
|
+ angle_diff = min(angle_diff, 180 - angle_diff)
|
|
|
|
|
+ print(f"[平行度] 角度差异: {angle_diff:.2f}° (值越小,内外框越平行)")
|
|
|
|
|
+
|
|
|
|
|
+ # --- 4. 计算定向边距和比值 (核心部分) ---
|
|
|
|
|
+ # 计算从外框中心指向内框中心的向量
|
|
|
|
|
+ center_delta_vec = (inner_center[0] - outer_center[0], inner_center[1] - outer_center[1])
|
|
|
|
|
+
|
|
|
|
|
+ # 将外框的旋转角度转换为弧度,用于三角函数计算
|
|
|
|
|
+ angle_rad = math.radians(outer_angle)
|
|
|
|
|
+
|
|
|
|
|
+ # 计算外框自身的坐标轴单位向量(相对于图像坐标系)
|
|
|
|
|
+ # local_x_axis: 沿着外框宽度的方向向量
|
|
|
|
|
+ # local_y_axis: 沿着外框高度的方向向量
|
|
|
|
|
+ local_x_axis = (math.cos(angle_rad), math.sin(angle_rad))
|
|
|
|
|
+ local_y_axis = (-math.sin(angle_rad), math.cos(angle_rad))
|
|
|
|
|
+
|
|
|
|
|
+ # 使用点积 (dot product) 将中心偏移向量投影到外框的局部坐标轴上
|
|
|
|
|
+ # 这能告诉我们,内框中心相对于外框中心,在“卡片自己的水平和垂直方向上”移动了多少
|
|
|
|
|
+ offset_along_width = center_delta_vec[0] * local_x_axis[0] + center_delta_vec[1] * local_x_axis[1]
|
|
|
|
|
+ offset_along_height = center_delta_vec[0] * local_y_axis[0] + center_delta_vec[1] * local_y_axis[1]
|
|
|
|
|
+
|
|
|
|
|
+ # 计算水平和垂直方向上的总边距
|
|
|
|
|
+ total_horizontal_margin = outer_dims[0] - inner_dims[0]
|
|
|
|
|
+ total_vertical_margin = outer_dims[1] - inner_dims[1]
|
|
|
|
|
+
|
|
|
|
|
+ # 根据投影的偏移量来分配总边距
|
|
|
|
|
+ # 理想情况下,偏移为0,左右/上下边距各分一半
|
|
|
|
|
+ # 如果有偏移,一侧增加,另一侧减少
|
|
|
|
|
+ margin_left = (total_horizontal_margin / 2) - offset_along_width
|
|
|
|
|
+ margin_right = (total_horizontal_margin / 2) + offset_along_width
|
|
|
|
|
+ margin_top = (total_vertical_margin / 2) - offset_along_height
|
|
|
|
|
+ margin_bottom = (total_vertical_margin / 2) + offset_along_height
|
|
|
|
|
+
|
|
|
|
|
+ # print("\n[定向边距分析 (沿卡片方向)]")
|
|
|
|
|
+ # print(f"水平边距 (像素): 左={margin_left:.1f} | 右={margin_right:.1f}")
|
|
|
|
|
+ # print(f"垂直边距 (像素): 上={margin_top:.1f} | 下={margin_bottom:.1f}")
|
|
|
|
|
+ #
|
|
|
|
|
+ # print("\n[精确边距比值分析]")
|
|
|
|
|
+ # # 直接展示浮点数比值
|
|
|
|
|
+ # print(f"水平比值 (左:右) = {margin_left:.2f} : {margin_right:.2f}")
|
|
|
|
|
+ # print(f"垂直比值 (上:下) = {margin_top:.2f} : {margin_bottom:.2f}")
|
|
|
|
|
+
|
|
|
|
|
+ # 为了更直观,也可以计算一个百分比
|
|
|
|
|
+ if total_horizontal_margin > 0:
|
|
|
|
|
+ left_percent = (margin_left / total_horizontal_margin) * 100
|
|
|
|
|
+ right_percent = (margin_right / total_horizontal_margin) * 100
|
|
|
print(f"水平边距分布: 左 {left_percent:.1f}% | 右 {right_percent:.1f}%")
|
|
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)
|
|
|
|
|
|
|
+ if total_vertical_margin > 0:
|
|
|
|
|
+ top_percent = (margin_top / total_vertical_margin) * 100
|
|
|
|
|
+ bottom_percent = (margin_bottom / total_vertical_margin) * 100
|
|
|
print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%")
|
|
print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%")
|
|
|
|
|
|
|
|
- angle_diff = 0.0
|
|
|
|
|
-
|
|
|
|
|
return ((left_percent, right_percent),
|
|
return ((left_percent, right_percent),
|
|
|
(top_percent, bottom_percent),
|
|
(top_percent, bottom_percent),
|
|
|
- angle_diff), inner_box_corners_float, outer_box_corners_float
|
|
|
|
|
|
|
+ angle_diff), inner_box_corners_int, outer_box_corners_int
|
|
|
|
|
|
|
|
|
|
|
|
|
def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[str, List]):
|
|
def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[str, List]):
|
|
@@ -179,37 +224,38 @@ def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[s
|
|
|
if isinstance(outer_points, str):
|
|
if isinstance(outer_points, str):
|
|
|
outer_points = get_rect_from_file(outer_points)
|
|
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)
|
|
|
|
|
|
|
+ # 转换为 numpy 数组
|
|
|
|
|
+ inner_contour = np.array(inner_points, dtype=np.int32)
|
|
|
|
|
+ outer_contour = np.array(outer_points, dtype=np.int32)
|
|
|
|
|
|
|
|
- # --- 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])
|
|
|
|
|
|
|
+ def get_int_bounding_rect(points):
|
|
|
|
|
+ # np.min 返回的是 numpy.float32,必须转为 python float
|
|
|
|
|
+ x_min = int(np.min(points[:, 0]))
|
|
|
|
|
+ y_min = int(np.min(points[:, 1]))
|
|
|
|
|
+ x_max = int(np.max(points[:, 0]))
|
|
|
|
|
+ y_max = int(np.max(points[:, 1]))
|
|
|
|
|
|
|
|
w = x_max - x_min
|
|
w = x_max - x_min
|
|
|
h = y_max - y_min
|
|
h = y_max - y_min
|
|
|
return x_min, y_min, w, h
|
|
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)
|
|
|
|
|
|
|
+ # --- 1. 获取横平竖直的矩形参数 (x, y, w, h) ---
|
|
|
|
|
+ # 即使传入的是4个点,用 boundingRect 也能确保取出准确的边界
|
|
|
|
|
+ ix, iy, iw, ih = get_int_bounding_rect(inner_contour)
|
|
|
|
|
+ ox, oy, ow, oh = get_int_bounding_rect(outer_contour)
|
|
|
|
|
|
|
|
# 构造标准的4点格式返回 (保持和 analyze_centering_rotated 输出一致)
|
|
# 构造标准的4点格式返回 (保持和 analyze_centering_rotated 输出一致)
|
|
|
def get_box_corners(x, y, w, h):
|
|
def get_box_corners(x, y, w, h):
|
|
|
return [[x, y], [x + w, y], [x + w, y + h], [x, y + 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)
|
|
|
|
|
|
|
+ inner_box_corners_int = get_box_corners(ix, iy, iw, ih)
|
|
|
|
|
+ outer_box_corners_int = 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}")
|
|
|
|
|
|
|
+ print("\n--- 基于修正矩形(横平竖直)的分析 ---")
|
|
|
|
|
+ print(f"内框: x={ix}, y={iy}, w={iw}, h={ih}")
|
|
|
|
|
+ print(f"外框: x={ox}, y={oy}, w={ow}, h={oh}")
|
|
|
|
|
|
|
|
- # --- 3. 计算边距 (Margins) ---
|
|
|
|
|
|
|
+ # --- 2. 计算边距 (Margins) ---
|
|
|
# 图像坐标系:x向右增加,y向下增加
|
|
# 图像坐标系:x向右增加,y向下增加
|
|
|
|
|
|
|
|
# 左边距:内框左边 - 外框左边
|
|
# 左边距:内框左边 - 外框左边
|
|
@@ -222,7 +268,7 @@ def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[s
|
|
|
# 下边距:外框下边(y+h) - 内框下边(y+h)
|
|
# 下边距:外框下边(y+h) - 内框下边(y+h)
|
|
|
margin_bottom = (oy + oh) - (iy + ih)
|
|
margin_bottom = (oy + oh) - (iy + ih)
|
|
|
|
|
|
|
|
- # --- 4. 计算百分比 ---
|
|
|
|
|
|
|
+ # --- 3. 计算百分比 ---
|
|
|
total_horizontal_margin = margin_left + margin_right
|
|
total_horizontal_margin = margin_left + margin_right
|
|
|
total_vertical_margin = margin_top + margin_bottom
|
|
total_vertical_margin = margin_top + margin_bottom
|
|
|
|
|
|
|
@@ -231,17 +277,17 @@ def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[s
|
|
|
top_percent = 50.0
|
|
top_percent = 50.0
|
|
|
bottom_percent = 50.0
|
|
bottom_percent = 50.0
|
|
|
|
|
|
|
|
- if total_horizontal_margin > 0.0001: # 避免除零
|
|
|
|
|
|
|
+ if total_horizontal_margin > 0:
|
|
|
left_percent = (margin_left / total_horizontal_margin) * 100
|
|
left_percent = (margin_left / total_horizontal_margin) * 100
|
|
|
right_percent = (margin_right / total_horizontal_margin) * 100
|
|
right_percent = (margin_right / total_horizontal_margin) * 100
|
|
|
print(f"水平边距分布: 左 {left_percent:.1f}% | 右 {right_percent:.1f}%")
|
|
print(f"水平边距分布: 左 {left_percent:.1f}% | 右 {right_percent:.1f}%")
|
|
|
|
|
|
|
|
- if total_vertical_margin > 0.0001:
|
|
|
|
|
|
|
+ if total_vertical_margin > 0:
|
|
|
top_percent = (margin_top / total_vertical_margin) * 100
|
|
top_percent = (margin_top / total_vertical_margin) * 100
|
|
|
bottom_percent = (margin_bottom / total_vertical_margin) * 100
|
|
bottom_percent = (margin_bottom / total_vertical_margin) * 100
|
|
|
print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%")
|
|
print(f"垂直边距分布: 上 {top_percent:.1f}% | 下 {bottom_percent:.1f}%")
|
|
|
|
|
|
|
|
- # --- 5. 角度差异 ---
|
|
|
|
|
|
|
+ # --- 4. 角度差异 ---
|
|
|
# 因为已经是横平竖直的矩形,角度差默认为 0
|
|
# 因为已经是横平竖直的矩形,角度差默认为 0
|
|
|
angle_diff = 0.0
|
|
angle_diff = 0.0
|
|
|
|
|
|
|
@@ -249,7 +295,7 @@ def analyze_centering_rect(inner_points: Union[str, List], outer_points: Union[s
|
|
|
# ((左%, 右%), (上%, 下%), 角度差), 内框点集, 外框点集
|
|
# ((左%, 右%), (上%, 下%), 角度差), 内框点集, 外框点集
|
|
|
return ((left_percent, right_percent),
|
|
return ((left_percent, right_percent),
|
|
|
(top_percent, bottom_percent),
|
|
(top_percent, bottom_percent),
|
|
|
- angle_diff), inner_box_corners_float, outer_box_corners_float
|
|
|
|
|
|
|
+ angle_diff), inner_box_corners_int, outer_box_corners_int
|
|
|
|
|
|
|
|
|
|
|
|
|
def formate_center_data(center_result,
|
|
def formate_center_data(center_result,
|