""" OCR Image Enhancement — Improve scan quality before OCR. Applies CLAHE contrast enhancement + bilateral filter denoising to degraded scans. Only runs when scan_quality.is_degraded is True. Pattern adapted from handwriting_htr_api.py (lines 50-68) and cv_layout.py (lines 229-241). All operations use OpenCV (Apache-2.0). """ import logging import cv2 import numpy as np logger = logging.getLogger(__name__) def enhance_for_ocr( img_bgr: np.ndarray, is_degraded: bool = False, clip_limit: float = 3.0, tile_size: int = 8, denoise_d: int = 9, denoise_sigma_color: float = 75, denoise_sigma_space: float = 75, sharpen: bool = True, ) -> np.ndarray: """ Enhance image quality for OCR processing. Only applies aggressive enhancement when is_degraded is True. For good scans, applies minimal enhancement (light CLAHE only). Args: img_bgr: Input BGR image is_degraded: Whether the scan is degraded (from ScanQualityReport) clip_limit: CLAHE clip limit (higher = more contrast) tile_size: CLAHE tile grid size denoise_d: Bilateral filter diameter denoise_sigma_color: Bilateral filter sigma for color denoise_sigma_space: Bilateral filter sigma for space sharpen: Apply unsharp mask for blurry scans Returns: Enhanced BGR image """ if not is_degraded: # For good scans: light CLAHE only (preserves quality) lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB) l_channel, a_channel, b_channel = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) l_enhanced = clahe.apply(l_channel) lab_enhanced = cv2.merge([l_enhanced, a_channel, b_channel]) result = cv2.cvtColor(lab_enhanced, cv2.COLOR_LAB2BGR) logger.info("enhance_for_ocr: light CLAHE applied (good scan)") return result # Degraded scan: full enhancement pipeline logger.info( f"enhance_for_ocr: full enhancement " f"(CLAHE clip={clip_limit}, denoise d={denoise_d}, sharpen={sharpen})" ) # 1. CLAHE on L-channel of LAB colorspace (preserves color for RapidOCR) lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB) l_channel, a_channel, b_channel = cv2.split(lab) clahe = cv2.createCLAHE( clipLimit=clip_limit, tileGridSize=(tile_size, tile_size), ) l_enhanced = clahe.apply(l_channel) lab_enhanced = cv2.merge([l_enhanced, a_channel, b_channel]) enhanced = cv2.cvtColor(lab_enhanced, cv2.COLOR_LAB2BGR) # 2. Bilateral filter: denoises while preserving edges enhanced = cv2.bilateralFilter( enhanced, d=denoise_d, sigmaColor=denoise_sigma_color, sigmaSpace=denoise_sigma_space, ) # 3. Unsharp mask for sharpening blurry text if sharpen: gaussian = cv2.GaussianBlur(enhanced, (0, 0), 3) enhanced = cv2.addWeighted(enhanced, 1.5, gaussian, -0.5, 0) logger.info("enhance_for_ocr: full enhancement pipeline complete") return enhanced