Jelajahi Sumber

fix: 修复倾斜角度计算和矫正逻辑,确保符号约定一致性

zhch158_admin 5 hari lalu
induk
melakukan
fd2b0bf294

+ 66 - 14
ocr_tools/universal_doc_parser/models/adapters/wired_table/skew_detection.py

@@ -274,7 +274,13 @@ class SkewDetector:
         return lines
 
     def calculate_skew_from_vectors(self, lines: List[Tuple[float, float, float, float]]) -> float:
-        """从矢量线段计算加权平均倾斜角"""
+        """
+        从矢量线段计算加权平均倾斜角
+    
+        符号约定(图像坐标系,Y轴向下):
+        - 正值: 线条逆时针倾斜(右端比左端高,y2 < y1)
+        - 负值: 线条顺时针倾斜(右端比左端低,y2 > y1)
+        """
         angles = []
         weights = []
         
@@ -283,21 +289,29 @@ class SkewDetector:
             length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
             if length < 100:
                 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)
-            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)
             weights.append(length)
-            
+        
         if not angles:
             return 0.0
-            
+        
         angles = np.array(angles)
         weights = np.array(weights)
         
@@ -350,10 +364,25 @@ class SkewDetector:
         Args:
             table_image: 表格图像
             ocr_boxes: OCR结果列表
-            skew_angle: 倾斜角度(度数,正值=逆时针,负值=顺时针)
+            skew_angle: 倾斜角度(度数)
+                ⚠️ 符号约定(基于Mask检测):
+                - 正值: 表格线/文本行相对水平线逆时针倾斜(向上倾斜)
+                - 负值: 表格线/文本行相对水平线顺时针倾斜(向下倾斜)
+                
+                示例:
+                    +2.0° → 表格顶部向右偏移(逆时针)
+                    -2.0° → 表格顶部向左偏移(顺时针)
             
         Returns:
             (矫正后的图像, 更新后的OCR框列表)
+            
+        矫正原理:
+            如果检测到表格逆时针倾斜 +θ°,需要顺时针旋转 -θ° 来矫正
+            因此 correction_angle = -skew_angle
+            
+            cv2.getRotationMatrix2D 约定:
+            - angle > 0: 图像逆时针旋转
+            - angle < 0: 图像顺时针旋转
         """
         if abs(skew_angle) < self.skew_threshold:
             return table_image, ocr_boxes
@@ -366,10 +395,21 @@ class SkewDetector:
             h, w = table_image.shape[: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)
             
             # 计算旋转后的图像尺寸(避免裁剪)
@@ -409,6 +449,7 @@ class SkewDetector:
                     logger.warning("BBoxExtractor不可用,无法更新OCR坐标")
                     return deskewed_image, ocr_boxes
                 
+                # 传递矫正角度(已经是负值)给 BBoxExtractor
                 updated_paddle_boxes = BBoxExtractor.correct_boxes_skew(
                     paddle_boxes, correction_angle, (new_w, new_h)
                 )
@@ -442,14 +483,25 @@ class SkewDetector:
                     
                     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
             else:
                 # 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
-            
+        
         except Exception as e:
             logger.error(f"倾斜矫正失败: {e}")
+            import traceback
+            logger.error(traceback.format_exc())
             return table_image, ocr_boxes