# 🧪 实验 Lab 水印去除/对比度增强/单元格预处理实验模块,统一归集到 `ocr_tools/lab/` 目录下。 包含 LaMa GAN inpainting、掩膜参数扫描、对比度增强扫描、单元格预处理 OCR 扫描。 ## Models - [LaMa-Inpainting](https://github.com/advimman/lama.git) - [BrushNet](https://github.com/TencentARC/BrushNet) ## 模型下载地址:https://drive.google.com/drive/folders/1B2x7eQDgecTL0oh3LSIBDGj0fTxs6Ips?usp=sharing --- ## LaMa 本地权重调用方式(不下载) 当前脚本已支持直接读取本地权重文件,不再依赖运行时下载。 - 默认权重路径:`/Users/zhch158/models/big-lama/models/best.ckpt` - 默认会自动推断配置文件:`/Users/zhch158/models/big-lama/config.yaml` ### 运行方式 先激活环境: ```bash conda activate mineru ``` 执行评估(使用默认本地权重): ```bash cd ocr_platform/ocr_tools/lab/gan_experiments_lab python evaluate.py ``` 或使用非交互方式: ```bash conda run -n mineru python ocr_tools/lab/gan_experiments_lab/evaluate.py ``` ### 可选参数 - `--lama-ckpt`:指定 `best.ckpt` 路径 - `--lama-config`:指定 `config.yaml` 路径(默认按 ckpt 自动推断) - `--lama-repo`:指定 LaMa 仓库路径(用于导入 `saicinpainting`;默认自动探测) 示例: ```bash conda run -n mineru python ocr_tools/lab/gan_experiments_lab/evaluate.py \ --lama-ckpt /Users/zhch158/models/big-lama/models/best.ckpt \ --lama-config /Users/zhch158/models/big-lama/config.yaml \ --lama-repo /path/to/lama conda run -n mineru python ocr_tools/lab/gan_experiments_lab/evaluate.py \ --lama-repo /Users/zhch158/workspace/repository.git/lama ``` --- ## 水印去除原理 LaMa、BrushNet 等 GAN inpainting 模型需要一张 **水印位置掩码图(watermark mask)** 作为输入,模型在 mask 标出的区域进行修复(inpainting)。 对于银行流水等文档,流程为: ``` 原图 → 水印检测 (build_watermark_mask) → 掩码图 → LaMa/BrushNet 修复 → 去水印结果 ``` ### 关键难点 - **浅色斜向水印**与正文笔划在灰度上重叠,传统图像学方法难以精确分离 - 掩码过大 → 伤到正文;掩码过小 → 水印去不干净 - 水印盖在正文上时,GAN 修复可能改变底层数字/文字 --- ## 水印掩膜实验(watermark_lab) 针对银行流水等场景的水印 mask 生成参数扫描,支持三种策略: | 策略 | 说明 | 适用场景 | |---|---|---| | `light_on_white` | Hough 斜向检测 + 浅色带提取 | 斜向文字水印(默认) | | `diagonal_midtone` | 中间调 + 斜向形态学 | 通用水印 | | `fused` | 以上两种 + 背景差异残差 OR 融合 | 多类型水印混合 | ### 快速扫描 ```bash cd ocr_platform conda run -n mineru python ocr_tools/lab/watermark_lab/watermark_sweep.py \ ocr_tools/lab/gan_experiments_lab/test_images/input/彭_广东兴宁农村商业银行_page_002.png \ --quick ``` ### 批量扫描 ```bash conda run -n mineru python ocr_tools/lab/watermark_lab/watermark_sweep.py \ ocr_tools/lab/gan_experiments_lab/test_images/input/ \ --lama-ckpt /Users/zhch158/models/big-lama/models/best.ckpt ``` ### 输出产物 ``` output/<图片名>/ ├── sweep_report.json # 扫描结果汇总(含各策略 ratio、排序) ├── sweep_summary.csv # CSV 汇总表 ├── light_on_white_*.png # light_on_white 策略各参数 overlay 图 ├── fused_*.png # 融合策略各参数 overlay 图 ├── diagonal_midtone_*.png # diagonal_midtone 策略 overlay 图 └── *_inpainted.png # (可选)LaMa 修复结果图 ``` ### 实验结果(银行流水样例) **测试图**:`彭_广东兴宁农村商业银行_page_002.png`(带斜向半透明水印) | 策略 | direction_filter | mask 覆盖率 | 说明 | |---|---|---|---| | `light_on_white` | `hough` | **10.8%** | Hough 斜向检测决定几何区域,是主力策略 | | `light_on_white` | `none` | **0%** | 无几何约束时 mask 为空 | | `diagonal_midtone` | — | **3.2%** | 中间调+斜向形态学,补充覆盖 | | `background_diff` | — | **0.5~0.7%** | 中值滤波背景差异残差,贡献极小 | | **fused** | `hough` | **11.3~11.5%** | 三策略 OR 融合,比纯 light_on_white 多 ~0.5% | **结论**:对于银行流水斜向水印,`light_on_white + direction_filter=hough` 是主力策略。 `fused` 融合在基础之上加入 `diagonal_midtone` 的 3.2% 补充,使整体 mask 增加到 11.3~11.5%。 但水印与正文重叠区域仍无法通过纯图像学方法精确分离。 --- ## 对比度增强实验(contrast_sweep) **核心思路**:不去水印,直接对原图做多种对比度增强,让水印在视觉上"淡化", 正文保持清晰,使后续 OCR 不受水印干扰。 支持四种增强方法: | 方法 | 说明 | 关键参数 | |---|---|---| | `text_restore` | 仅非背景像素动态范围压缩 | `text_black_target`(40~120), `background_threshold`(235~252) | | `clahe` | 自适应直方图均衡(局部) | `clip_limit`(0.5~8.0), `tile_grid_size`(4~32) | | `gamma` | Gamma 校正(<1 加深文字) | `gamma`(0.3~0.9) | | `linear` | 分位线性拉伸 | `black_percentile`(1~8), `white_percentile`(92~98) | ### 快速扫描 ```bash cd ocr_platform conda run -n mineru python ocr_tools/lab/watermark_lab/contrast_sweep.py \ ocr_tools/lab/gan_experiments_lab/test_images/input/彭_广东兴宁农村商业银行_page_002.png \ --quick ``` ### 全量扫描 + OCR 验证 ```bash conda run -n mineru python ocr_tools/lab/watermark_lab/contrast_sweep.py \ ocr_tools/lab/gan_experiments_lab/test_images/input/ \ --ocr --model-dir /path/to/paddleocr_models ``` ### 输出产物 ``` output/<图片名>/ ├── contrast_report.json # 扫描结果汇总(含 fade/sharpness/combined 评分) ├── contrast_summary.csv # CSV 汇总表 ├── quad_compare.png # 四宫格对比图(原图 + 各方法最优) ├── clahe_cl*.png # CLAHE 各参数增强结果 ├── text_restore_t*.png # text_restore 各参数结果 ├── gamma_g*.png # Gamma 各参数结果 └── linear_b*.png # Linear 各参数结果 ``` ### 评分指标 | 指标 | 含义 | 越大越好 | |---|---|---| | `fade_score` | 水印淡化程度:增强前后背景残差方差变化率 | 正数=水印变淡 | | `sharpness_score` | 文字清晰度:局部标准差均值 | ✓ | | `combined_score` | 综合分 = fade×0.5 + sharpness(归一化)×0.5 | ✓ | ### 实验结果(银行流水样例) **测试图**:`彭_广东兴宁农村商业银行_page_002.png`(带斜向半透明水印,20 组参数) | 排名 | 方法 | 参数 | fade | sharpness | combined | |---|---|---|---|---|---| | **1** | **clahe** | clip=1.0, tile=16 | 0.009 | 13.42 | **0.504** | | 2 | clahe | clip=0.5, tile=8 | 0.009 | 13.40 | 0.504 | | 3 | gamma | g=0.85 | -0.253 | 20.49 | 0.499 | | 4 | linear | bp=2.0, wp=95.0 | -0.163 | 17.06 | 0.426 | | 5 | text_restore | t=85, bg=240 | -0.769 | 17.09 | 0.116 | **关键发现**: 1. **CLAHE(clip_limit=1.0, tile_grid_size=16)是最优方案**,水印被局部均衡化"摊平"几乎消失,文字保持清晰 2. `text_restore` 对银行流水有害(fade 为负):它把水印像素当成"要恢复的文字"一并压深,水印反而更显眼 3. `gamma=0.85` 作为备选简单有效,整体对比度提升但水印淡化程度不如 CLAHE 4. CLAHE 仅需 **2.7ms**,比 text_restore 快 4 倍 **推荐生产配置**: ```python from ocr_utils.watermark.contrast import enhance_document_contrast enhanced = enhance_document_contrast( gray, method="clahe", clip_limit=1.0, # 不要过大,否则水印也被增强 tile_grid_size=16, # 大块更平滑,水印淡化更均匀 ) ``` **结论:对银行流水类文档,CLAHE 对比度增强替代去水印流程更干净、更快、更简单。** --- ## 目录结构 ``` ocr_tools/lab/ │ ├── README.md # 本文件 │ ├── gan_experiments_lab/ # LaMa GAN inpainting 评估 │ ├── evaluate.py # 去水印评估(baseline vs GAN) │ ├── lama_inpaint.py # LaMa 推理封装 │ ├── watermark_synthesis.py # 水印合成脚本 │ ├── test_images/ # 测试图片 │ │ ├── input/ # 原图(带水印) │ │ └── clean/ # 无印参考图(用于 PSNR/SSIM) │ └── output/ # 评估输出 │ ├── compare/ # 三联对比图 │ ├── inpainted/ # 修复结果 │ ├── mask_debug/ # 掩膜可视化 │ └── metrics/ # 评估指标 JSON │ ├── watermark_lab/ # 水印实验(掩膜扫描 + 对比度扫描) │ ├── fused_mask.py # 三策略融合水印掩膜 │ ├── watermark_sweep.py # 掩膜参数网格扫描 │ ├── contrast_sweep.py # 对比度增强参数网格扫描 │ └── output/ # 实验输出 │ └── cell_preprocess_lab/ # 单元格预处理实验 ├── cell_preprocess_lab.py # 单元格预处理主脚本 ├── cell_sweep.py # 单元格预处理 + OCR 参数扫描 └── output/ # 实验输出 ``` **生产配置对齐**:`bank_statement_yusys_local.yaml` 的 `table_recognition.second_pass_ocr.cell_preprocess` 已与 cell_sweep 最优参数对齐(Pass1:`threshold=150` + CLAHE `cl=1.0, tile=8` + `upscale_min_side=128`;Pass2 `enhance_retry`:CLAHE `tile=4`)。 --- # 多种提高清晰度方法说明 ## CLAHE(自适应直方图均衡) ### 原理 CLAHE 是 AHE(自适应直方图均衡)的改进版。核心思想是: 1. **分块**:把图像切成 `tile_grid_size × tile_grid_size` 像素的小块 2. **局部均衡**:每个小块内独立做直方图均衡化——让局部灰度分布更均匀 3. **对比度限制**:直方图统计时,"削平"过高的柱子(超过 `clip_limit` 的部分被裁掉并均匀分给所有灰度级),防止局部噪声被过度放大 4. **双线性插值**:块边界处像素用相邻四个块的映射函数做双线性插值,消除块效应 **为什么对水印有效**:水印是覆盖在全图上的均匀半透明层,在局部窗口(例如 16×16 像素)内,它表现为"整体灰度偏高一点点"而非"纹理"。CLAHE 的局部均衡化把每个窗口的灰度动态范围拉满后,水印的这种"整体偏移"就被摊平了,而文字作为局部高频信息被保留甚至增强。 ### 参数 | 参数 | 含义 | 取值范围 | 效果 | |---|---|---|---| | `clip_limit` | 对比度剪切阈值 | 0.5~8.0(默认2.0) | **越小**→输出越均匀、噪声抑制越强、水印淡化越好;**越大**→局部对比度越强、细节突出但可能引入噪声 | | `tile_grid_size` | 分块大小(像素) | 4~32(默认8) | **越小**→更"局部"、细小文字更清晰;**越大**→更"全局"、更平滑、水印淡化更均匀 | 在你实验的最优参数 `clip_limit=1.0, tile=16` 中:低 `clip` 强力压制了水印纹理想被放大的趋势,大 `tile` 让过渡更平滑。 --- ## Gamma 校正 ### 原理 对图像做幂律变换: ``` output = 255 × (input / 255)^(1/γ) ``` 这是一个**像素级别的非线性映射**,查表(LUT)实现,速度快。 ``` gamma < 1 → 中间调整体提亮(向右拉伸),深色笔划变浅 → 去水印后恢复用 gamma > 1 → 中间调整体压暗(向左压缩),浅色水印可能变深 灰度映射曲线(gamma < 1): 255| ╱ | ╱ | ╱╱ 0 |______╱ 0 128 255 ``` 你们代码里的实现: ```94:101:ocr_tools/lab/../../ocr_utils/watermark/contrast.py if method == "gamma": gamma = max(0.1, min(float(gamma), 3.0)) inv_gamma = 1.0 / gamma table = np.array( [((i / 255.0) ** inv_gamma) * 255 for i in range(256)], dtype=np.uint8, ) return cv2.LUT(gray, table) ``` ### 参数 | 参数 | 含义 | 你用过的范围 | 效果 | |---|---|---|---| | `gamma` | 校正指数 | 0.3~0.9(默认0.85) | **<1**→提亮中间调(深色变浅);**>1**→压暗中间调(浅色变深) | 你的实验里 `gamma=0.85` 排第三(combined=0.499),因为提亮效果让文字轻微变浅,所以 sharpness 高但 fade 为负(水印反而更明显了)。 --- ## Linear(分位线性拉伸) ### 原理 统计全图灰度的 `black_percentile` 分位值(如 2% 分位值=40)和 `white_percentile` 分位值(如 98% 分位值=245),然后把 `[p_low, p_high]` 这个区间线性映射到 `[0, 255]`: ``` output = (input - p_low) × 255 / (p_high - p_low) ``` 代码: ```103:109:ocr_tools/lab/../../ocr_utils/watermark/contrast.py if method == "linear": p_low = float(np.percentile(gray, black_percentile)) p_high = float(np.percentile(gray, white_percentile)) if p_high <= p_low + 1.0: return gray stretched = (gray.astype(np.float32) - p_low) * 255.0 / (p_high - p_low) return np.clip(stretched, 0, 255).astype(np.uint8) ``` ### 参数 | 参数 | 含义 | 取值范围 | 效果 | |---|---|---|---| | `black_percentile` | 黑色锚点分位 | 1%~8%(默认2%) | **越大**→更多暗部被裁掉、整体更亮 | | `white_percentile` | 白色锚点分位 | 92%~98%(默认98%) | **越小**→更多亮部被裁掉、整体更暗 | **缺点**:这是全局操作,水印和正文同时被线性拉伸,水印不会单独被淡化。你的实验结果也印证了这点(fade 为负)。 --- ## Text Restore(去水印后专用修复) ### 原理 专门为**去水印后文字变浅**的场景设计。核心逻辑: 1. **背景保护**:`gray >= background_threshold`(默认 248)的白色背景像素**不动**,避免白底变灰 2. **文字区域**:`gray < background_threshold` 的非背景像素做动态范围压缩 3. **压缩公式**:取文字区域像素的 `[text_lo%, text_hi%]` 分位区间,线性映射到 `[0, text_black_target]` ```40:42:ocr_tools/lab/../../ocr_utils/watermark/contrast.py stretched = (vals - lo) * target / (hi - lo) result[text_mask] = np.clip(stretched, 0, 255).astype(np.uint8) return result ``` **对银行流水为什么有害**(fade=-0.769):水印的灰色像素(灰度 100~220)也落在 `gray < background_threshold` 范围内,被当成"需要恢复的文字"一起往深色(target=40~85)拉伸,结果水印反而变得更黑了。 ### 参数 | 参数 | 含义 | 取值范围 | 效果 | |---|---|---|---| | `text_black_target` | 最深文字目标灰度 | 10~200(默认85) | **越小**→文字越黑、水印也跟着变黑 | | `background_threshold` | 背景判定阈值 | 200~255(默认248) | 大于等于此值的像素不动;**越低**→更多像素参与压缩 | | `text_lo_percentile` | 文字区下分位 | 1%~5%(默认1%) | 排除极暗的异常像素 | | `text_hi_percentile` | 文字区上分位 | 95%~99%(默认99%) | 排除极亮的异常像素 | --- ## 三个评测指标 ### fade_score(水印淡化程度) ```150:168:ocr_tools/lab/watermark_lab/contrast_sweep.py def _compute_watermark_fade_score( original: np.ndarray, enhanced: np.ndarray, window: int = 31 ) -> float: o_f = original.astype(np.float32) e_f = enhanced.astype(np.float32) k = max(3, window) | 1 o_bg = cv2.medianBlur(o_f.astype(np.uint8), k).astype(np.float32) e_bg = cv2.medianBlur(e_f.astype(np.uint8), k).astype(np.float32) o_res = cv2.absdiff(o_f, o_bg) e_res = cv2.absdiff(e_f, e_bg) return float(1.0 - np.var(e_res) / max(np.var(o_res), 1.0)) ``` **计算原理**: 1. 用 31×31 大核中值滤波估计**背景**(抹掉文字,保留水印纹理和整体亮度) 2. 计算残差 `|原图 - 背景|`:残差大 = 水印纹理明显(波动剧烈) 3. 计算增强前后残差的方差比 ``` fade = 1 - var(增强后的残差) / var(原始残差) ``` - **正值**(接近 1)→ 增强后残差方差变小很多 → 水印纹理解除了 → ✅ - **接近 0** → 增强前后差不多 - **负值** → 增强后残差反而更剧烈 → 水印被加强了 → ❌ CLAHE 得到 0.009 的正值,说明水印纹理被轻微减弱。text_restore 得到 -0.769,说明它反而把水印纹理放大了。 ### sharpness_score(文字清晰度) ```171:179:ocr_tools/lab/watermark_lab/contrast_sweep.py def _compute_text_sharpness_score( enhanced: np.ndarray, win: int = 3 ) -> float: """局部标准差均值,越大 = 文字越清晰。""" e_f = enhanced.astype(np.float32) kernel = np.ones((win, win), np.float32) / (win * win) mean = cv2.filter2D(e_f, -1, kernel) sq_mean = cv2.filter2D(e_f * e_f, -1, kernel) var = np.maximum(sq_mean - mean * mean, 0) return float(np.sqrt(var).mean()) ``` **计算原理**:用 3×3 窗口计算每个像素的局部标准差(= 局部方差开根号),再取全图均值。 - **高值**→ 像素之间的差异大 → 文字和背景分得开 → 文字清晰 ✅ - **低值**→ 图像比较"平"→ 文字可能模糊 CLAHE=13.42 的 sharpness 在正常范围内(不过不影响 OCR 识别)。注意这个值不能直接跨方法比较——gamma=20.49 是因为提亮让文字区像素差变大,而非 OCR 识别率更高。 ### combined_score(综合得分) ```python combined = fade * 0.5 + (sharpness / max_sharpness) * 0.5 ``` - `fade`:原始值(可能为负) - `sharpness`:归一化到 [0, 1](除以本轮扫描中最大的 sharpness) - 各占 50% 权重 **设计意图**:在水印淡化和文字清晰度之间找平衡。实际使用中,如果你更关注水印去除效果,可以调整权重偏向 fade。