从线条到结构化文档:Indra OCR 阅读顺序模型与版式块如何工作(快速指南)
从线条到结构化文档: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)。它不输出任何坐标,只做三件事:
- 成对排序:判断任意两行"谁在前、谁在后";
- 行分类:给每行打 10 种类别之一的概率;
- 同块判断:判断两行是否属于同一个版式块。
理解这三件事,就理解了 Indra OCR 如何"读懂"版面。
阅读顺序模型:不排序,先问"谁在前"
直觉的做法是给每行打一个"序号分"再排序,但多栏页面、脚注、侧边文字会让这种线性打分失效。Indra OCR 的 order.onnx 用的是成对比较:输入每行 16 个特征(10 个几何特征 + 6 个文本特征,定义在 indra_ocr/order_post.py 中):
- 几何:行框在页面上的相对坐标、宽高比、行高相对中位数的偏差;
- 文本:文本长度、数字占比、是否以项目符号/编号开头、是否以句末标点结束、是否以大写字母开头、是否连字符断行。
模型输出一个 [N, N] 的 logit 矩阵,a[i, j] > 0 表示"第 i 行在第 j 行之前"的概率更高。
解码:行和和 + 插入式局部搜索(decode_order()):
- 先用每行的"前于他人概率和 − 后于他人概率和"得到一个初始排列;
- 🔁 反复把每一行取出、插回到"使成对关系总概率最大"的位置,扫若干轮直到不再改进。
插入式搜索比相邻交换强得多:它能把一整段(比如某栏被排错的尾巴)一次性挪到远处正确位置,而逐次交换要挪很多步。
版式块:先聚块,再排块
有了"谁与谁同块"的判断后,decode_blocks_order() 分三步组装阅读顺序:
- 聚类成块:对"同块"logit 做平均链接聚类——把两两平均概率高于 0.5 的组不断合并。与连通分量不同,一两条误判不会把两个大块误并;
- 块内定序:普通块按 XY-cut(找最宽的空白间隙递归切分);Table、Form、Table-Of-Contents 这类网格块则按"从上到下、从左到右"逐行读;
- 块间排序:块与块之间取"平均成对排序 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保留。
如果表头模型判断"同一单元格"的置信度足够,还可以直接用行/列/单元格三个头的 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)。
查看版式块与阅读顺序: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。
性能与局限:快速,但要选对场景
在 32 核 CPU 上约 44 页/秒,约为 Tesseract 的 15 倍、PaddleOCR 文本模型的 10 倍。不过它面向干净印刷体优化,以下场景不适合:
- 复杂表格(合并单元格、跨行跨列、无线框)可能把单元格放错行列;
- 手写页可读但不准(连笔和淡铅笔最弱);
- 老旧、褪色、低分辨率扫描件表现较差。
这类高难度文档,官方建议走 Datalab 平台上更大的模型。
小结
Indra OCR 的"结构感"来自一个极小的 order.onnx:成对排序 + 插入式搜索决定阅读顺序,平均链接聚类 + 10 类行分类决定版式块,几何规则(XY-cut、表格行列对齐)兜底并负责最终渲染。这套设计让它只有几 MB 的模型体积,也能在纯 CPU 上把多栏论文、带表格的期刊页读成结构正确的 Markdown。




