|
|
9 giờ trước cách đây | |
|---|---|---|
| .. | ||
| cell_preprocess_lab | 9 giờ trước cách đây | |
| gan_experiments_lab | 2 ngày trước cách đây | |
| watermark_lab | 2 ngày trước cách đây | |
| README.md | 2 ngày trước cách đây | |
| __init__.py | 2 ngày trước cách đây | |
水印去除/对比度增强/单元格预处理实验模块,统一归集到 ocr_tools/lab/ 目录下。
包含 LaMa GAN inpainting、掩膜参数扫描、对比度增强扫描、单元格预处理 OCR 扫描。
当前脚本已支持直接读取本地权重文件,不再依赖运行时下载。
/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 修复 → 去水印结果
针对银行流水等场景的水印 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%。
但水印与正文重叠区域仍无法通过纯图像学方法精确分离。
核心思路:不去水印,直接对原图做多种对比度增强,让水印在视觉上"淡化", 正文保持清晰,使后续 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
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 |
关键发现:
text_restore 对银行流水有害(fade 为负):它把水印像素当成"要恢复的文字"一并压深,水印反而更显眼gamma=0.85 作为备选简单有效,整体对比度提升但水印淡化程度不如 CLAHE推荐生产配置:
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 是 AHE(自适应直方图均衡)的改进版。核心思想是:
tile_grid_size × tile_grid_size 像素的小块clip_limit 的部分被裁掉并均匀分给所有灰度级),防止局部噪声被过度放大为什么对水印有效:水印是覆盖在全图上的均匀半透明层,在局部窗口(例如 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 让过渡更平滑。
对图像做幂律变换:
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 为负(水印反而更明显了)。
统计全图灰度的 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 为负)。
专门为去水印后文字变浅的场景设计。核心逻辑:
gray >= background_threshold(默认 248)的白色背景像素不动,避免白底变灰gray < background_threshold 的非背景像素做动态范围压缩压缩公式:取文字区域像素的 [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%) | 排除极亮的异常像素 |
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))
计算原理:
|原图 - 背景|:残差大 = 水印纹理明显(波动剧烈)计算增强前后残差的方差比
fade = 1 - var(增强后的残差) / var(原始残差)
CLAHE 得到 0.009 的正值,说明水印纹理被轻微减弱。text_restore 得到 -0.769,说明它反而把水印纹理放大了。
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 = fade * 0.5 + (sharpness / max_sharpness) * 0.5
fade:原始值(可能为负)sharpness:归一化到 [0, 1](除以本轮扫描中最大的 sharpness)设计意图:在水印淡化和文字清晰度之间找平衡。实际使用中,如果你更关注水印去除效果,可以调整权重偏向 fade。