2952 字
约 9 分钟
18
S27-X21 · OCR 与文档解析工程:版面分析、识别与后处理

把一页扫描件丢进 OCR,拿到一坨字符,然后发现下游抽不出任何字段——这是最典型的失败。识别率高不等于可用,因为结构丢了。下面按链路拆每一环的产出、失败模式与验收口径。


1. 完整链路

采集 → 预处理 → 版面分析 → 文本识别 → 表格/公式还原 → 后处理 → 结构化
(扫描/PDF/拍照) (去噪/纠偏/二值化) (区域+阅读顺序) (行级转写)   (单元格/LaTeX)  (纠错/合并)  (字段+JSON)
环节 输出 主要失败模式
预处理 规整位图 过度二值化吃掉细笔画
版面分析 区域框 + 类型 + 顺序 双栏读序错、表格框漏
文本识别 字符序列 + 置信度 小字丢失、字体失配
表格还原 单元格矩阵 合并单元格错位
后处理 修正文本 纠错引入新错
结构化 JSON 字段 字段错配、单位丢失

2. 为什么 OCR 不等于文档理解

OCR 回答"这页上有什么字",文档理解回答"这些字在什么位置、属于哪个字段"。

层级 问题 只做 OCR 的后果
字符层 这个字是什么 基本可用
行层 行的边界在哪 双栏拼成一行,语义错乱
区域层 这是标题还是页脚 正文与页脚混在一起
结构层 哪格属于哪列 金额错列,算错账
语义层 这是合计还是单价 字段错配

结论:识别准确率是必要条件,不是充分条件;下游要字段,字段依赖结构。

3. 版面分析:区域类型与阅读顺序

{"page": 3, "regions": [
  {"id": "r1", "type": "title",   "bbox": [72, 60, 520, 96],  "order": 0},
  {"id": "r2", "type": "text",    "bbox": [72, 110, 300, 640], "order": 1, "col": 1},
  {"id": "r3", "type": "text",    "bbox": [320, 110, 540, 640], "order": 2, "col": 2},
  {"id": "r4", "type": "table",   "bbox": [72, 660, 540, 900], "order": 3},
  {"id": "r5", "type": "footer",  "bbox": [72, 950, 540, 980], "order": 4}
]}
区域类型 下游用途 判错代价
title / heading 章节树 目录错乱
paragraph 正文抽取 顺序错则语义反转
table 结构化 错行错列
figure + caption 图文关联 图注丢失
header / footer 通常剔除 页码混进正文
seal / stamp 合规校验 误判签署状态

阅读顺序的三个难点:

难点 表现 处理
双栏 按 y 排序会串栏 先聚栏,栏内再按 y
混排 通栏标题 + 双栏正文 + 跨栏表格 按 x 覆盖范围判断是否跨栏
表格跨页 第 1 页表头在第 2 页续 跨页表头继承

4. 文字识别的三个关键因素

因素 具体表现 工程对策
图像质量 分辨率、噪点、压缩伪影、透背 300 DPI 量级重采样、去噪、按需超分
语言与字体 中英混排、繁体、手写体、特殊符号 语言模型切换、字典扩充
方向与倾斜 旋转 90°/180°、拍摄倾斜、弯曲 方向分类 + 透视矫正

小字丢失最常见:整页缩到 1000px 宽再识别,8pt 字只剩几个像素高。正确做法是先裁区域,再按原始分辨率识别。

# PP-OCR 三段式:检测 → 方向分类 → 识别(命令与参数名按所装版本核对)
python tools/infer/predict_system.py \
  --image_dir ./pages/ \
  --det_model_dir ./models/det \
  --cls_model_dir ./models/cls \
  --rec_model_dir ./models/rec \
  --rec_char_dict_path ./models/ppocr_keys_v1.txt \
  --use_angle_cls True \
  --drop_score 0.5 \
  --output ./out/
段 作用 输出 调参点
检测(DB 类) 找文字行框 四点/多边形框 阈值过松 → 粘连成块;过紧 → 断行
方向分类 判 0/90/180/270 角度标签 漏判 → 整行乱码
识别(CRNN/SVTR 类) 行图转字符 文本 + 分数 字典不含的字必错

PP-OCR 的中文适配点:中文字典覆盖常用汉字与标点、检测对竖排与窄间距做过适配、识别对中英混排做过训练。字典之外的生僻字、异体字一定会错,靠后处理白名单兜。

5. 表格还原:两条路线

路线 做法 优点 失败模式
结构识别模型 直接预测单元格与行列关系 无线框表、合并单元格也能处理 复杂表头(多级)易错;大表推理慢
规则线框检测 形态学找横竖线 → 求交点 → 切格 快、可解释、对有框表极稳 无框表、断线、扫描歪斜直接崩

实务上两条都跑,结果一致才自动通过,不一致进人工复核。判断依据是线框完整度:

def line_ratio(binary_img):
    h = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (40, 1)))
    v = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (1, 40)))
    return (h.sum() + v.sum()) / binary_img.size / 255   # 经验阈值约 0.02~0.1,须按样本核对

合并单元格是头号难题:rowspan/colspan 判错会让后续所有列连带错位。所以表格结果必须带每个单元格的 bbox 与 span,便于回溯。

6. 公式与手写

类型 输出形态 难点 验收
印刷公式 LaTeX 上下标、分式、矩阵 渲染回图比像素
手写体 文本或 LaTeX 连笔、涂改 人工抽检
化学式/单位 保留原文 特殊符号 字段级校验

公式验收的实用技巧:把识别出的 LaTeX 渲染回图片,与原图做结构相似度比较;文本级 BLEU 对公式几乎无意义。

7. 后处理:真正决定可用性

动作 目的 风险
错字纠正 修形近字常错对 过度纠正改掉专有名词
繁简统一 统一入库口径 法律文本的繁体原文被改
断行合并 段落还原 列表项被错误合并
页眉页脚剔除 去噪声 页码是必要字段时被删
全半角/空格规整 便于匹配 代码或编号被破坏
数字与单位校验 防金额错 校验规则写错反而误杀
postprocess:
  normalize: [fullwidth_to_halfwidth, strip_trailing_space]
  merge_lines:
    enable: true
    join_when: "line_end 不以句末标点结尾 且 下一行首字符非列表符号"
    keep_hyphen: false
  drop_regions: [header, footer]
  correct:
    dict: ./dict/domain_terms.txt      # 领域白名单优先于通用纠错
    protect: ["[A-Z]{2,}-\\d+", "\\d+(\\.\\d+)?\\s*(元|万元|%)"]
  traditional_to_simplified: false      # 合同类文档默认关闭

8. 评测:字符准确率不够

CER 只回答"字对不对",不回答"字段能不能用"。

指标 口径 局限
CER / 字符准确率 1 - 编辑距离/字符数 一个错字不影响字段可用性
行级准确率 整行全对占比 严格但仍是字符视角
区域类型准确率 版面分类正确率 不反映顺序错
阅读顺序准确率 相邻区域对顺序正确率 需要人工标注顺序
表格单元格准确率 行列坐标 + 内容都对 标注成本高
字段抽取通过率 下游 schema 校验通过的比例 最贴近业务

验收建议:以字段抽取通过率为主指标,CER 只作辅助监控。上线前必须单独跑一批低质量样本(歪斜、低分辨率、盖章遮挡),否则印刷体上好看的数字毫无意义。

9. 与 LLM 的配合

正确姿势:先把版面结构化成带标签的文本,再喂给模型抽字段;不要整页图直接丢进去。

做法 优点 问题
整页图直接给多模态模型 省事 小字丢、表格对齐不稳、成本高、不可复算
结构化文本 + 模型抽取 可复算、可校验、便宜 依赖前置解析质量
结构化文本 + 规则抽取 + 模型兜底 稳定且可控 需要维护规则
prompt = f"""你是单据抽取器。只输出 JSON,字段缺失填 null,不要解释。
字段:invoice_no, date, amount, tax, seller
规则:amount 为价税合计;日期统一 YYYY-MM-DD。

<document>
{structured_text}   # 已带区域标签: [TABLE:1] [CELL r1c3] ...
</document>"""

10. 工程要点

pipeline:
  ingest: {max_pages: 200, dpi: 300, accept: [pdf, jpg, png, tiff]}
  batch: {page_concurrency: 8, doc_concurrency: 2, queue: redis}
  retry: {max_attempts: 3, backoff: [2, 8, 30], retry_on: [timeout, empty_result]}
  cache:
    key: "sha256(file) + parser_version + model_version"
    ttl: 30d
  review_queue:
    trigger: "field_pass_rate < 0.9 or avg_conf < 0.75"
    sample_rate: 0.02        # 即便通过也抽检
    ui: {side_by_side: true, edit_fields: true, feedback_to: dataset}
要点 关键决定
批量与并发 并发按页还是按文档,决定内存峰值
失败重试 只重试幂等阶段,避免重复计费
人工复核 触发条件要可解释,否则队列爆
缓存 key 须含解析器与模型版本,否则换模型拿到旧结果

11. 排错:六种典型故障

现象 原因 处理
小字大面积丢失 整页缩图后识别 按区域裁切,原始分辨率识别
整行乱码 未做方向分类或漏判 打开 use_angle_cls,检查旋转样本
双栏文本串行 按 y 坐标全局排序 先聚栏再栏内排序
表格金额错列 合并单元格判错 保留 bbox 与 span,复核
专有名词被"纠正" 通用纠错覆盖领域词 领域白名单优先
通过率虚高 只测印刷体干净样本 加低质量样本集

12. 边界:这些情况该换方案

场景 纯 OCR 管线的局限 更合适的做法
有原生文本层的 PDF 不必识别 直接抽文本层 + 版面坐标
手写为主 识别率不稳 人工录入 + 模型辅助
极低质量传真件 前置修复收益有限 退件重扫
只需关键字段 全量转写浪费算力 定向区域识别
需要语义理解 OCR 不懂语义 结构化文本 + 模型抽取

一句话:OCR 的产出是文字,文档解析的产出是能被程序消费的结构。前者做对只是起点。


13. 自测

  1. 识别率 98% 的管线,为什么下游仍可能抽不出字段?
  2. 双栏文档按 y 坐标排序会出什么问题?正确顺序怎么定?
  3. PP-OCR 三段式各段的调参点分别是什么?
  4. 表格还原的两条路线各在什么表上失效?
  5. 为什么字段抽取通过率比 CER 更适合做主指标?
  6. 缓存 key 为什么必须包含解析器与模型版本?
S27-X21 · OCR 与文档解析工程:版面分析、识别与后处理
https://www.clxhxhhr.top/posts/6004/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。