Forráskód Böngészése

feat(新增水印去除策略): 添加两层独立的水印去除能力,支持PDF层级和图像级水印处理

zhch158_admin 6 napja
szülő
commit
5bb909781f

+ 153 - 2
docs/ocr_tools/universal_doc_parser/模型统一框架.md

@@ -645,6 +645,112 @@ def process_text_element(
 
 ---
 
+## 水印去除策略 ⭐
+
+框架提供**两层独立的水印去除能力**,针对不同类型的文档进行优化:
+
+### 处理流程
+
+```mermaid
+graph TB
+    A[输入文档] --> B{是否为 PDF?}
+    
+    B -->|是| C[阶段一: PDF 层级去水印]
+    B -->|否| F
+    
+    C --> D{启用 txt_pdf_watermark_removal?}
+    D -->|是| E[扫描前 N 页检测水印 XObject]
+    D -->|否| G
+    E --> E1{发现水印?}
+    E1 -->|是| E2[清除 XObject 内容流]
+    E1 -->|否| G
+    E2 --> G
+    
+    G[渲染为图像] --> H{PDF 类型?}
+    H -->|文字型 txt| I[跳过阶段二]
+    H -->|扫描件 ocr| J
+    
+    F[图像输入] --> J[阶段二: 图像级去水印]
+    
+    J --> K{启用 watermark_removal?}
+    K -->|是| L[阈值化去除浅色水印]
+    K -->|否| N
+    L --> N[方向校正]
+    
+    N --> O[Layout 检测]
+```
+
+### 阶段一:PDF 层级水印去除
+
+**适用场景**:文字型 PDF(`pdf_type='txt'`),水印以 XObject 形式叠加在文字层上方。
+
+**原理**:
+- 通过 PyMuPDF (fitz) 直接操作 PDF 内部结构
+- 检测 Form XObject(旋转变换/透明度组)和 Image XObject(半透明全页背景图)
+- 清空或替换水印 XObject 的内容流,**保留文字可搜索性**
+
+**水印 XObject 判断规则**:
+
+| 类型 | 判断规则 |
+|------|---------|
+| Form XObject | 1. 含旋转变换矩阵(45° 斜向)<br/>2. 有透明度组 + 透明操作符(ca/CA)<br/>3. 有透明度组 + 大体积流(>2KB) |
+| Image XObject | 1. 有 `/SMask`(半透明)<br/>2. 全页尺寸(≥600×800)<br/>3. 像素均值 ≥ 240(近乎全白) |
+
+### 阶段二:图像级水印去除
+
+**适用场景**:扫描件/图片(`pdf_type='ocr'`),无法从 PDF 内部结构处理。
+
+**原理**:
+- 银行流水水印特征:浅色(灰度 160-220)、斜向(45°)、文字稀疏
+- **阈值化处理**:将灰度值高于阈值的像素置为白色,保留深色正文
+- 可选斜向纹理验证(Hough 直线变换)避免误判
+
+### 两阶段对比
+
+| 维度 | 阶段一(PDF 层级) | 阶段二(图像级) |
+|------|------------------|-----------------|
+| **处理对象** | 文字型 PDF | 扫描件/图片 |
+| **处理层级** | PDF XObject | 图像像素 |
+| **保留文字可搜索性** | ✅ 是 | ❌ 否 |
+| **无损处理** | ✅ 是 | ❌ 否 |
+| **处理时机** | 渲染前 | 渲染后、检测前 |
+
+### 配置示例
+
+```yaml
+# 输入配置 - PDF 层级去水印
+input:
+  dpi: 200
+  txt_pdf_watermark_removal:
+    enabled: true        # 是否启用 PDF 层级去水印
+    sample_pages: 3      # 快速预扫描页数(避免大文件全量扫描)
+
+# 预处理配置 - 图像级去水印
+preprocessor:
+  module: "mineru"
+  orientation_classifier:
+    enabled: true
+  watermark_removal:
+    enabled: true           # 是否启用图像级去水印
+    threshold: 160          # 灰度阈值(140-180),越大越保守
+    morph_close_kernel: 0   # 形态学核大小,建议 0
+```
+
+### 触发条件
+
+**阶段一触发**:
+1. 文件是 PDF
+2. `input.txt_pdf_watermark_removal.enabled: true`
+3. 扫描发现水印 XObject
+
+**阶段二触发**:
+1. PDF 类型为 `ocr`(扫描件)
+2. `preprocessor.watermark_removal.enabled: true`
+
+详细说明请参考:`水印去除技术文档.md`
+
+---
+
 ## Layout 后处理
 
 ### 大面积文本块转表格
@@ -821,6 +927,11 @@ universal_doc_parser/
   ├── test_skew_correction.py # 倾斜矫正测试
   ├── test_table_routing.py # 表格路由测试
   └── *.png # 测试图片文件
+
+├── ocr_utils/ # 共享工具模块(跨项目复用)⭐
+│ ├── watermark_utils.py # 水印检测与去除工具 ⭐ 新增
+│ ├── output_formatter.py # 统一输出格式化器
+│ └── ...
 ```
 
 ---
@@ -955,12 +1066,23 @@ scene_name: "bank_statement"
 input:
   supported_formats: [".pdf", ".png", ".jpg", ".jpeg"]
   dpi: 200  # PDF 转图片的 DPI
+  
+  # PDF 层级水印去除 ⭐ 新增
+  txt_pdf_watermark_removal:
+    enabled: true        # 是否启用 PDF 层级去水印
+    sample_pages: 3      # 快速预扫描页数
 
-# 预处理(方向识别)
+# 预处理(方向识别 + 水印去除
 preprocessor:
   module: "mineru"
   orientation_classifier:
     enabled: true  # 扫描件自动开启
+  
+  # 图像级水印去除 ⭐ 新增
+  watermark_removal:
+    enabled: true           # 是否启用图像级去水印
+    threshold: 160          # 灰度阈值(140-180)
+    morph_close_kernel: 0   # 形态学核大小,建议 0
 
 # ============================================================
 # Layout 检测配置 - 智能路由器(按场景直接选择模型)⭐ 新增
@@ -1306,6 +1428,7 @@ vl_recognition:
 
 主流水线管理器,实现完整处理流程:
 - PDF 分类(扫描件/数字原生)
+- **水印去除**(PDF 层级 + 图像级)⭐ 新增
 - 页面方向识别
 - Layout 检测与去重
 - **整页 OCR + Span-Block 匹配** ⭐
@@ -1314,7 +1437,25 @@ vl_recognition:
 - 阅读顺序排序
 - 坐标转换
 
-### 2. SpanMatcher (`layout_utils.py`)
+### 2. WatermarkUtils (`ocr_utils/watermark_utils.py`) ⭐ 新增
+
+水印检测与去除工具模块,提供两层独立的水印去除能力:
+
+**图像级水印处理**:
+- `detect_watermark()` - 检测图像中的浅色斜向文字水印
+- `remove_watermark_from_image()` - 去除水印,返回灰度图
+- `remove_watermark_from_image_rgb()` - 去除水印,返回 RGB 图(适合模型输入)
+
+**PDF 层级水印处理**:
+- `scan_pdf_watermark_xobjs()` - 快速扫描 PDF 是否含水印 XObject(无副作用)
+- `remove_txt_pdf_watermark()` - 从内存 PDF bytes 去除水印,返回新 bytes 或 None
+
+**内部函数**:
+- `_is_watermark_xobj()` - 判断 Form XObject 是否为水印
+- `_is_watermark_image_xobj()` - 判断 Image XObject 是否为水印背景图
+- `_blank_watermark_image()` - 将水印图替换为全白像素
+
+### 3. SpanMatcher (`layout_utils.py`)
 
 OCR Span 与 Layout Block 匹配器,参考 MinerU 实现:
 - `match_spans_to_blocks()` - 将 spans 匹配到对应的 blocks
@@ -1486,6 +1627,7 @@ from ocr_tools.ocr_merger import TableCellMatcher, TextMatcher
 | `normalize_financial_numbers` | 金额标准化 | 金融数字标准化(金额、百分比等) |
 | `PDFUtils` | PDF 处理工具 | PDF 文本提取、坐标转换等 |
 | `BBoxExtractor` | 边界框提取器 | 从 HTML 等格式提取边界框 |
+| `watermark_utils` | 水印处理工具 ⭐ 新增 | 水印检测与去除(PDF 层级 + 图像级) |
 | `file_utils` | 文件处理工具 | 文件列表处理、PDF 转图片等 |
 | `image_utils` | 图像处理工具 | 图像解码、预处理、坐标转换等 |
 | `html_utils` | HTML 处理工具 | HTML 表格解析、图片处理等 |
@@ -1503,6 +1645,15 @@ from ocr_utils import (
     normalize_financial_numbers, # 金额标准化
 )
 
+# 水印处理工具 ⭐ 新增
+from ocr_utils.watermark_utils import (
+    detect_watermark,              # 检测图像水印
+    remove_watermark_from_image,   # 图像级去水印
+    remove_watermark_from_image_rgb,
+    scan_pdf_watermark_xobjs,      # 扫描 PDF 水印
+    remove_txt_pdf_watermark,      # PDF 层级去水印
+)
+
 # PDF 工具(延迟导入)
 from ocr_utils import PDFUtils, BBoxExtractor
 

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

@@ -0,0 +1,491 @@
+# 水印去除技术文档
+
+## 概述
+
+水印去除模块 (`ocr_utils/watermark_utils.py`) 提供了**两层独立的水印去除能力**,针对不同类型的文档和场景进行优化:
+
+| 层级 | 处理对象 | 适用场景 | 特点 |
+|------|---------|---------|------|
+| **PDF 层级** | 文字型 PDF 的 XObject | 银行流水等文字型 PDF | 保留文字可搜索性,无损处理 |
+| **图像层级** | 扫描件/渲染图像的像素 | 扫描件、图片 | 像素级处理,适用于 OCR 前预处理 |
+
+---
+
+## 处理流程
+
+```mermaid
+graph TB
+    A[输入文档] --> B{是否为 PDF?}
+    
+    B -->|是| C[阶段一: PDF 层级去水印]
+    B -->|否| F
+    
+    C --> D{启用 txt_pdf_watermark_removal?}
+    D -->|是| E[扫描前 N 页检测水印 XObject]
+    D -->|否| G
+    E --> E1{发现水印?}
+    E1 -->|是| E2[清除 XObject 内容流]
+    E1 -->|否| G
+    E2 --> G
+    
+    G[渲染为图像] --> H{PDF 类型?}
+    H -->|文字型 txt| I[跳过阶段二]
+    H -->|扫描件 ocr| J
+    
+    F[图像输入] --> J[阶段二: 图像级去水印]
+    
+    J --> K{启用 watermark_removal?}
+    K -->|是| L[检测浅色斜向水印]
+    K -->|否| N
+    L --> M[阈值化去除水印]
+    M --> N[方向校正]
+    
+    N --> O[Layout 检测]
+    O --> P[OCR 识别]
+    
+    style C fill:#e1f5ff
+    style E fill:#e1f5ff
+    style E2 fill:#e1f5ff
+    style J fill:#fff4e1
+    style L fill:#fff4e1
+    style M fill:#fff4e1
+```
+
+---
+
+## 阶段一:PDF 层级水印去除
+
+### 适用场景
+
+**文字型 PDF(`pdf_type='txt'`)**:PDF 内部包含可提取的文字层,水印通常以 XObject 形式叠加在文字上方。
+
+### 原理
+
+PDF 文件中的水印通常通过以下两种 XObject 实现:
+
+1. **Form XObject**:矢量绘图对象,包含旋转、透明度等变换矩阵
+2. **Image XObject**:位图对象,通常是半透明的全页背景图
+
+通过 PyMuPDF (fitz) 直接操作 PDF 内部结构,**清空或替换水印 XObject 的内容流**,而不影响文字层的可搜索性。
+
+### 水印 XObject 判断规则
+
+#### Form XObject 水印判断 (`_is_watermark_xobj`)
+
+满足以下条件之一即判定为水印:
+
+| 规则 | 说明 | 原理 |
+|------|------|------|
+| 旋转变换 | 内容流中 `cm` 指令的 sin/cos 分量非零 | 水印通常斜向 45° 放置 |
+| 透明度组 + 透明操作符 | `/Group` 存在且内容流含 `ca/CA` | 水印具有半透明效果 |
+| 透明度组 + 大体积流 | `/Group` 存在且流体积 > 2KB | 大量重复绘图 = 平铺水印 |
+
+```python
+# 判断逻辑伪代码
+def _is_watermark_xobj(doc, xref, obj_str):
+    if "/Form" not in obj_str:
+        return False
+    
+    stream_text = doc.xref_stream(xref).decode("latin-1")
+    
+    # 规则1:旋转变换
+    if has_rotation_transform(stream_text):
+        return True
+    
+    # 规则2-3:透明度组相关
+    if "/Group" in obj_str:
+        if has_transparency_operators(stream_text):
+            return True
+        if len(stream_text) > 2048:
+            return True
+    
+    return False
+```
+
+#### Image XObject 水印判断 (`_is_watermark_image_xobj`)
+
+必须同时满足以下条件:
+
+| 条件 | 说明 |
+|------|------|
+| `/Subtype /Image` | 确认是图像对象 |
+| 存在 `/SMask` | 有透明通道(半透明) |
+| 宽 >= 600 且 高 >= 800 | 全页尺寸(排除小图标) |
+| 像素均值 >= 240 | 近乎全白(水印文字稀疏) |
+
+### 处理方法
+
+```python
+def remove_txt_pdf_watermark(pdf_bytes: bytes) -> Optional[bytes]:
+    """
+    对文字型 PDF 执行原生水印去除
+    
+    处理方式:
+    - Form XObject:清空内容流 (update_stream(b""))
+    - Image XObject:替换为全白像素 + 移除 DecodeParms
+    
+    Returns:
+        去水印后的 PDF bytes,若未发现水印返回 None
+    """
+    doc = fitz.open(stream=pdf_bytes, filetype="pdf")
+    
+    for page in doc:
+        # 处理 Form XObject 水印
+        for xref, name, *_ in page.get_xobjects():
+            if _is_watermark_xobj(doc, xref, obj_str):
+                doc.update_stream(xref, b"")  # 清空内容流
+        
+        # 处理 Image XObject 水印
+        for img_tuple in page.get_images(full=True):
+            img_xref = img_tuple[0]
+            if _is_watermark_image_xobj(doc, img_xref, obj_str):
+                _blank_watermark_image(doc, img_xref)  # 替换为全白
+    
+    return doc.tobytes(garbage=4, deflate=True)
+```
+
+### 关键技术细节
+
+**移除 `/DecodeParms` 的必要性**:
+
+当 Image XObject 使用 Predictor 压缩时,必须先移除 `/DecodeParms` 再调用 `update_stream`,否则渲染器会尝试 Predictor 解码失败后回退原始数据,水印依然可见。
+
+```python
+def _blank_watermark_image(doc, img_xref):
+    # 关键:先移除 DecodeParms
+    doc.xref_set_key(img_xref, "DecodeParms", "null")
+    # 再更新为全白像素
+    doc.update_stream(img_xref, bytes([255]) * (w * h * channels))
+```
+
+### 快速预扫描 (`scan_pdf_watermark_xobjs`)
+
+对于大型 PDF(如财报),先执行只读扫描判断是否存在水印,避免不必要的全量处理:
+
+```python
+def scan_pdf_watermark_xobjs(pdf_bytes: bytes, sample_pages: int = 3) -> bool:
+    """
+    快速扫描前 N 页,判断是否含水印 XObject
+    
+    Args:
+        sample_pages: 扫描页数上限,默认 3(银行流水通常前几页有水印)
+    
+    Returns:
+        True 表示发现水印 XObject
+    """
+    doc = fitz.open(stream=pdf_bytes, filetype="pdf")
+    for i in range(min(sample_pages, len(doc))):
+        # 检查 Form XObject 和 Image XObject
+        ...
+    return False
+```
+
+---
+
+## 阶段二:图像级水印去除
+
+### 适用场景
+
+**扫描件/图片(`pdf_type='ocr'`)**:无法从 PDF 内部结构处理,只能对渲染后的图像进行像素级处理。
+
+### 原理
+
+银行流水等金融文档的水印特征:
+
+- **颜色浅**:灰度值通常在 160-220 之间(介于正文和背景之间)
+- **角度斜**:通常 45° 斜向排列
+- **文字稀疏**:水印文字占比较小
+
+基于这些特征,采用**阈值化处理**:将灰度值高于阈值的像素置为白色,保留深色正文。
+
+### 水印检测 (`detect_watermark`)
+
+采用两阶段检测策略:
+
+1. **中间调检测**:统计灰度在 100-220 之间的像素占比
+2. **斜向验证**:使用 Hough 直线变换验证是否存在斜向纹理
+
+```python
+def detect_watermark(image, midtone_low=100, midtone_high=220, ratio_threshold=0.03):
+    """
+    检测图像中是否存在浅色斜向文字水印
+    
+    步骤:
+    1. 提取中间调像素(100-220),计算占比
+    2. 若占比 > 3%,进行斜向验证
+    3. 使用 Canny 边缘检测 + Hough 直线变换
+    4. 统计 30-60° 斜向直线数量
+    """
+    gray = to_grayscale(image)
+    
+    # 步骤1:中间调检测
+    midtone_mask = (gray > midtone_low) & (gray < midtone_high)
+    if midtone_mask.sum() / gray.size < ratio_threshold:
+        return False
+    
+    # 步骤2:斜向验证
+    edges = cv2.Canny(midtone_mask, 50, 150)
+    lines = cv2.HoughLines(edges, rho=1, theta=np.pi/180, threshold=80)
+    
+    # 统计斜向(30-60°)直线
+    diagonal_count = count_diagonal_lines(lines, angle_range=(30, 60))
+    return diagonal_count >= 2
+```
+
+### 水印去除 (`remove_watermark_from_image`)
+
+```python
+def remove_watermark_from_image(image, threshold=160, morph_close_kernel=0):
+    """
+    去除图像中的浅色斜向文字水印
+    
+    原理:
+    - 正文为深黑色(灰度 < threshold)
+    - 水印为浅灰(灰度 > threshold)
+    - 将高于阈值的像素置为白色(255)
+    
+    Args:
+        threshold: 灰度阈值,建议 140-180,默认 160
+        morph_close_kernel: 形态学闭运算核,0 表示跳过
+    """
+    gray = to_grayscale(image)
+    
+    # 阈值化:保留深色正文
+    cleaned = gray.copy()
+    cleaned[gray > threshold] = 255
+    
+    # 可选:形态学闭运算填补字符断裂
+    if morph_close_kernel > 0:
+        kernel = np.ones((morph_close_kernel, morph_close_kernel), np.uint8)
+        cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel)
+    
+    return cleaned
+```
+
+### 参数说明
+
+| 参数 | 默认值 | 说明 | 调整建议 |
+|------|--------|------|---------|
+| `threshold` | 160 | 灰度阈值 | 140-180,越大越保守(可能残留水印) |
+| `morph_close_kernel` | 0 | 形态学核大小 | 非二值图建议设为 0(闭运算会适得其反) |
+
+---
+
+## 配置说明
+
+### 完整配置示例
+
+```yaml
+# 输入配置 - PDF 层级去水印
+input:
+  dpi: 200
+  txt_pdf_watermark_removal:
+    enabled: true        # 是否启用 PDF 层级去水印
+    sample_pages: 3      # 快速预扫描页数
+
+# 预处理配置 - 图像级去水印
+preprocessor:
+  module: "mineru"
+  orientation_classifier:
+    enabled: true
+  watermark_removal:
+    enabled: true           # 是否启用图像级去水印
+    threshold: 160          # 灰度阈值
+    morph_close_kernel: 0   # 形态学核大小(建议 0)
+```
+
+### 配置项详解
+
+| 配置路径 | 类型 | 默认值 | 说明 |
+|---------|------|--------|------|
+| `input.txt_pdf_watermark_removal.enabled` | bool | `false` | PDF 层级去水印开关 |
+| `input.txt_pdf_watermark_removal.sample_pages` | int | 3 | 预扫描页数 |
+| `preprocessor.watermark_removal.enabled` | bool | `false` | 图像级去水印开关 |
+| `preprocessor.watermark_removal.threshold` | int | 160 | 灰度阈值 |
+| `preprocessor.watermark_removal.morph_close_kernel` | int | 0 | 形态学核大小 |
+
+**注意**:两个配置均无默认值,必须在 YAML 中显式配置 `enabled: true` 才会触发。
+
+---
+
+## 触发条件
+
+### 阶段一触发条件
+
+```python
+# pipeline_manager_v2.py: process_document()
+
+if is_pdf:
+    wm_cfg = config.get('input', {}).get('txt_pdf_watermark_removal', {})
+    if wm_cfg.get('enabled', False):                    # 条件①
+        if scan_pdf_watermark_xobjs(pdf_bytes, sample_pages=3):  # 条件②
+            cleaned = remove_txt_pdf_watermark(pdf_bytes)
+```
+
+**触发条件**:
+1. 文件是 PDF
+2. `enabled: true`
+3. 扫描发现水印 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()
+
+if config.get('watermark_removal', {}).get('enabled', False):  # 条件②
+    image = remove_watermark_from_image_rgb(image, threshold=160)
+```
+
+**触发条件**:
+1. PDF 类型为 `ocr`(扫描件)
+2. `preprocessor.watermark_removal.enabled: true`
+
+---
+
+## 两阶段对比
+
+| 维度 | 阶段一(PDF 层级) | 阶段二(图像级) |
+|------|------------------|-----------------|
+| **处理对象** | 文字型 PDF | 扫描件/图片 |
+| **处理层级** | PDF XObject | 图像像素 |
+| **保留文字可搜索性** | ✅ 是 | ❌ 否 |
+| **无损处理** | ✅ 是 | ❌ 否(像素修改) |
+| **处理时机** | 渲染前 | 渲染后、检测前 |
+| **依赖库** | PyMuPDF (fitz) | OpenCV, NumPy |
+
+---
+
+## 代码集成
+
+### 流水线集成
+
+```python
+# pipeline_manager_v2.py
+
+from ocr_utils.watermark_utils import (
+    scan_pdf_watermark_xobjs,
+    remove_txt_pdf_watermark
+)
+
+class EnhancedDocPipeline:
+    def process_document(self, doc_path):
+        # 阶段一:PDF 层级去水印
+        _pdf_bytes_override = None
+        if is_pdf and config['input']['txt_pdf_watermark_removal']['enabled']:
+            raw_bytes = doc_path.read_bytes()
+            if scan_pdf_watermark_xobjs(raw_bytes):
+                _pdf_bytes_override = remove_txt_pdf_watermark(raw_bytes)
+        
+        # 渲染 PDF(使用去水印后的 bytes)
+        images, pdf_type, pdf_doc = PDFUtils.load_and_classify_document(
+            doc_path, pdf_bytes=_pdf_bytes_override
+        )
+        
+        # 逐页处理
+        for page_idx, original_image in enumerate(images):
+            # 阶段二:图像级去水印(在 preprocessor.process 中)
+            if pdf_type == 'ocr':
+                detection_image, angle = self.preprocessor.process(original_image)
+            
+            # Layout 检测、OCR...
+```
+
+### 预处理器集成
+
+```python
+# models/adapters/mineru_adapter.py
+
+from ocr_utils.watermark_utils import remove_watermark_from_image_rgb
+
+class MinerUPreprocessor:
+    def process(self, image):
+        # 图像级水印去除(在方向校正之前)
+        if self.config.get('watermark_removal', {}).get('enabled', False):
+            threshold = self.config.get('watermark_removal', {}).get('threshold', 160)
+            image = remove_watermark_from_image_rgb(image, threshold=threshold)
+        
+        # 方向校正
+        if self.orientation_classifier:
+            angle = self.orientation_classifier.predict(image)
+            image = self._apply_rotation(image, angle)
+        
+        return image, angle
+```
+
+---
+
+## 使用示例
+
+### 命令行
+
+```bash
+# 处理含水印的银行流水 PDF
+python main_v2.py -i bank_statement.pdf -c config/bank_statement_yusys_v4.yaml --scene bank_statement
+
+# 配置文件中已启用:
+# input.txt_pdf_watermark_removal.enabled: true
+# preprocessor.watermark_removal.enabled: true
+```
+
+### Python API
+
+```python
+from core.pipeline_manager_v2 import EnhancedDocPipeline
+
+# 使用包含水印去除配置的 YAML
+with EnhancedDocPipeline("config/bank_statement_yusys_v4.yaml") as pipeline:
+    results = pipeline.process_document("document.pdf")
+```
+
+---
+
+## 调试与验证
+
+### 日志输出
+
+```python
+# 阶段一日志
+logger.info(f"🧹 文字型 PDF 原生去水印完成({doc_path.name})")
+logger.debug(f"  [Form XObject] 清空水印 xref={xref}, name={name}")
+logger.debug(f"  [Image XObject] 替换水印图像 xref={img_xref}")
+
+# 阶段二日志
+logger.info(f"🧹 Watermark removed (threshold={threshold})")
+```
+
+### 可视化验证
+
+在 debug 模式下,可以通过对比去水印前后的图像来验证效果:
+
+```bash
+# 开启 debug 模式
+python main_v2.py -i doc.pdf -c config.yaml --scene bank_statement --debug
+
+# 输出文件:
+# {doc}_pdf_page_001.png  - 渲染后的页面图像(去水印后)
+# {doc}_page_001_layout.png - Layout 可视化
+```
+
+---
+
+## 注意事项
+
+1. **两个阶段是互补的**:阶段一处理文字型 PDF,阶段二处理扫描件,实际不会重复执行
+2. **阈值选择**:`threshold=160` 适用于大多数银行流水,如果误删浅色文字可适当提高
+3. **形态学运算**:`morph_close_kernel=0` 是推荐值,非二值图时闭运算可能引入噪声
+4. **大文件优化**:`sample_pages=3` 快速预扫描,避免对无水印的大文件全量处理
+5. **依赖要求**:PDF 层级去水印需要 `PyMuPDF`,图像级需要 `OpenCV`
+
+---
+
+## 参考资料
+
+- `ocr_utils/watermark_utils.py` - 水印工具函数实现
+- `core/pipeline_manager_v2.py` - 流水线集成
+- `models/adapters/mineru_adapter.py` - 预处理器集成
+- `config/bank_statement_*.yaml` - 配置示例