Просмотр исходного кода

fix(优化单元格OCR处理逻辑): 更新有线表格识别文档,详细说明二次OCR的核心设计与实现,增加对水印过滤、纵向完整性检测及列空判断的描述,提升OCR处理的准确性与灵活性。

zhch158_admin 4 часов назад
Родитель
Сommit
4cd6801a2f

+ 69 - 3
docs/ocr_tools/universal_doc_parser/OCR识别差异分析与改进方案.md

@@ -79,6 +79,59 @@
 
 ## 💡 改进方案
 
+### 方案0:单元格二次 OCR(已实施,效果最佳)⭐
+
+**核心思路**:不仅依赖一次整页 OCR 的参数调优,而是在有线表格场景对**每个单元格单独裁剪、预处理、再 OCR**。这是从"提升整页 OCR 质量"到"对关键区域精准重试"的策略升级。
+
+#### 为什么二次 OCR 比调整参数更有效
+
+| 问题 | 仅调参数 | 二次 OCR(单元格裁剪) |
+|------|---------|----------------------|
+| **检测框不完整**("合同资产"→"货资产") | 调高 box_thresh 可能直接丢失该行 | 单格裁剪后文字占比更高,det 盒完整 |
+| **检测框合并错误**("其他非流动资产"→"非其非产动产") | 关闭 merge_det_boxes 可能引入其他问题 | 单元格天然隔离,不存在跨格合并 |
+| **水印干扰**("有限公司"被误识别) | 无法处理 | 斜框角度过滤(`_is_bbox_slanted`)+ 格级去水印 |
+| **弱信号遗漏**(空格、低分碎片) | 降低 drop_score 引入噪声 | 精确判定列空性(`_column_empty_ratio`),按需触发 |
+| **纵向不全**(只识别到"支行") | 无法检测 | `_is_ocr_vertically_incomplete` 纵向偏移+空白不对称检测 |
+
+#### 处理流程
+
+详见 [有线表格识别技术文档 — 六、单元格 OCR](./有线表格识别技术文档.md),核心要点:
+
+```mermaid
+graph TB
+    A[整页 OCR] --> B[一验:中心点匹配<br/>fill_text_by_center_point]
+    B --> C{触发二次 OCR?<br/>_should_second_pass_cell}
+    C --> D[Pass1:格级预处理<br/>去水印+upscale+OCR]
+    C --> E[跳过,保持一验结果]
+    D --> F{Pass1 分数达标?}
+    F -->|否| G[Pass2:enhance_retry<br/>更强预处理+再OCR]
+    F -->|是| H[采纳结果]
+    G --> H
+    
+    style B fill:#e1f5ff
+    style D fill:#fff4e1
+    style G fill:#fff4e1
+```
+
+#### 关键能力
+
+| 能力 | 方法 | 对应问题 |
+|------|------|---------|
+| **斜框过滤** | `_is_bbox_slanted`(多边形角度 > 10° → 丢弃) | 水印误识别(如"有限公司"被当成单元格文字) |
+| **纵向完整性** | `_is_ocr_vertically_incomplete`(y_center 偏移 + 空白不对称) | 检测框截断(如只识别到"支行"而非完整户名) |
+| **列空判断** | `_column_empty_ratio`(基于 `matched_boxes_list` 判空) | 区分"OCR 遗漏"和"列本来就空",仅前者触发重试 |
+| **Pass2 独立配置** | `enhance_retry` 可与 `cell_preprocess` 解耦(`upscale_min_side` 等) | 低分难例二次增强,不牺牲ปกติ格的处理效率 |
+
+#### 与方案1-4的关系
+
+方案1-4 的整页 OCR 参数调整 **仍然有价值**,是与二次 OCR **互补**而非替代:
+
+- **整页 OCR 参数**(方案1-3):降低噪声框基数,减少二次 OCR 的触发频率
+- **二次 OCR**(方案0):对整页 OCR 无法解决的问题做精准修正
+- 推荐组合:整页 OCR 平衡参数 + `bank_statement` 模式二次 OCR
+
+---
+
 ### 方案1:调整OCR参数(推荐,快速改进)
 
 修改 `MinerUOCRRecognizer` 的初始化参数,使其更接近 PPStructureV3:
@@ -179,7 +232,18 @@ def get_ocr_config(pdf_type: str, has_tables: bool) -> Dict[str, Any]:
 
 ## 🎯 推荐实施步骤
 
-### 阶段1:快速改进(立即实施)
+### 阶段0:单元格二次 OCR(✅ 已完成)
+
+1. **有线表格二次 OCR**:
+   - 整页 OCR 匹配后,对需要重处理的单元格裁剪再 OCR
+   - `_is_bbox_slanted`:过滤斜向水印
+   - `_is_ocr_vertically_incomplete`:检测纵向不完全文本
+   - `_column_empty_ratio`:基于 matched_boxes_list 智能判空
+   - Pass1 格级预处理 + Pass2 enhance_retry 独立配置
+
+> 详见:[有线表格识别技术文档 — 六、单元格 OCR](./有线表格识别技术文档.md)
+
+### 阶段1:快速改进(整页OCR参数调优)
 
 1. **调整检测参数**:
    - `det_db_box_thresh`: 0.3 → **0.6**
@@ -239,9 +303,11 @@ ocr:
 
 ## 📈 预期效果
 
-实施阶段1改进后:
+实施阶段0(✅ 已完成)+ 阶段1改进后:
 - ✅ 检测准确率提升:减少噪声框和错误合并
-- ✅ 识别完整度提升:保留更多低置信度但正确的结果
+- ✅ 识别完整度提升:单元格二次 OCR 修复整页漏检、截断误识别
+- ✅ 水印豁免:`_is_bbox_slanted` 过滤斜向水印,减少误匹配
+- ✅ 智能空列判断:`_column_empty_ratio` 区分"OCR遗漏"与"真空白",减少无效重试
 - ✅ 表格识别准确率:预计从 60-70% 提升到 85-90%
 
 ## ⚠️ 注意事项

+ 237 - 1
docs/ocr_tools/universal_doc_parser/有线表格识别技术文档.md

@@ -13,7 +13,7 @@ wired_table/
 ├── ocr_formatter.py # OCR 格式转换
 ├── skew_detection.py # 倾斜检测与矫正 ⭐
 ├── grid_recovery.py # 网格恢复(表格线 → 单元格)⭐
-├── text_filling.py # 文本填充(OCR → 单元格)⭐
+├── text_filling.py # 文本填充(OCR → 单元格)⭐⭐⭐
 ├── html_generator.py # HTML 生成
 └── visualization.py # 可视化工具
 
@@ -53,3 +53,239 @@ graph TB
     style H fill:#fff4e1
 ```
 
+---
+
+## 六、单元格 OCR:文本填充机制(text_filling.py)⭐⭐⭐
+
+这是整个表格识别中最复杂的部分。银行流水等场景的 OCR 质量直接影响最终结果,本节详细说明其设计思路和实现细节。
+
+### 6.1 为什么需要二次 OCR?
+
+一次 OCR(整页识别)存在天然局限:
+
+| 问题 | 表现 | 原因 |
+|------|------|------|
+| **检测遗漏** | 单元格内无文字 | 表格线干扰、文字过小、字号不统一 |
+| **识别不完整** | 只识别到"支行"而非完整户名 | 文字偏单元格底部,上半部分被忽略 |
+| **水印干扰** | "有限公司"被误识别为金额 | 斜向水印与正常文字重叠在同一单元格 |
+| **低分碎片** | 识别结果置信度低 | 背景噪声、对比度不足 |
+
+二次 OCR 在**单元格裁剪图**上重新识别,预处理空间更大,精度更高。
+
+### 6.2 核心设计理念
+
+**"先匹配,后反刍"**:
+
+1. **一验**(`fill_text_by_center_point`):将整页 OCR 结果按中心点 + 重叠比例匹配到单元格
+2. **触发判定**(`_should_second_pass_cell`):判断哪些格需要二次 OCR
+3. **二验**(`second_pass_ocr_fill`):对判定需重处理的格,裁剪后再做 OCR
+
+```mermaid
+graph TB
+    subgraph 一验:整页OCR匹配
+        A[整页OCR结果<br/>ocr_boxes] --> B[遍历每个单元格]
+        B --> C{中心点+重叠比例<br/>匹配到OCR框?}
+        C -->|是| D[斜框过滤<br/>_is_bbox_slanted >10°?]
+        D -->|否,水平| E[纵向完整性检测<br/>_is_ocr_vertically_incomplete?]
+        D -->|是,斜框| D2[丢弃该框<br/>水印豁免]
+        E -->|不完整| E2[清空text/score<br/>→ 加入need_reocr_indices]
+        E -->|完整| F[嵌套框处理<br/>_resolve_cell_matched_boxes]
+        F --> G[拼接文本 + 计算置信度]
+        C -->|否| H[text=""<br/>score=0.0]
+    end
+    
+    subgraph 触发判定:_should_second_pass_cell
+        I{是否需要二次OCR?} --> J[force_all]
+        I --> K[spanning/cross_cell]
+        I --> L[low_first_pass_score <0.9]
+        I --> M[tall_cell_low_score]
+        I --> N{bank_statement<br/>空格特殊逻辑}
+        N -->|表头空格| N1[header_row_empty → 触发]
+        N -->|表体空格| N2[_column_empty_ratio<br/>matched_boxes_list判空]
+        N2 -->|列大部分有框| N3[body_row_empty_column_mostly_filled → 触发]
+        N2 -->|列大部分无框| N4[跳过,列本身就空]
+        I --> O{任何reason命中?}
+    end
+    
+    subgraph 二验:单元格裁剪OCR
+        P[裁剪cell图像] --> Q[Pass1: 格级预处理<br/>去水印 + upscale + OCR]
+        Q --> R{Pass1分数达标?}
+        R -->|是| S[采纳Pass1结果]
+        R -->|否| T[Pass2: enhance_retry<br/>更强预处理 + OCR]
+        T --> U[择优:pick_better]
+        U --> V[更新texts]
+    end
+    
+    G --> I
+    H --> I
+    D2 --> F
+    E2 --> I
+    O -->|是| P
+    O -->|否| W[保持一验结果]
+    
+    style D fill:#ffe1e1
+    style E fill:#ffe1e1
+    style F fill:#e1f5ff
+    style N fill:#e1f5ff
+    style N2 fill:#e1f5ff
+    style Q fill:#fff4e1
+    style T fill:#fff4e1
+```
+
+### 6.3 一验匹配策略
+
+#### 中心点 + 重叠比例
+
+```python
+# 第一步:中心点筛选
+if not (cell_x1 <= cx <= cell_x2 and cell_y1 <= cy <= cell_y2):
+    continue  # 中心点必须在单元格内
+
+# 第二步:重叠比例检查
+overlap_ratio = calculate_overlap_ratio(ocr_bbox, cell_bbox)
+if overlap_ratio >= 0.5:  # OCR框至少50%在单元格内
+    matched.append(...)
+```
+
+> 相比纯 IOU 更宽松,能匹配到更多 OCR 框;比纯中心点更准确,能过滤跨单元格的 OCR 框。
+
+#### 嵌套框处理(`_resolve_cell_matched_boxes`)
+
+一个单元格内可能出现大框套小框的情况:
+
+| 场景 | 处理 |
+|------|------|
+| 大框有字、小框在内 | 丢弃小框碎片,保留大框 |
+| 大框无字、小框在内 | 丢弃小框,整格 score=0 → 触发二次 OCR |
+
+#### 斜框过滤(`_is_bbox_slanted`)
+
+银行流水的水印通常为 30-45° 斜向,与正常水平文字(0-2°)角度差异明显:
+
+```python
+@staticmethod
+def _is_bbox_slanted(original_box, *, angle_threshold=10.0):
+    """基于原始多边形角度检测。上边与水平面夹角 > angle_threshold → 斜框。"""
+    poly = box.get("poly") or box.get("original_bbox") or box.get("bbox") or []
+    # 计算上边 dx, dy → atan2 → 角度
+    angle_deg = abs(math.degrees(math.atan2(dy, dx)))
+    return angle_deg > angle_threshold
+```
+
+> **为什么放在 `_resolve_cell_matched_boxes` 中?** 斜框丢弃与嵌套框丢弃的语义一致——都是"某个匹配 OCR 框不应被保留"。
+
+#### 纵向完整性检测(`_is_ocr_vertically_incomplete`)
+
+银行流水单元格文字纵向居中。若一次 OCR 只匹配到局部字(如"支行"),需要触发二次 OCR。
+
+**两种判定互补**:
+
+| 条件 | 原理 | 示例 |
+|------|------|------|
+| **y_center 偏移** | 合并 bbox 的 y_center 偏离 cell y_center 超过 `cell_h * 1/3` | OCR 框完全跑到单元格底部 |
+| **空白不对称** | 上下空白占比差 > 0.3(文字贴顶部或底部) | "支行" 36px 高偏在 86px 高单元格的底部 |
+
+```python
+# 条件一:y_center 偏移
+deviation = abs(merged_yc - cell_yc)
+if deviation > cell_h * y_deviation_ratio:  # 默认 1/3
+    return True
+
+# 条件二:空白不对称
+top_gap = merged_y1 - cell_y1
+bottom_gap = cell_y2 - merged_y2
+asymmetry = abs(top_gap/cell_h - bottom_gap/cell_h)
+return asymmetry > margin_asymmetry_threshold  # 默认 0.3
+```
+
+> **为什么不单纯用 y_center 偏移?** "支行"偏底部但 y_center 偏差不大(20.5px < 28.6px),单靠 y_center 条件无法检测。空白不对称(上方 53% vs 下方 5% → 0.48)才能正确触发。
+
+### 6.4 bank_statement 空单元格特殊逻辑
+
+银行流水的表格结构具有规律性,可以对空单元格做更智能的判断。
+
+#### 问题
+
+如果一列的多数格本身都是空的(如"附言"列),那么其中一个空格就可能是**真的空**,不应触发二次 OCR。反之,如果一列大部分有文字,某个空格大概率是 OCR 遗漏。
+
+#### 实现:基于 matched_boxes_list 的列空判断
+
+**关键设计决策**:用 `matched_boxes_list`(是否命中 OCR 框)而非 `texts`(是否识别出文字)判空。
+
+| `matched_boxes_list[i]` | `texts[i]` | 含义 |
+|-------------------------|-----------|------|
+| `[{text: '', bbox: [...]}]` | `""` | OCR **检到了框但未识别** → 列不空 |
+| `[]` | `""` | **真的空** → 列空 |
+| `[{text: '广东农信', ...}]` | `"广东农信"` | 正常 |
+
+因为 OCR 可能检测出框(说明有内容)但识别失败(text 为空)。用 texts 判空会把"有框无字"的格误判为该列为空 → 跳过二次 OCR → 本该识别的内容永远丢失。
+
+```python
+@staticmethod
+def _column_empty_ratio(merged_cells, matched_boxes_list, col, header_row):
+    col_cells = [
+        matched_boxes_list[j]  # 基于已匹配的 OCR 框判空
+        for j, c in enumerate(merged_cells)
+        if int(c.get("col")) == col and int(c.get("row")) > header_row
+    ]
+    return sum(1 for boxes in col_cells if not boxes) / len(col_cells)
+```
+
+**触发规则**:
+
+| 场景 | 条件 | 触发原因 |
+|------|------|---------|
+| 表头行空格 | `row == header_row` | `header_row_empty` |
+| 表体行空格 + 列大部分有 OCR 框 | `col_empty_ratio < 0.5` | `body_row_empty_column_mostly_filled` |
+| 表体行空格 + 列大部分无 OCR 框 | `col_empty_ratio >= 0.5` | **跳过**,列本来就空 |
+
+### 6.5 二次 OCR 预处理
+
+```mermaid
+graph TB
+    A[raw_crop 单元格裁剪] --> B{Pass1: light 模式}
+    B --> C[格级去水印<br/>WatermarkProcessor cell]
+    C --> D[可选: denoise / contrast]
+    D --> E[upscale<br/>light.upscale_min_side]
+    E --> F[det分行 + whole兜底 OCR]
+    
+    F --> G{分数达标?}
+    G -->|是| H[采纳Pass1]
+    G -->|否| I{Pass2: enhance_retry<br/>是否为启用?}
+    I -->|否| H
+    I -->|是| J[enhance 模式预处理<br/>可选更激进去水印+对比度+upscale]
+    J --> K[再次 OCR]
+    K --> L[择优: pick_better_ocr_result]
+    L --> H
+    
+    style C fill:#fff4e1
+    style E fill:#e1f5ff
+    style J fill:#fff4e1
+```
+
+### 6.6 配置示例
+
+```yaml
+table_recognition_wired:
+  second_pass_ocr:
+    reocr_mode: "bank_statement"   # 启用水印过滤、列空判断等启发式
+    min_hit_score: 0.9
+    suspicious_short_min_chars: 4
+    cell_preprocess:               # Pass1 预处理
+      watermark:
+        enabled: true
+        method: threshold
+        threshold: 155
+      upscale_min_side: 96         # Pass1 放大最短边
+      contrast:
+        enabled: false
+    enhance_retry:                 # Pass2 预处理
+      enabled: true
+      upscale_min_side: 128        # Pass2 独立放大尺寸
+      contrast:
+        enabled: true
+        method: clahe
+        clip_limit: 1.0
+        tile_grid_size: 4
+```
+

+ 23 - 11
docs/ocr_tools/universal_doc_parser/模型统一框架.md

@@ -436,15 +436,16 @@ graph TB
      - ✅ 提高有线表格识别的鲁棒性和准确性
 
 4. **文本填充**(`text_filling.py`):
-   - 计算OCR框与单元格的中心点匹配
-   - 匹配OCR文本到对应单元格
-   - 支持置信度过滤(`ocr_conf_threshold`)
+   - 整页 OCR 结果按中心点+重叠比例匹配到单元格
+   - 匹配阶段自动过滤斜向 OCR 框(水印豁免)
+   - 检测纵向不完整( текст偏顶部/底部),触发二次 OCR
 
 5. **二次OCR修正**(`text_filling.py`):
-   - **总是执行**,不区分PDF类型
-   - 对单元格进行裁剪OCR,补充或修正文本
-   - 处理空文本单元格、低置信度文本、OCR误合并等情况
-   - 对于文字PDF(`pdf_type='txt'`),空文本单元格不触发二次OCR,为了解决文字PDF中误合并问题
+   - 基于 `_should_second_pass_cell` 触发:
+     - 跨格/低分/高窄格:统一触发
+     - `bank_statement` 模式:表头空格、列级智能判断(`_column_empty_ratio` 基于 `matched_boxes_list` 判空)
+   - 支持 Pass1(格级预处理 + OCR)和 Pass2(增强重试 + 对比度)
+   - `enhance_retry` 可独立配置 `upscale_min_side`,与 Pass1 解耦
 
 6. **HTML生成**:
    - 生成标准表格HTML
@@ -458,10 +459,13 @@ graph TB
    - 如果有线识别失败(返回空HTML或空cells),自动切换到VLM识别
    - 无需手动干预,确保识别鲁棒性
 
-**文字PDF支持**:
-- 支持传递 `pdf_type='txt'` 参数到有线表格识别器
-- 二次OCR修正,处理空文本单元格、低置信度文本、OCR误合并等情况
-- 对于文字PDF(`pdf_type='txt'`),空文本单元格不触发二次OCR,为了解决文字PDF中误合并问题
+**✅ 二次 OCR 核心改进(最新)**:
+- 斜框过滤(`_is_bbox_slanted`):基于 OCR 原始多边形角度,过滤 >10° 斜向水印
+- 纵向完整性检测(`_is_ocr_vertically_incomplete`):y_center 偏移 + 空白不对称互补判定
+- `bank_statement` 列空判断:基于 `matched_boxes_list` 而非 `texts`,区分"有框无字"和"真正为空"
+- 独立 Pass2 配置:`enhance_retry.upscale_min_side` 与 `cell_preprocess.upscale_min_side` 解耦
+
+详见:[有线表格识别技术文档 — 六、单元格OCR](./有线表格识别技术文档.md)
 
 **关键配置**:
 ```yaml
@@ -480,6 +484,14 @@ table_recognition_wired:
   col_threshold: 15             # 列合并阈值(像素)
   ocr_conf_threshold: 0.8       # OCR置信度阈值
   use_custom_postprocess: true  # 使用自定义后处理(支持OCR边缘补偿)
+  second_pass_ocr:
+    reocr_mode: "bank_statement"
+    cell_preprocess:
+      watermark: {enabled: true, method: threshold, threshold: 155}
+      upscale_min_side: 96
+    enhance_retry:
+      enabled: true
+      upscale_min_side: 128
 ```
 
 #### 无线表格识别流程(VLM)

+ 50 - 26
docs/ocr_tools/universal_doc_parser/水印去除技术文档.md

@@ -6,12 +6,29 @@
 
 除 PDF/页级预处理外,银行流水等场景在 **有线表格二次 OCR** 中可对单个单元格裁剪图再次去水印(`text_filling.cell_preprocess`)。
 
+### 两种完全不同的水印机制
+
+**关键认知**:文字 PDF 和图片 PDF 的水印是两种完全不同的机制,不可混用配置。
+
+| 维度 | 文字 PDF(`pdf_type='txt'`) | 图片 PDF(`pdf_type='ocr'`) |
+|------|---------------------------|---------------------------|
+| **水印形态** | PDF 内部的 Form/Image XObject | 像素化的浅色文字纹理 |
+| **去水印原理** | 操作 PDF 字节流(清空/空白化 XObject),再渲染 | OpenCV 像素级阈值处理 |
+| **是否需要阈值** | **不需要** | 需要(threshold 等参数) |
+| **配置位置** | `input.txt_pdf_watermark_removal` | `preprocessor.watermark_removal` |
+| **默认推荐** | **打开**(`enabled: true`) | **关闭**(`enabled: false`,对图片 PDF 效果不佳) |
+| **实现文件** | `ocr_utils/watermark/pdf.py` | `ocr_utils/watermark/removal.py` |
+
+### 三层水印防护
+
 | 层级 | 处理对象 | 配置位置 | 适用场景 |
 |------|---------|---------|---------|
 | **PDF 层级** | 文字型 PDF 的 XObject | `input.txt_pdf_watermark_removal` | 文字型 PDF,渲染前 |
 | **页级图像** | 整页渲染图 | `preprocessor.watermark_removal` | 扫描件页级 OCR 前(可选) |
 | **格级图像** | 单元格裁剪图 | `table_recognition_wired.second_pass_ocr.cell_preprocess.watermark` | 二次 OCR 前(推荐 cell-first) |
 
+**当前策略**(银行流水场景):`txt_pdf_watermark_removal.enabled: true`(文字 PDF 走 XObject 去水印),`preprocessor.watermark_removal.enabled: false`(图片 PDF 关掉像素级去水印),格级独立配置。
+
 **实现模块:**
 
 | 路径 | 职责 |
@@ -31,27 +48,27 @@
 graph TB
     A[输入文档] --> B{是否为 PDF?}
     
-    B -->|是| C[阶段一: PDF 层级去水印]
+    B -->|是| C[阶段一: PDF 层级去水印<br/>XObject 清理,无需阈值]
     B -->|否| F
     
     C --> D{启用 txt_pdf_watermark_removal?}
     D -->|是| E[扫描前 N 页检测水印 XObject]
     D -->|否| G
     E --> E1{发现水印?}
-    E1 -->|是| E2[清除 XObject 内容流]
+    E1 -->|是| E2[Form XObject → 清空内容流<br/>Image XObject → 替换全白]
     E1 -->|否| G
     E2 --> G
     
     G[渲染为图像] --> H{PDF 类型?}
-    H -->|文字型 txt| I[跳过阶段二]
+    H -->|文字型 txt| I[跳过阶段二<br/>水印已在PDF层面清除]
     H -->|扫描件 ocr| J
     
-    F[图像输入] --> J[阶段二: 图像级去水印]
+    F[图像输入] --> J[阶段二: 图像级去水印<br/>像素阈值处理]
     
     J --> K{启用 watermark_removal?}
-    K -->|是| L[WatermarkProcessor page]
+    K -->|是| L[WatermarkProcessor page<br/>method: threshold]
     K -->|否| N
-    L --> M[method: threshold / masked / masked_adaptive]
+    L --> M[gray > threshold → 255]
     M --> N[方向校正]
     
     N --> O[Layout 检测]
@@ -396,35 +413,39 @@ python cell_sweep.py /path/to/tablecell_ocr/ -o ./sweep_out --quick
 ```yaml
 input:
   txt_pdf_watermark_removal:
-    enabled: true
+    enabled: true          # 文字PDF:XObject清理,无需阈值
     sample_pages: 3
 
 preprocessor:
   order: orient_first
   watermark_removal:
-    enabled: false              # cell-first:页级可关
+    enabled: false         # 图片PDF:像素阈值去除,默认关闭(效果不佳)
     detect_before_remove: true
     method: threshold
-    threshold: 175              # 页级预设默认 175
+    threshold: 175
     contrast_enhancement:
       enabled: false
 
 table_recognition_wired:
   second_pass_ocr:
-    suspicious_short_min_chars: 4
     cell_preprocess:
       watermark:
         enabled: true
         method: threshold
-        threshold: 155          # 建议显式写出;未写则用 cell 预设 155
+        threshold: 155     # 格级阈值,与页级独立
       denoise:
         enabled: false
       contrast:
-        enabled: false          # Pass1 可选 text_restore
-      light:
-        upscale_min_side: 192
+        enabled: false
+      upscale_min_side: 96
     enhance_retry:
-      enabled: false            # Pass2 增强重试(与 cell_preprocess 同级)
+      enabled: true
+      upscale_min_side: 128
+      contrast:
+        enabled: true
+        method: clahe
+        clip_limit: 1.0
+        tile_grid_size: 4
 ```
 
 ### 配置项详解
@@ -466,24 +487,27 @@ if is_pdf:
 2. `enabled: true`
 3. 扫描发现水印 XObject
 
+> **注意**:此阶段**无需阈值参数**,直接操作 PDF XObject。
+
 ### 阶段二触发条件
 
 ```python
 # pipeline_manager_v2.py: _process_single_page()
 
-if pdf_type == 'ocr':  # 条件①:仅扫描件
-    detection_image, angle = self.preprocessor.process(original_image)
-
-# mineru_adapter.py: MinerUPreprocessor.process()
-
-processor = WatermarkProcessor.from_user_config(wm_cfg, scope="page")
-if processor.enabled:
-    image, _ = processor.process(image)  # 内部 detect_before_remove + method
+# 页级水印去除在 prepare_detection_image 中执行
+# preprocessor 从 config[preprocessor][watermark_removal] 读取配置
+detection_image, rotate_angle = self.preprocessor.prepare_detection_image(
+    original_image.copy(),
+    pdf_rotate_angle=pdf_rotate_angle,
+    use_orientation_classifier=pdf_type == 'ocr',  # 仅扫描件走方向分类
+)
 ```
 
 **触发条件**:
-1. PDF 类型为 `ocr`(扫描件)
-2. `preprocessor.watermark_removal.enabled: true`
+1. `preprocessor.watermark_removal.enabled: true`
+2. preprocessor 内部执行像素级去水印(`method: threshold` 等)
+
+> **当前推荐**:对银行流水场景,阶段二页级水印 **默认关闭**(`enabled: false`),因为图片 PDF 的像素阈值去除效果不佳,文字 PDF 的水印已在阶段一清除。格级去水印在二次 OCR 时独立控制。
 
 **格级二次 OCR**(`text_filling.py`):表体触发二次 OCR 时,对 `raw_crop` 调用 `_preprocess_cell_for_ocr` → `WatermarkProcessor(scope="cell")`。
 
@@ -623,7 +647,7 @@ python main_v2.py -i doc.pdf -c config.yaml --scene bank_statement --debug
 
 ## 注意事项
 
-1. **三层互补**:PDF 层级、页级、格级可独立开关;银行流水推荐 **cell-first**(页级 wm 关、格级 wm 开)。
+1. **三种机制各自独立**:PDF XObject 清理(无阈值)、页级像素去水印(有阈值)、格级去水印(有阈值)可独立开关。银行流水推荐 **PDF XObject + 格级**(页级 wm 关)。
 2. **灰度方向**:**0=黑、255=白**;`gray > threshold → 255` 表示把「比阈值更亮」的像素刷白。
 3. **threshold 方向**:**调高**更保守(易留水印、少伤字);**调低**更激进(背景更干净、易啃淡笔画)。页级与格级应分别调参。
 4. **勿混用 det 阈值**:`ocr_recognition.det_threshold` 是 OCR 检测框过滤,与去水印 `threshold` 无关。