zhch158_admin ba79f0c7d9 feat(新增多个单元格原始图像文件): 新增多个空白和带线条的单元格原始图像文件,以支持后续的OCR处理和实验。 12 годин тому
..
cell_preprocess_lab ba79f0c7d9 feat(新增多个单元格原始图像文件): 新增多个空白和带线条的单元格原始图像文件,以支持后续的OCR处理和实验。 12 годин тому
gan_experiments_lab 9780104eed feat(重构实验模块与单元格预处理功能): 删除cell_sweep.py,新增cell_preprocess_lab.py和cell_sweep.py,整合单元格裁剪图预处理与参数扫描功能,优化水印去除、对比度增强及放大处理,提升OCR处理的准确性与灵活性。同时新增实验模块集合与README文档,明确各子模块功能与用法。 2 днів тому
watermark_lab 9780104eed feat(重构实验模块与单元格预处理功能): 删除cell_sweep.py,新增cell_preprocess_lab.py和cell_sweep.py,整合单元格裁剪图预处理与参数扫描功能,优化水印去除、对比度增强及放大处理,提升OCR处理的准确性与灵活性。同时新增实验模块集合与README文档,明确各子模块功能与用法。 2 днів тому
README.md 9780104eed feat(重构实验模块与单元格预处理功能): 删除cell_sweep.py,新增cell_preprocess_lab.py和cell_sweep.py,整合单元格裁剪图预处理与参数扫描功能,优化水印去除、对比度增强及放大处理,提升OCR处理的准确性与灵活性。同时新增实验模块集合与README文档,明确各子模块功能与用法。 2 днів тому
__init__.py 9780104eed feat(重构实验模块与单元格预处理功能): 删除cell_sweep.py,新增cell_preprocess_lab.py和cell_sweep.py,整合单元格裁剪图预处理与参数扫描功能,优化水印去除、对比度增强及放大处理,提升OCR处理的准确性与灵活性。同时新增实验模块集合与README文档,明确各子模块功能与用法。 2 днів тому

README.md

🧪 实验 Lab

水印去除/对比度增强/单元格预处理实验模块,统一归集到 ocr_tools/lab/ 目录下。 包含 LaMa GAN inpainting、掩膜参数扫描、对比度增强扫描、单元格预处理 OCR 扫描。

Models

模型下载地址: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

运行方式

先激活环境:

conda activate mineru

执行评估(使用默认本地权重):

cd ocr_platform/ocr_tools/lab/gan_experiments_lab
python evaluate.py

或使用非交互方式:

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;默认自动探测)

示例:

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 融合 多类型水印混合

快速扫描

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

批量扫描

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)

快速扫描

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 验证

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 倍

推荐生产配置

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.yamltable_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

你们代码里的实现:

    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)

代码:

    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]

    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(水印淡化程度)

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(文字清晰度)

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(综合得分)

combined = fade * 0.5 + (sharpness / max_sharpness) * 0.5
  • fade:原始值(可能为负)
  • sharpness:归一化到 [0, 1](除以本轮扫描中最大的 sharpness)
  • 各占 50% 权重

设计意图:在水印淡化和文字清晰度之间找平衡。实际使用中,如果你更关注水印去除效果,可以调整权重偏向 fade。