从线条到结构化文档:Indra OCR 阅读顺序模型与版式块如何工作(快速指南)

原创2026-10-24 19:44:29613 阅读

从线条到结构化文档:Indra OCR 阅读顺序模型与版式块如何工作(快速指南)

Indra OCR 是一个只有约 100 万(1M)参数的 OCR 模型,全程在 CPU 上运行,速度约为 Tesseract 的 15 倍,输出质量接近 1B 级大模型。它不只是把扫描件变成一串文字——Indra OCR 还会给每一行文字算出阅读顺序,把它们归入带标签的版式块(章节标题、正文、列表、表格……),最终产出结构化的 Markdown 或 JSON 文档。这篇文章带你走一遍这套机制:从一行行检测出来的文字,到带顺序、带结构的文档输出。

一条流水线:从文字行到结构化文档

Indra OCR 的处理分为五步(核心实现在 indra_ocr/pipeline.py):

det.onnx(检测文字行、图片区域)
  → rec.onnx(逐行识别文字)
  → order.onnx(阅读顺序 + 版式块)
  → 组装 markdown / JSON

模型文件很小(默认约 76 MB,首次运行自动下载并缓存),其中负责"顺序 + 版式"的是 order.onnx(文件清单见 indra_ocr/models.py)。它不输出任何坐标,只做三件事:

  1. 成对排序:判断任意两行"谁在前、谁在后";
  2. 行分类:给每行打 10 种类别之一的概率;
  3. 同块判断:判断两行是否属于同一个版式块。

理解这三件事,就理解了 Indra OCR 如何"读懂"版面。

Indra OCR 速度-精度基准:CPU 上 15x Tesseract 的文档解析性能

阅读顺序模型:不排序,先问"谁在前"

直觉的做法是给每行打一个"序号分"再排序,但多栏页面、脚注、侧边文字会让这种线性打分失效。Indra OCR 的 order.onnx 用的是成对比较:输入每行 16 个特征(10 个几何特征 + 6 个文本特征,定义在 indra_ocr/order_post.py 中):

  • 几何:行框在页面上的相对坐标、宽高比、行高相对中位数的偏差;
  • 文本:文本长度、数字占比、是否以项目符号/编号开头、是否以句末标点结束、是否以大写字母开头、是否连字符断行。

模型输出一个 [N, N] 的 logit 矩阵,a[i, j] > 0 表示"第 i 行在第 j 行之前"的概率更高。

解码:行和和 + 插入式局部搜索(decode_order()):

  1. 先用每行的"前于他人概率和 − 后于他人概率和"得到一个初始排列;
  2. 🔁 反复把每一行取出、插回到"使成对关系总概率最大"的位置,扫若干轮直到不再改进。

插入式搜索比相邻交换强得多:它能把一整段(比如某栏被排错的尾巴)一次性挪到远处正确位置,而逐次交换要挪很多步。

Indra OCR 多栏页面阅读顺序:双栏期刊页的版式块与线条标注

版式块:先聚块,再排块

有了"谁与谁同块"的判断后,decode_blocks_order() 分三步组装阅读顺序:

  1. 聚类成块:对"同块"logit 做平均链接聚类——把两两平均概率高于 0.5 的组不断合并。与连通分量不同,一两条误判不会把两个大块误并;
  2. 块内定序:普通块按 XY-cut(找最宽的空白间隙递归切分);Table、Form、Table-Of-Contents 这类网格块则按"从上到下、从左到右"逐行读;
  3. 块间排序:块与块之间取"平均成对排序 logit",再走一遍上面的插入式搜索。

模型同时给每行输出 10 个版式块类别的概率(BLOCK_CLASSES):

标签 含义
Text 正文段落
Section-Header 章节标题
Page-Header / Page-Footer 页眉 / 页脚
List-Group 列表
Table 表格
Caption 图注
Footnote 脚注
Table-Of-Contents 目录
Form 表单

一个块的最终标签,取块内所有行类别概率之和的 argmax(page_blocks()),保证标签与分块一致。

从版式块到 Markdown:每类块各有一套渲染规则

Markdown 组装在 layout_post.py 中,规则直白:

  • Section-Header → ## 标题文字;
  • Text / Caption 等 → 合并为一个段落;tumori- + genesis 会接回 tumorigenesis,两个 CJK 字符之间断行则不加空格(join_lines());
  • List-Group → 每行一个条目,遇到项目符号或编号自动开新条目;
  • Table → 组装成 HTML 表格:按垂直中心把单元格聚成行、用"最满的行"估计列边界、跨列自动补 colspan(table_html());
  • Page-Header / Page-Footer → 默认丢弃,--keep-furniture 保留。

Indra OCR 表格版式块:期刊页表格输出为 HTML 表格

如果表头模型判断"同一单元格"的置信度足够,还可以直接用行/列/单元格三个头的 logits 聚类出单元格(table_html_heads());一旦校验发现它把并排的行错误合并(heads_plausible()),就自动退回上面的几何法——模型出错时有几何兜底。

特殊方向:竖排、倒置与从右到左

  • 竖排页面(中文、日文竖排):方向模型 orient.onnx 识别出竖排后,页面被逆时针转 90°,每个竖列变成一行交给 CJK 识别器;竖排页不走排序模型,改由 XY-cut 分块(xycut_blocks(),测试见 tests/test_vertical_order.py),读完后所有框坐标再映射回原页面,page.vertical 置为 True;
  • 倒置/倾斜:倾斜搜索(最大 45°)+ 90° 整数旋转,把页面摆正后再检测;
  • 从右到左(阿拉伯语、希伯来语等):整页先做水平镜像,让"第一栏"变成右边那一栏,排序照常进行,识别出的文字再用逻辑顺序翻转(indra_ocr/rtl.py)。

Indra OCR 方向纠正:倒置扫描页旋转 180° 后按正确阅读顺序读出

查看版式块与阅读顺序:JSON 输出和本地查看器

想直接看模型输出了什么?两条路:

indra-ocr scan.png --format json   # 每行、每个版式块 + 阅读顺序 + 置信度
indra-ocr-viewer                   # 本地 Web 查看器,http://localhost:8765

JSON 里每页都有 lines(按阅读顺序排列的 Line(box, text, conf))和 blocks(按阅读顺序排列的 {"label", "bbox", "text"})。查看器则把线条、版式块和阅读顺序直接画在页面上,还能与 Tesseract 的结果对照。可运行示例见 examples/ocr_image.py 与 examples/quickstart.ipynb。

性能与局限:快速,但要选对场景

Indra OCR 吞吐量对比:32 核 CPU 上约 44 页/秒

在 32 核 CPU 上约 44 页/秒,约为 Tesseract 的 15 倍、PaddleOCR 文本模型的 10 倍。不过它面向干净印刷体优化,以下场景不适合:

  • 复杂表格(合并单元格、跨行跨列、无线框)可能把单元格放错行列;
  • 手写页可读但不准(连笔和淡铅笔最弱);
  • 老旧、褪色、低分辨率扫描件表现较差。

这类高难度文档,官方建议走 Datalab 平台上更大的模型。

小结

Indra OCR 的"结构感"来自一个极小的 order.onnx:成对排序 + 插入式搜索决定阅读顺序,平均链接聚类 + 10 类行分类决定版式块,几何规则(XY-cut、表格行列对齐)兜底并负责最终渲染。这套设计让它只有几 MB 的模型体积,也能在纯 CPU 上把多栏论文、带表格的期刊页读成结构正确的 Markdown。

登录后查看全文
indra