OCR(光学字符识别)把图片/扫描件里的文字变成可编辑文本——发票识别、证件录入、截图取字、纸质档案数字化。它是 OpenCV 系列最自然的实战延伸,也是文档智能的入口技术。
前置阅读:OpenCV 图像处理(预处理操作)、PDF 解析(扫描版 PDF 的兜底方案就是它)。
两阶段流水线:先找到字,再认出字
现代 OCR 系统把任务拆成两步,各有专用模型:
文本检测(Detection):找出文字区域的位置——输出每个文本行的四边形框。难点是文字形态多变:横排竖排、弯曲排列、字号悬殊。代表算法 DB(可微分二值化),对弯曲文本友好。
文字识别(Recognition):把每个文本框内的图像切成规范高度后,识别出字符序列。输入是变长图像、输出是变长字符序列,用 CTC 解决对齐问题(和 ASR 里讲的一样)或注意力解码。
方向分类(可选第三步):判断文本框是否旋转了 180°,纠正倒置。
PaddleOCR:中文场景的默认答案
中文 OCR 的开源事实标准是 PaddleOCR:中英文混排效果好、模型小(手机都能跑)、生态完整:
# pip install paddleocr paddlepaddle
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, lang="ch")
result = ocr.ocr("invoice.jpg")
for line in result[0]:
box, (text, conf) = line
print(f"[{conf:.2f}] {text} 位置: {box}")
输出的 box 是四点坐标,可以画回原图校验,也可以按位置做后续结构化(比如”右上角的数字是金额”)。conf 低于 0.8 的结果建议人工复核——这是发票等严肃场景的常规阈值策略。
预处理:识别率的隐形半壁江山
扫描件/拍照件的质量问题在进模型之前解决:
import cv2
img = cv2.imread("scan.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 去阴影/光照不均:大核背景估计 + 除法归一
bg = cv2.medianBlur(gray, 51)
norm = cv2.divide(gray, bg, scale=255)
# 二值化(对噪点多的老文档用自适应阈值)
binary = cv2.adaptiveThreshold(norm, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 35, 11)
# 纠偏:霍夫直线或 minAreaRect 估算倾角后旋转
常见问题逐个对应:透视变形(拍照歪了)→ 四点透视变换拉正;光照不均 → 背景除法;噪点 → 中值滤波;倾斜 → 纠偏。我在票据项目里的体感:预处理把识别准确率从 88% 拉到 96%,比换任何模型都值。
结构化:从”一堆字”到”有用的数据”
识别出金额字符串,不等于已经知道它是合计、税额还是单价。结构化需要结合位置、字段名和格式约束,并保留原文与坐标供核对。低置信或金额关系不一致时,应转入复核,而不是用一个看似合理的值补齐。
OCR 输出是”文本 + 位置”的列表,业务要的是字段(发票号码、金额、日期)。结构化三条路:
- 规则 + 坐标:版式固定(同一种发票/证件)时,按坐标区域 + 正则提取。简单可靠,版式一变就废。
- 关键词锚点:找”金额:“字样,取其右侧/下方的文本。比纯坐标抗版式变化。
- 版面分析 + KIE 模型:PaddleOCR 的 PP-Structure 做版面恢复(表格识别成结构化单元格),LayoutXLM 类模型直接做关键信息抽取(KIE)——复杂版式(表格、多栏)的正解。
实践中按版式复杂度递增选择:固定版式走规则,半固定走锚点,自由版式上 KIE 模型。
工程化:批量处理管道
档案数字化这类批量场景,套用平台通用的管道纪律(AI 自动化):每页一个任务、结果按页落盘(JSON:文本+坐标+置信度)、失败页隔离重试、低置信页自动进入人工复核队列。GPU 批处理时 detection 和 recognition 分开组批,吞吐比逐页串行高数倍。
踩坑排查
| 症状 | 原因 | 处理 |
|---|---|---|
| 识别结果顺序乱 | 按检测顺序输出未排序 | 按坐标”从上到下、从左到右”重排 |
| 中文标点全丢 | 模型字典不含标点 | 检查字典配置或后处理补全 |
| 表格变成一坨文本 | 缺版面分析 | 上 PP-Structure 表格识别 |
| 手写字识别差 | 模型是印刷体训练的 | 换手写专用模型或扩大训练集 |
| 竖排文本漏检 | 检测模型不适配 | 旋转 90° 再识别/换支持竖排的模型 |
| 小字模糊识别错 | 原图分辨率不足 | 超分预处理或换高清源 |
练习
- 用 PaddleOCR 识别一张发票,画出检测框并输出置信度最低的 5 处。
- 对一张光照不均的拍照文档做背景除法预处理,对比前后识别率。
- 用锚点法从识别结果中提取”发票号码”和”金额”字段。
- 用 PP-Structure 识别一张含表格的页面,导出结构化单元格。
面试常问
Q:OCR 为什么要拆成检测 + 识别两步? 整页直接识别的话序列太长且含大量空白,两步解耦让每个子问题更简单:检测专注定位(对形态鲁棒),识别专注小图内的序列解码。这也是流水线可替换性的来源——检测或识别模型可独立升级。
Q:CTC 在 OCR 里解决什么? 识别输入是变长图像特征序列、输出是变长字符序列,两者没有对应对齐关系。CTC 通过空白符和对所有可能路径求和,免对齐训练序列模型——和语音识别里的作用相同。
Q:扫描件和拍照件的 OCR 难点差异? 扫描件:噪点、泛黄、装订弯曲——靠预处理清洗;拍照件:透视变形、光照不均、模糊——需要几何矫正和光照归一。两者的共同解都是”进模型前的预处理管道”。
Q:识别结果怎么做结构化抽取? 按版式复杂度:固定版式用坐标规则,半固定用关键词锚点 + 相对位置,自由版式用 KIE 模型(LayoutXLM 系,联合文本与版面布局编码)。表格场景用专用的表格结构识别模型。
相关阅读:OpenCV 实战项目、PDF 解析(扫描版 PDF 的 OCR 支线)。
💬 评论
这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。