OCR 与文档理解

本文系统讲解 OCR 与文档理解,回答检测与识别怎么串、PaddleOCR 与 Tesseract 怎么选、版面与表格怎么结构化等实战问题。覆盖 DBNet 可微二值化、CRNN 与 CTC 解码、倾斜矫正与低质量增强、中文多语言难点、表格识别、评估指标与后处理纠错,并给出 PaddleOCR 推理与 CTC 解码两段可运行代码。

引言

OCR(Optical Character Recognition,光学字符识别)把图像中的文字转成可编辑文本。它看起来是个老问题,但真正难的不是单字识别,而是把「检测、矫正、识别、版面、结构化」串成一条在真实文档上稳定的流水线。票据、合同、证件、工业铭牌的版式千差万别,扫描质量、光照、倾斜、印章遮挡都会让准确率断崖式下跌。

工程上最常见的误区是把 OCR 当成纯粹的模型问题:以为换个更大的识别网络就能提升效果,实际上多数失败来自文本检测漏检(小字、密集行)、预处理没做倾斜矫正、以及缺少领域后处理(字典、正则、校验位)。另一个误区是忽略「结构化」——业务要的往往不是一串文本,而是「发票号、金额、开票日期」这样的键值对。

本文按「任务拆解 → 检测 → 识别 → 框架选型 → 预处理 → 版面与表格 → 结构化抽取 → 评估与纠错」的顺序讲,重点给出可直接运行的代码与真实取舍。检测与识别的基础概念可先看 目标检测与 YOLO 系列工程落地 ,图像预处理手段见 OpenCV 图像基础与预处理 。

目录

  1. OCR 任务拆解与链路
  2. 文本检测:DBNet 与可微二值化
  3. 文本识别:CRNN 与 CTC
  4. 端到端 OCR 框架选型
  5. 预处理:倾斜矫正与低质量增强
  6. 代码:PaddleOCR 端到端识别
  7. 代码:CTC 解码与编辑距离评估
  8. 版面分析与表格识别
  9. 文档结构化抽取
  10. 中文与多语言识别难点
  11. 评估指标与后处理纠错
  12. 部署与性能优化

1. OCR 任务拆解与链路

工业级 OCR 是一条多级流水线,每级都可能成为瓶颈:

阶段输入输出典型方法
预处理原始图像矫正、增强后的图去噪、二值化、透视矫正
文本检测图像若干文本行四边形DBNet、PSENet、CRAFT
方向分类文本行裁剪0/90/180/270轻量分类网络
文本识别文本行裁剪字符串CRNN+CTC、SVTR、TrOCR
版面分析整页图像区域类型与顺序LayoutLM、PP-Structure
结构化文本行 + 版面键值对、表格规则 + 模型

把链路拆开的价值在于定位问题:如果最终字段错误,先看是检测框没框住、识别错字、还是版面顺序错。不做拆解就只能盲目换模型。

2. 文本检测:DBNet 与可微二值化

文本检测与通用目标检测的区别在于目标是「细长不规则区域」,且相邻字符必须合并成一个文本行。

DBNet(Differentiable Binarization)的思路是:网络先预测一个概率图 P,再用一个自适应阈值图 T,通过近似的阶跃函数得到二值图 B。因为二值化可微,整个流程可以端到端训练。它的后处理简单:对二值图取连通域,再用 Vatti 算法把轮廓膨胀回文本框。DBNet 在 ICDAR2015 上约 87% 的 Hmean,速度也很快,是工业首选。

其他方案:

  • CRAFT:预测字符区域与字符间连接,擅长处理任意形状与密集文本。
  • PSENet:用渐进式尺度扩张分离紧邻文本实例。
  • EAST:早期方案,直接回归框的几何参数,对长文本不如 DBNet 稳。

工程上最常调的两个参数是 det_db_thresh(概率图阈值,默认 0.3)与 det_db_box_thresh(框置信度阈值,默认 0.6)。低质量图要下调阈值防漏检,但会引入噪声框。

3. 文本识别:CRNN 与 CTC

CRNN(Convolutional Recurrent Neural Network)是识别分支的经典结构,三段式:

  1. 卷积层提取特征,得到高度为 1 的特征图(如 H/32 × W/8)。
  2. 循环层(BiLSTM)沿宽度方向建模字符间的序列依赖。
  3. 转录层把序列映射成字符串。

难点在于「不定长对齐」:图像宽度与字符数不对应,无法逐帧标注。CTC(Connectionist Temporal Classification)用「空白符号 + 多对一折叠」解决:允许输出重复字符与空白,最后折叠重复并去掉空白。例如序列 -a-a-b-b- 折叠为 ab。

CTC 的损失对每一帧的输出做边缘化求和,训练时不需要字符级位置标注,这是它能在工业上大规模使用的原因。缺点是假设「字符间条件独立」,对语言模型约束弱,中文长句容易出错。改进方向有 Attention 解码(如 TrOCR)、以及识别后接语言模型纠错。

import torch
import torch.nn as nn

# CTC 损失:log_probs 形状 (T, N, C),targets 是拼接后的标签
ctc = nn.CTCLoss(blank=0, reduction="mean", zero_infinity=True)
T, N, C = 26, 4, 38          # 时间步、批大小、字符集大小(含 blank)
log_probs = torch.log_softmax(torch.randn(T, N, C), dim=2)
targets = torch.randint(1, C, (N, 8), dtype=torch.long)   # 每样本 8 个字符
input_lengths = torch.full((N,), T, dtype=torch.long)
target_lengths = torch.full((N,), 8, dtype=torch.long)
loss = ctc(log_probs, targets, input_lengths, target_lengths)
print("ctc loss:", float(loss))

注意 CTCLoss 的输入必须是 log_softmax 之后的值,且时间维在最前。忘记 log_softmax 是最常见的报错来源。

4. 端到端 OCR 框架选型

自己从零实现检测加识别不现实,工程上直接用成熟框架。

框架检测识别中文支持特点
PaddleOCRDBNetCRNN/SVTR极好中文场景事实标准,模型全
Tesseract内置LSTM一般老牌,无 GPU 也能跑,版面弱
EasyOCRCRAFTCRNN好API 简单,80+ 语言
MMOCR多种多种好OpenMMLab 生态,可换模块
TrOCR无Transformer一般印刷体英文强,中文需微调
docTRDBNetCRNN/ViT一般文档场景友好

选型建议:中文与票据场景优先 PaddleOCR;需要换模块做研究用 MMOCR;只要一个简单 API 用 EasyOCR;纯英文印刷体且要求离线轻量可用 Tesseract。

5. 预处理:倾斜矫正与低质量增强

预处理往往比换模型更有效。常见手段:

  • 灰度化与去噪:中值滤波去椒盐噪声,双边滤波保边去噪。
  • 二值化:全局 Otsu 适合光照均匀,自适应阈值适合光照不均。
  • 倾斜矫正:用霍夫变换估计文本行倾角,或用最小外接矩形角度做仿射矫正。
  • 透视矫正:检测文档四角点,用 getPerspectiveTransform 展平。
  • 超分与去模糊:低分辨率票据可先用 Real-ESRGAN 类模型放大再识别。
  • 对比度增强:CLAHE 对低对比度扫描件提升明显。
import cv2
import numpy as np

def deskew(gray):
    # 用 Otsu 反色后找非零点的最小外接矩形角度做矫正
    thr = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
    coords = np.column_stack(np.where(thr > 0))
    angle = cv2.minAreaRect(coords)[-1]
    if angle < -45:
        angle = 90 + angle
    h, w = gray.shape[:2]
    M = cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1.0)
    return cv2.warpAffine(gray, M, (w, h), flags=cv2.INTER_CUBIC,
                          borderMode=cv2.BORDER_REPLICATE)

gray = cv2.cvtColor(cv2.imread("receipt.jpg"), cv2.COLOR_BGR2GRAY)
print("deskewed:", deskew(gray).shape)

注意 minAreaRect 返回的角度语义在不同 OpenCV 版本间有差异,务必用可视化验证,不要盲信。

6. 代码:PaddleOCR 端到端识别

PaddleOCR 把检测、方向分类、识别封装成一次调用。

from paddleocr import PaddleOCR

# use_angle_cls 开启方向分类,lang 指定语言模型
ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=False)

result = ocr.ocr("invoice.jpg", cls=True)
for line in result[0]:
    box, (text, score) = line           # box 是四个角点,text 是识别串
    print(f"{score:.3f}  {text}")

# 版面分析 + 表格识别(PP-Structure)
from paddleocr import PPStructure
engine = PPStructure(show_log=False, layout=True, table=True)
for region in engine("page.png"):
    print(region["type"], region.get("res", "")[:40])

cls=True 会多跑一次方向分类,慢一点但能救回倒置的文本行。版面分析返回的 region["type"] 常见取值为 text、title、table、figure,表格会额外返回 HTML 结构。

7. 代码:CTC 解码与编辑距离评估

训练之外,评估环节需要贪心解码与编辑距离。下面手写一遍,便于理解框架内部行为。

import numpy as np

def ctc_greedy_decode(logits, blank=0):
    # logits: (T, C),返回去掉 blank 并折叠重复后的索引序列
    idx = logits.argmax(axis=1)
    out, prev = [], None
    for i in idx:
        if i != prev and i != blank:
            out.append(int(i))
        prev = i
    return out

def edit_distance(a, b):
    # 标准 Levenshtein 距离,用于计算字符错误率 CER
    dp = list(range(len(b) + 1))
    for i, ca in enumerate(a, 1):
        new = [i]
        for j, cb in enumerate(b, 1):
            new.append(min(dp[j] + 1, new[j - 1] + 1, dp[j - 1] + (ca != cb)))
        dp = new
    return dp[-1]

logits = np.random.randn(20, 10)
pred = ctc_greedy_decode(logits)
print("decoded:", pred)
print("CER:", edit_distance([1, 2, 3], [1, 2, 4, 5]) / 4)   # 0.5

贪心解码实现简单但非最优,同一输入用 beam search 常能少错一两个字;线上对延迟敏感时贪心仍是主流。

8. 版面分析与表格识别

版面分析回答「这一页有哪些区块、顺序如何」,是文档理解的关键一环。

  • 区域检测:把页面切成标题、正文、表格、图片、页眉页脚等区域。
  • 阅读顺序:中文与英文的阅读顺序不同,多栏排版需要排序算法或模型预测。
  • 表格识别:先检测表格线或单元格,再逐格 OCR,最后按行列还原成 HTML 或二维数组。
  • 无线表格:没有框线的表格靠单元格坐标聚类还原,难度显著更高。

常用模型有 LayoutLMv3、PP-Structure、以及基于 DETR 的版面检测器。评估指标常用 mAP 与区域级的 F1。

表格还原的坑在于「合并单元格」:跨行跨列会让行列索引错乱,工程上要显式建模 rowspan/colspan。

9. 文档结构化抽取

业务要的是键值对,不是文本行。抽取分两条路线:

  • 规则路线:用关键词定位字段(如「发票号码」右侧最近文本),配正则与校验位。成本低、可解释、适合版式固定的票据。
  • 模型路线:用 LayoutLM 系列做多模态序列标注,把文本、位置、视觉特征一起输入 Transformer,直接输出字段标签。适合版式多变、字段语义复杂的场景。
import re

PATTERNS = {
    "invoice_no": r"发票号码[::]?\s*([0-9]{8,20})",
    "amount": r"(?:价税合计|金额)[^\d]{0,6}([0-9]+\.[0-9]{2})",
    "date": r"(\d{4})\s*年\s*(\d{1,2})\s*月\s*(\d{1,2})\s*日",
}

def extract(text):
    out = {}
    for key, pat in PATTERNS.items():
        m = re.search(pat, text)
        if m:
            out[key] = m.group(0) if key == "date" else m.group(1)
    return out

print(extract("发票号码:12345678 价税合计 1200.00 2026年10月07日"))

规则与模型通常混用:模型做初抽,规则做校验(如金额合计是否等于明细之和),不一致时转人工。

10. 中文与多语言识别难点

中文 OCR 比英文难在四点:

  • 字符集大:常用汉字 6000+,加上符号与生僻字,分类头远大于英文的 26 个字母。
  • 字形相近:己/已/巳、日/曰、未/末,低分辨率下极易混淆。
  • 无词边界:英文可借空格分词,中文必须靠语言模型消歧。
  • 混排:中英数字标点混排,需要统一字符集与全半角归一化。

多语言场景还要处理从右到左(阿拉伯语)、上下结构(泰语)、以及变音符(越南语)。工程手段是分语言训练识别模型,或用共享字符集的统一模型;后处理上做全角转半角、繁简统一、以及领域词典约束。

手写体是另一个量级:连笔、笔画粘连、书写风格差异大,通常需要专门的标注数据与更强的序列模型,通用印刷体模型直接迁移效果很差。

11. 评估指标与后处理纠错

OCR 的指标必须分开看检测与识别:

指标含义适用
Precision / Recall / Hmean检测框的准确与召回文本检测
字符准确率正确字符数 / 总字符数识别
词准确率整串完全正确的比例识别,更严格
CER编辑距离 / 真值长度识别,越低越好
字段准确率结构化字段全对的比例业务最终指标

注意「字符准确率 99%」听起来很高,但一张票据 200 个字符,意味着平均两张就有一个错字,字段级准确率可能只有 80%。业务指标要用字段准确率而非字符准确率。

后处理纠错三招:

  • 词典约束:识别结果与领域词表做模糊匹配,纠正形近字。
  • 规则校验:身份证校验位、金额格式、日期合法性。
  • 语言模型重排:用 n-gram 或小型 LLM 对 beam 候选重排,提升长句正确率。
def cer(pred, truth):
    if not truth:
        return 0.0
    return edit_distance(pred, truth) / len(truth)

print("CER:", round(cer("发票号玛", "发票号码"), 4))   # 0.25

12. 部署与性能优化

OCR 服务化的瓶颈常在检测阶段(高分辨率整图)与预处理(CPU 串行解码)。

  • 检测降采样:整图缩放到长边 960 到 1280 通常够用,再大收益递减。
  • 批处理:识别阶段把多个文本行拼成一个批,吞吐提升明显。
  • 模型选择:移动端用 PP-OCRv4 mobile 系列,参数量几 M,CPU 也能跑。
  • 推理引擎:导出 ONNX 后用 ONNX Runtime 或 TensorRT,FP16 通常有 1.5 到 2 倍加速。
  • 缓存:同一文档重复提交时按哈希缓存结果,省掉重复推理。
  • 异步流水线:检测与识别分到不同 worker,请求在阶段间流动。

性能与精度的平衡点要靠压测确定,方法与 视觉服务化与推理加速 中的动态批处理与延迟分析一致。

权衡取舍

维度倾向代价
通用框架与自训练先用通用框架,难例再微调微调需标注数据
规则抽取与模型抽取固定版式用规则,多变版式用模型模型需 GPU 与标注
检测精度与速度提高分辨率提升小字召回延迟与显存上升
贪心解码与 beam search延迟敏感用贪心,精度优先用 beambeam 慢数倍
端到端与多级流水多级易定位问题,端到端省工程端到端难调试
字典约束与开放识别领域字典提升准确率生僻字被误纠

几条实用原则:

  • 先测「检测召回率」,检测漏了的字,识别再强也救不回。
  • 字段准确率才是业务指标,不要用字符准确率汇报。
  • 领域词典与校验规则的成本远低于重训模型,优先做。
  • 低质量图先做预处理,往往比换大模型收益更大。

常见坑清单

  • 预处理与训练不一致:训练用灰度二值图,线上直接送彩色原图,掉点严重。
  • 忘记方向分类:倒置或竖排文本行识别成乱码,cls=True 能救回大部分。
  • 检测阈值过高:低对比度票据上的浅色小字直接漏检,需下调 det_db_thresh。
  • 框未做外扩:紧贴文字的检测框会切掉笔画边缘,识别出错,需按比例外扩。
  • 中文全半角混用:123 与 123 是两个字符,必须归一化后再匹配字段。
  • 表格合并单元格不建模:行列索引错位,抽取的字段张冠李戴。
  • 用字符准确率汇报:掩盖字段级错误,业务验收时被打回。
  • 忽略阅读顺序:多栏文档按检测顺序拼接,语义完全错乱。
  • 缺校验位验证:身份证与银行卡号不做校验,错一位也当成功。
  • 训练数据与线上字体不匹配:新字体上线后准确率骤降,需持续回流难例。
  • 忘记归一化:识别模型输入必须做与训练一致的缩放与填充。

小结

OCR 工程的主线是:预处理矫正 → DBNet 检测 → 方向分类 → CRNN 加 CTC 识别 → 版面分析 → 规则与模型结合做结构化 → 字段级评估与后处理纠错。记住三个判断点:检测召回决定上限、预处理性价比最高、字段准确率才是业务指标。中文场景优先用 PaddleOCR 这类成熟框架,把精力投在预处理、领域词典与校验规则上。

当版式高度多变、字段语义复杂时,再引入 LayoutLM 系列做多模态序列标注;当文档规模巨大且重复率高时,优先做缓存与增量处理。OCR 的上限往往由数据质量与业务规则决定,而不是网络结构。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机视觉」更多文章

  1. 检测与分割的评估指标
  2. 多模态视觉语言模型
  3. 3D 视觉:点云与深度估计