| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- """水印 对比度增强(由 ocr_utils.watermark_utils 迁入)。"""
- from __future__ import annotations
- import json
- import re
- from pathlib import Path
- from typing import Any, Dict, Optional, Tuple, Union
- import cv2
- import numpy as np
- from loguru import logger
- from PIL import Image
- def _enhance_text_restore(
- gray: np.ndarray,
- *,
- background_threshold: int = 248,
- text_lo_percentile: float = 1.0,
- text_hi_percentile: float = 99.0,
- text_black_target: int = 85,
- ) -> np.ndarray:
- """
- 仅对非背景像素做动态范围压缩,将最深笔画拉向 text_black_target(默认 ~85,接近扫描件原图)。
- 背景(>= background_threshold)保持白色,避免整图 gamma 导致背景发灰。
- """
- result = gray.copy()
- bg_th = int(np.clip(background_threshold, 200, 255))
- text_mask = gray < bg_th
- if not np.any(text_mask):
- return result
- vals = gray[text_mask].astype(np.float32)
- lo = float(np.percentile(vals, text_lo_percentile))
- hi = float(np.percentile(vals, text_hi_percentile))
- target = int(np.clip(text_black_target, 10, 200))
- if hi <= lo + 1.0:
- return result
- stretched = (vals - lo) * target / (hi - lo)
- result[text_mask] = np.clip(stretched, 0, 255).astype(np.uint8)
- return result
- def enhance_document_contrast(
- gray: np.ndarray,
- method: str = "text_restore",
- *,
- clip_limit: float = 2.0,
- tile_grid_size: int = 8,
- gamma: float = 0.85,
- black_percentile: float = 2.0,
- white_percentile: float = 98.0,
- background_threshold: int = 248,
- text_lo_percentile: float = 1.0,
- text_hi_percentile: float = 99.0,
- text_black_target: int = 85,
- ) -> np.ndarray:
- """
- 文档灰度图对比度增强(常用于去水印后恢复笔画深度)。
- Args:
- gray: 单通道 uint8 灰度图
- method: text_restore | clahe | gamma | linear
- clip_limit: CLAHE 对比度限制
- tile_grid_size: CLAHE 分块大小
- gamma: gamma 校正指数,<1 加深文字(去水印后发浅时适用)
- black_percentile: linear 拉伸下分位(映射到 0)
- white_percentile: linear 拉伸上分位(映射到 255)
- background_threshold: text_restore 背景阈值(>= 视为白底不处理)
- text_lo_percentile: text_restore 笔画下分位
- text_hi_percentile: text_restore 笔画上分位(映射到 text_black_target)
- text_black_target: text_restore 最深笔画目标灰度(越小越深,建议 75~95)
- Returns:
- 增强后的灰度图
- """
- if gray is None or gray.size == 0:
- return gray
- if gray.ndim != 2:
- raise ValueError("enhance_document_contrast expects single-channel grayscale image")
- method = (method or "text_restore").lower().strip()
- if method == "text_restore":
- return _enhance_text_restore(
- gray,
- background_threshold=background_threshold,
- text_lo_percentile=text_lo_percentile,
- text_hi_percentile=text_hi_percentile,
- text_black_target=text_black_target,
- )
- 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)
- 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)
- # 默认 CLAHE:局部对比度,适合扫描件
- tile = max(2, int(tile_grid_size))
- clahe = cv2.createCLAHE(
- clipLimit=max(0.1, float(clip_limit)),
- tileGridSize=(tile, tile),
- )
- return clahe.apply(gray)
- def apply_contrast_enhancement_config(
- gray: np.ndarray,
- contrast_cfg: Optional[Dict[str, Any]],
- ) -> np.ndarray:
- """按配置字典应用对比度增强;未启用时原样返回。"""
- if not contrast_cfg or not contrast_cfg.get("enabled", False):
- return gray
- return enhance_document_contrast(
- gray,
- method=contrast_cfg.get("method", "text_restore"),
- clip_limit=contrast_cfg.get("clip_limit", 2.0),
- tile_grid_size=contrast_cfg.get("tile_grid_size", 8),
- gamma=contrast_cfg.get("gamma", 0.85),
- black_percentile=contrast_cfg.get("black_percentile", 2.0),
- white_percentile=contrast_cfg.get("white_percentile", 98.0),
- background_threshold=contrast_cfg.get("background_threshold", 248),
- text_lo_percentile=contrast_cfg.get("text_lo_percentile", 1.0),
- text_hi_percentile=contrast_cfg.get("text_hi_percentile", 99.0),
- text_black_target=contrast_cfg.get("text_black_target", 75),
- )
|