|
@@ -274,7 +274,13 @@ class SkewDetector:
|
|
|
return lines
|
|
return lines
|
|
|
|
|
|
|
|
def calculate_skew_from_vectors(self, lines: List[Tuple[float, float, float, float]]) -> float:
|
|
def calculate_skew_from_vectors(self, lines: List[Tuple[float, float, float, float]]) -> float:
|
|
|
- """从矢量线段计算加权平均倾斜角"""
|
|
|
|
|
|
|
+ """
|
|
|
|
|
+ 从矢量线段计算加权平均倾斜角
|
|
|
|
|
+
|
|
|
|
|
+ 符号约定(图像坐标系,Y轴向下):
|
|
|
|
|
+ - 正值: 线条逆时针倾斜(右端比左端高,y2 < y1)
|
|
|
|
|
+ - 负值: 线条顺时针倾斜(右端比左端低,y2 > y1)
|
|
|
|
|
+ """
|
|
|
angles = []
|
|
angles = []
|
|
|
weights = []
|
|
weights = []
|
|
|
|
|
|
|
@@ -283,21 +289,29 @@ class SkewDetector:
|
|
|
length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
|
length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
|
|
if length < 100:
|
|
if length < 100:
|
|
|
continue
|
|
continue
|
|
|
-
|
|
|
|
|
- angle = math.degrees(math.atan2(y2-y1, x2-x1))
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 🔧 关键修复:符号约定
|
|
|
|
|
+ # 图像坐标系Y轴向下,需要反转Y方向的符号
|
|
|
|
|
+ # 使用 (y1-y2) 而不是 (y2-y1),这样:
|
|
|
|
|
+ # - 逆时针倾斜(右端高): y2 < y1 → (y1-y2) > 0 → 正角度 ✅
|
|
|
|
|
+ # - 顺时针倾斜(右端低): y2 > y1 → (y1-y2) < 0 → 负角度 ✅
|
|
|
|
|
+ angle = math.degrees(math.atan2(y1 - y2, x2 - x1))
|
|
|
|
|
|
|
|
# Normalize to horizontal (-45 to 45)
|
|
# Normalize to horizontal (-45 to 45)
|
|
|
- if angle > 45: angle -= 180
|
|
|
|
|
- elif angle < -45: angle += 180
|
|
|
|
|
|
|
+ if angle > 45:
|
|
|
|
|
+ angle -= 180
|
|
|
|
|
+ elif angle < -45:
|
|
|
|
|
+ angle += 180
|
|
|
|
|
|
|
|
- if abs(angle) > 30: continue
|
|
|
|
|
|
|
+ if abs(angle) > 30:
|
|
|
|
|
+ continue
|
|
|
|
|
|
|
|
angles.append(angle)
|
|
angles.append(angle)
|
|
|
weights.append(length)
|
|
weights.append(length)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if not angles:
|
|
if not angles:
|
|
|
return 0.0
|
|
return 0.0
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
angles = np.array(angles)
|
|
angles = np.array(angles)
|
|
|
weights = np.array(weights)
|
|
weights = np.array(weights)
|
|
|
|
|
|
|
@@ -350,10 +364,25 @@ class SkewDetector:
|
|
|
Args:
|
|
Args:
|
|
|
table_image: 表格图像
|
|
table_image: 表格图像
|
|
|
ocr_boxes: OCR结果列表
|
|
ocr_boxes: OCR结果列表
|
|
|
- skew_angle: 倾斜角度(度数,正值=逆时针,负值=顺时针)
|
|
|
|
|
|
|
+ skew_angle: 倾斜角度(度数)
|
|
|
|
|
+ ⚠️ 符号约定(基于Mask检测):
|
|
|
|
|
+ - 正值: 表格线/文本行相对水平线逆时针倾斜(向上倾斜)
|
|
|
|
|
+ - 负值: 表格线/文本行相对水平线顺时针倾斜(向下倾斜)
|
|
|
|
|
+
|
|
|
|
|
+ 示例:
|
|
|
|
|
+ +2.0° → 表格顶部向右偏移(逆时针)
|
|
|
|
|
+ -2.0° → 表格顶部向左偏移(顺时针)
|
|
|
|
|
|
|
|
Returns:
|
|
Returns:
|
|
|
(矫正后的图像, 更新后的OCR框列表)
|
|
(矫正后的图像, 更新后的OCR框列表)
|
|
|
|
|
+
|
|
|
|
|
+ 矫正原理:
|
|
|
|
|
+ 如果检测到表格逆时针倾斜 +θ°,需要顺时针旋转 -θ° 来矫正
|
|
|
|
|
+ 因此 correction_angle = -skew_angle
|
|
|
|
|
+
|
|
|
|
|
+ cv2.getRotationMatrix2D 约定:
|
|
|
|
|
+ - angle > 0: 图像逆时针旋转
|
|
|
|
|
+ - angle < 0: 图像顺时针旋转
|
|
|
"""
|
|
"""
|
|
|
if abs(skew_angle) < self.skew_threshold:
|
|
if abs(skew_angle) < self.skew_threshold:
|
|
|
return table_image, ocr_boxes
|
|
return table_image, ocr_boxes
|
|
@@ -366,10 +395,21 @@ class SkewDetector:
|
|
|
h, w = table_image.shape[:2]
|
|
h, w = table_image.shape[:2]
|
|
|
center = (w / 2, h / 2)
|
|
center = (w / 2, h / 2)
|
|
|
|
|
|
|
|
- # 计算矫正角度
|
|
|
|
|
- correction_angle = skew_angle
|
|
|
|
|
|
|
+ # 🔧 Bug修复: 矫正角度应该与检测角度相反
|
|
|
|
|
+ # 如果检测到逆时针倾斜(+θ°),需要顺时针旋转(-θ°)来矫正
|
|
|
|
|
+ correction_angle = -skew_angle
|
|
|
|
|
+
|
|
|
|
|
+ logger.debug(
|
|
|
|
|
+ f"倾斜矫正参数: 检测角度={skew_angle:.3f}°, "
|
|
|
|
|
+ f"矫正角度={correction_angle:.3f}° "
|
|
|
|
|
+ f"({'顺时针' if correction_angle < 0 else '逆时针'}旋转)"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# 构建旋转矩阵
|
|
# 构建旋转矩阵
|
|
|
|
|
+ # cv2.getRotationMatrix2D(center, angle, scale)
|
|
|
|
|
+ # - center: 旋转中心点
|
|
|
|
|
+ # - angle: 旋转角度(正值=逆时针,负值=顺时针)
|
|
|
|
|
+ # - scale: 缩放比例(1.0=不缩放)
|
|
|
rotation_matrix = cv2.getRotationMatrix2D(center, correction_angle, 1.0)
|
|
rotation_matrix = cv2.getRotationMatrix2D(center, correction_angle, 1.0)
|
|
|
|
|
|
|
|
# 计算旋转后的图像尺寸(避免裁剪)
|
|
# 计算旋转后的图像尺寸(避免裁剪)
|
|
@@ -409,6 +449,7 @@ class SkewDetector:
|
|
|
logger.warning("BBoxExtractor不可用,无法更新OCR坐标")
|
|
logger.warning("BBoxExtractor不可用,无法更新OCR坐标")
|
|
|
return deskewed_image, ocr_boxes
|
|
return deskewed_image, ocr_boxes
|
|
|
|
|
|
|
|
|
|
+ # 传递矫正角度(已经是负值)给 BBoxExtractor
|
|
|
updated_paddle_boxes = BBoxExtractor.correct_boxes_skew(
|
|
updated_paddle_boxes = BBoxExtractor.correct_boxes_skew(
|
|
|
paddle_boxes, correction_angle, (new_w, new_h)
|
|
paddle_boxes, correction_angle, (new_w, new_h)
|
|
|
)
|
|
)
|
|
@@ -442,14 +483,25 @@ class SkewDetector:
|
|
|
|
|
|
|
|
updated_ocr_boxes.append(updated_box)
|
|
updated_ocr_boxes.append(updated_box)
|
|
|
|
|
|
|
|
- logger.info(f"✅ 倾斜矫正完成: {skew_angle:.3f}° → 0° (图像尺寸: {w}x{h} → {new_w}x{new_h}),已更新{len(updated_ocr_boxes)}个OCR框")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"✅ 倾斜矫正完成: 检测={skew_angle:+.3f}° → "
|
|
|
|
|
+ f"旋转={correction_angle:+.3f}° → 结果≈0° "
|
|
|
|
|
+ f"(图像: {w}×{h} → {new_w}×{new_h}, "
|
|
|
|
|
+ f"OCR框: {len(updated_ocr_boxes)}个已更新)"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
return deskewed_image, updated_ocr_boxes
|
|
return deskewed_image, updated_ocr_boxes
|
|
|
else:
|
|
else:
|
|
|
# OCR框为空,只返回矫正后的图像
|
|
# OCR框为空,只返回矫正后的图像
|
|
|
- logger.info(f"✅ 倾斜矫正完成: {skew_angle:.3f}° → 0° (图像尺寸: {w}x{h} → {new_w}x{new_h}),无OCR框需要更新")
|
|
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ f"✅ 倾斜矫正完成: 检测={skew_angle:+.3f}° → "
|
|
|
|
|
+ f"旋转={correction_angle:+.3f}° → 结果≈0° "
|
|
|
|
|
+ f"(图像: {w}×{h} → {new_w}×{new_h}, 无OCR框)"
|
|
|
|
|
+ )
|
|
|
return deskewed_image, ocr_boxes
|
|
return deskewed_image, ocr_boxes
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
logger.error(f"倾斜矫正失败: {e}")
|
|
logger.error(f"倾斜矫正失败: {e}")
|
|
|
|
|
+ import traceback
|
|
|
|
|
+ logger.error(traceback.format_exc())
|
|
|
return table_image, ocr_boxes
|
|
return table_image, ocr_boxes
|