5. 图片预处理:低质量图也能识别
这是什么:图片预处理 = 识别之前先把照片「洗干净」:转灰度、二值化、去噪、放大。
为什么用:现实里拍出来的图常是暗、糊、背景杂、对比度低,OCR 模型最怕这种图,直接喂进去多半认出一堆乱码。预处理就是先递一张模型看得清的干净图过去。
如何用:本章用 OpenCV 一步步做灰度、二值化和去噪,每一步都拿同一张烂图对比前后效果。
5.1 先装 OpenCV
pip install opencv-python
版本说明:OpenCV 当前最新是 5.0.0.93。但注意 EasyOCR 会带
opencv-python-headless,PaddleOCR 则由 paddlex 固定opencv-contrib-python==4.10.0.84——三者都提供同一个cv2模块,同一环境里只能留一个,详见 5.5。
5.2 灰度:去掉颜色噪音
import cv2
img = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE) # 1 通道灰度图
cv2.imwrite('gray.png', img)
彩色图里的渐变、彩色噪点会干扰后面的阈值分割;先转灰度,文字和背景的明暗差才拉得开。
5.3 二值化:黑白分明
_, binary = cv2.threshold(img, 150, 255, cv2.THRESH_BINARY) # 阈值 150
cv2.imwrite('binary.png', binary)
threshold 的规则是:像素值 > 阈值 → 255(白),≤ 阈值 → 0(黑)。所以选阈值就是选「文字和背景的分界线」:
| 类型 | 适用场景 | 说明 |
|---|---|---|
THRESH_BINARY |
亮背景 + 暗文字(绝大多数截图) | 背景变白、文字变黑 |
THRESH_BINARY_INV |
暗背景 + 亮文字(深色模式、翻拍) | 背景变黑、文字变白 |
口诀:亮底暗字用 BINARY,暗底亮字用 INV;阈值卡在文字和背景中间。
阈值没有万能值:文字灰度约 60、背景灰度约 220,阈值取 140 左右最好;先取 128~160 试,再按效果微调。
5.4 去噪与放大:小字救命
# 高斯模糊(降低高频噪音)
blurred = cv2.GaussianBlur(img, (5, 5), 0)
# 放大 2 倍
resized = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
和 EasyOCR 的 mag_ratio 原理一样,放大是提升小字命中率最有效的手段(放大后再二值化再识别)。模糊别过头——它会让细笔画糊成一团。
5.5 光照不均:自适应二值化
普通阈值是「一刀切」:整张图用同一个分界线。但手机拍的照片常常一半亮一半暗(有阴影),这时一刀切必然顾此失彼——亮的一边好好的,暗的一边文字糊成黑块。
自适应阈值(adaptive threshold)的思路是:分区域来定阈值,每个小块按自己的亮度算分界线:
binary = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, blockSize=31, C=2)
| 参数 | 含义 | 调整方向 |
|---|---|---|
blockSize |
每个小块的边长(必须是奇数) | 块越大越平滑,块太小容易把笔画切断 |
C |
从局部均值里减掉的偏移量 | 调大 → 更多像素判为黑(文字更粗) |
用不上自适应就别用:它比普通阈值慢,而且干净截图上效果反而更差。先试普通阈值,不行再上自适应。
5.6 链条式预处理管线
def preprocess(img_path, threshold=150, scale=2):
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
if img is None:
raise FileNotFoundError(f'读不出图片:{img_path}')
# 1. 放大
img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
# 2. 去噪
img = cv2.GaussianBlur(img, (5, 5), 0)
# 3. 二值化
_, binary = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)
return binary
binary_img = preprocess('test.png', threshold=130, scale=2)
cv2.imwrite('preprocessed.png', binary_img)
常见场景的参数经验:
| 场景 | 阈值 | 放大 |
|---|---|---|
| 手机截图(亮背景) | 140–160 | 1.5–2 |
| 扫描件 / 深色模式(暗背景) | 90–110 + BINARY_INV |
2–3 |
| 代码截图(小字) | 120–140 | 2–3 |
5.7 常见翻车现场
| 报错 / 现象 | 原因 | 修法 |
|---|---|---|
import cv2 报错 |
多个 OpenCV 发行版打架 | pip list | findstr opencv,只留一个 |
cv2.imshow 报 can't display |
装成了 headless 版 | 装 opencv-python(非 headless) |
| 二值化后文字糊成一团 | 阈值压到笔画里了 | 阈值往上调 10~20 再试 |
| 二值化后背景全是黑字 | 暗底亮字用了 BINARY |
改用 THRESH_BINARY_INV |
| 放大后识别反而更差 | 模糊过度 | 去掉高斯模糊,或把核从 (5,5) 降到 (3,3) |
5.8 动手改
- 找一张暗图 / 小图,分别跑原图和预处理图,对比 EasyOCR 的命中率
- 调整
threshold和scale,把二值化结果存成 png 肉眼看效果 - 写个脚本:遍历文件夹,批量预处理并识别
5.9 小结
- 预处理四步:灰度 → 放大 → 去噪 → 二值化
- 阈值按「文字 vs 背景」的灰度分界来定,暗底亮字记得用
BINARY_INV - 放大 + 阈值调优是小字暗图的救星
- 一个环境只留一个 OpenCV 发行版
下一章:综合练习:批量图片转文本 🐸