如何适配黑底白字/白底黑字场景实现OCR图像二值化处理
面向OCR识别的自适应正反底色二值化预处理方案
原实现失效的核心原因有三点:
- 固定使用
THRESH_BINARY阈值模式,强制输出固定明暗方向的二值结果,无法自动适配黑底/白底两类输入 - 固定使用3x3全1卷积核做先腐蚀后膨胀的形态学操作,对细笔画文字损伤大,容易出现文字糊块、断裂问题
- 输出格式未对齐Tesseract的默认输入要求:Tesseract默认训练数据针对白底黑字场景优化,直接输入黑底白字图识别率会大幅下降。
优化逻辑
- 保留原实现的除法归一化逻辑,解决常规场景下的局部光照不均问题,保证原有场景效果不回退
- 新增底色自动判断逻辑:取图像四周边缘区域(大概率为纯背景)计算平均亮度,自动识别当前是亮底还是暗底
- 二值化时根据底色自动选择正向/反向阈值模式,最终统一输出为白底黑字格式,完全匹配Tesseract输入要求
- 替换原有的3x3全1核为小尺寸十字形结构元素,做轻度开运算去噪,最大程度保留文字笔画完整性,避免糊块、断裂
- 去掉原代码中固定传入的阈值参数100,完全交给OTSU算法自动计算全局最优阈值,适配不同对比度场景
优化后完整代码
import cv2 import numpy as np def preprocessing(image): # 转单通道灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) img_h, img_w = gray.shape # 光照归一化:通过不同核大小的中值模糊做除法,消除局部光照差影响 blur_fine = cv2.medianBlur(gray, 3) blur_coarse = cv2.medianBlur(gray, 51) divided = np.ma.divide(blur_fine, blur_coarse).data normed = np.uint8(255 * divided / divided.max()) # 取图像四周10%宽度的边缘像素计算背景平均亮度,避免中间文字区域干扰判断 edge_area = np.concatenate([ normed[:int(img_h*0.1), :].flatten(), normed[int(img_h*0.9):, :].flatten(), normed[:, :int(img_w*0.1)].flatten(), normed[:, int(img_w*0.9):].flatten() ]) bg_avg_bright = np.mean(edge_area) # 根据背景亮度选择二值化模式,统一输出白底黑字 if bg_avg_bright > 127: # 亮背景场景:正向二值化 _, binary_img = cv2.threshold(normed, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) else: # 暗背景场景:反向二值化转成白底黑字 _, binary_img = cv2.threshold(normed, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) # 小尺寸十字核做轻度开运算,去除孤立杂点同时不破坏文字笔画 denoise_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (2, 2)) binary_img = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, denoise_kernel, iterations=1) return binary_img
适配效果说明
常规场景
原有常规白底黑字文档场景处理效果和原实现完全一致,无效果回退:
- 原始图像:

- 二值化结果:

正反底色异常场景
两类反色、低对比度场景下都能输出清晰的白底黑字结果,文字笔画完整无糊块,可直接输入Tesseract识别:
- 黑底白字测试原图:

- 低对比度测试原图:

参数调优提示
- 如果处理的图像文字笔画极细,可以把去噪核大小从
(2,2)改为(1,1),或者直接去掉形态学去噪步骤,避免笔画被清除 - 如果遇到光照差极大的场景,可以把大核中值模糊的核大小
51调整为图像最短边长度1/10左右的奇数值,光照归一化效果会更稳定 - 如果需要处理带复杂边框的图像(边缘不是纯背景),可以把底色判断逻辑替换为统计OTSU阈值输出后的前后景像素占比,自动选择是否反色即可
内容的提问来源于stack exchange,提问作者Yash Mistry
相关产品推荐
相关产品推荐

