🐸 青蛙编程网
首页 / AI / 文字识别 OCR / 5. 图片预处理

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 动手改

  1. 找一张暗图 / 小图,分别跑原图和预处理图,对比 EasyOCR 的命中率
  2. 调整 threshold 和 scale,把二值化结果存成 png 肉眼看效果
  3. 写个脚本:遍历文件夹,批量预处理并识别

5.9 小结

  • 预处理四步:灰度 → 放大 → 去噪 → 二值化
  • 阈值按「文字 vs 背景」的灰度分界来定,暗底亮字记得用 BINARY_INV
  • 放大 + 阈值调优是小字暗图的救星
  • 一个环境只留一个 OpenCV 发行版

下一章:综合练习:批量图片转文本 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。