MinerU 段落拼接实战指南:跨栏跨页智能处理完整拆解
【免费下载链接】MinerUTransforms complex documents like PDFs and Office docs into LLM-ready markdown/JSON for your Agentic workflows.项目地址: https://gitcode.com/GitHub_Trending/mi/MinerU
拿一篇 30 页的双栏学术论文跑一次普通的「PDF 转 Markdown」,你大概率会看到这样的翻车现场:第 3 页左栏的最后一行在页尾被拦腰截断,输出的 Markdown 里一段以逗号结尾,下一段却以小写字母开头;同一页的两栏还按横坐标左右拼接,左栏第 1 段后面直接跟着右栏第 1 段,阅读顺序彻底错乱。这就是双栏跨页文档在段落拼接上最典型的失败模式。
MinerU 是一个把 PDF 与 Office 文档转换成 LLM-ready Markdown / JSON 的开源文档解析项目,本文拆解它的「段落拼接」模块:如何用跨栏跨页智能处理,把断页的段落拼回完整句子、还原双栏阅读顺序、并让列表与公式上下文不被打散。
问题拆解:段落拼接到底难在哪
把翻车现场拆开看,其实是 4 个相互独立的子问题:
- 跨页截断(物理断裂):版面按页切分,页尾的半句话和页首的半句话属于两个不同的 block,坐标上再近也跨了页边界。
- 双栏顺序(坐标陷阱):按 x、y 全局排序会把左右栏交错混排,正确顺序应是「左栏读完再读右栏」。
- 边界误判(该合不合 / 不该合却合):页尾行恰好以句号结尾的段落会被错误合并;列表换行项又会被当成独立段落拆出去。
- 结构丢失:公式、表格、列表一旦被拆散,输出的 Markdown 里引用编号、表格行列和列表层级全部失效。
📌 注意:这四个问题分别发生在流水线的不同阶段,所以 MinerU 用「分阶段各治其病」的方式处理,而不是指望一个通用规则包打天下。
整体思路:从像素到 Markdown 的流水线
整条链路是单向分层流水线,每一层只依赖上一层的产物:
上图是典型双栏论文的布局检测结果:红框为文本块,绿框为公式块,编号即阅读顺序——左栏从上到下,再切到右栏。
几个关键分工:
- 布局层:检测模型(YOLO 家族的版面检测思路,项目内置 PP-DocLayoutV2,见 mineru/model/layout/)一次性给出「有哪些块」+「块之间什么顺序」。双栏顺序在模型输出阶段就解决了,下游拿到的 block 序列已是阅读序。
- 行级聚合:OCR 与公式识别只负责往块里填字,产出
line → span两级结构,并把 block 的 bbox 按实际行重新计算(避免检测框抖动影响后续几何判断)。 - 段落合并层:核心逻辑集中在 mineru/backend/pipeline/para_split.py,它把所有页面的 block 放进同一条序列处理——这正是跨页拼接得以成立的前提。
逐个击破核心难点
跨页段落怎么拼:6 个几何判定还原断开的句子
跨页拼接最巧妙的一点是利用了坐标系重置:上一页底部的 block,其 y 值接近页高;下一页顶部的 block,y 值又回到 0 附近。于是「续块顶边 y < 断块底边 y」这种在单页内不可能成立的倒挂关系,就成了跨页续接的天然信号。
光有坐标信号不够,__merge_2_text_blocks还要求 6 个条件同时满足才执行合并(以下为核心逻辑的伪代码精简):
LINE_STOP_FLAG = ('.', '!', '?', '。', '!', '?', ')', ')', '"', '”', ':', ':', ';', ';') def try_merge(next_head, prev_tail): """next_head=下一页顶部块, prev_tail=上一页底部块""" if (prev_tail.末行右缘贴齐块右边界 # 满行:句子写到版面边缘被截断 and not prev_tail.末行末span.endswith(LINE_STOP_FLAG) # 没有句末标点 and 两块宽度差 < 较窄块宽度 # 宽度相当:排除两栏误拼 and not next_head.首字符是数字或大写 # 排除新句子/编号开头 and next_head.top_y < prev_tail.bottom_y # 跨页坐标倒挂 and (next_head多行 or prev_tail多行)): # 排除单行标题类块 if 页码不同: 给 next_head 的每个 span 打 CROSS_PAGE 标记 prev_tail.lines += next_head.lines # 续行并入原段落尾部三类跨页内容的处理策略各不相同:
| 跨页类型 | 检测方式 | 处理策略 |
|---|---|---|
| 文本 | 坐标倒挂 + 满行 + 无句末标点(上述 6 条件) | 并入同一 block,续行 span 打CROSS_PAGE标记,空块标记删除防止重复输出 |
| 表格 | 表格结构连续性(mineru/utils/ 中的 table_continuation / table_merge) | 保持表格完整性,跨页行归并到同一表格结构 |
| 图像 | 图像本身是独立布局块 | 不参与文本合并,保持独立元素与原始顺序 |
双栏顺序怎么还原:最快做法是别自己排
很多人会想自己写「先按中点 x 分栏、再各自排序」的脚本,但 MinerU 的思路是把这个难题交给布局模型:PP-DocLayoutV2 这类检测模型在训练时就以阅读顺序为标签输出检测结果,双栏页天然产出「左栏从上到下 → 右栏从上到下」的序列。下游para_split拿到的preproc_blocks已经是阅读序,跨栏跳读问题在源头消解。
自己排序真正容易踩的坑其实出在合并阶段:如果两栏宽度不同,简单的「相邻就合」会把左栏末段和右栏首段拼到一起。跨页合并里的宽度相当条件(两块宽度差小于较窄块自身的宽度)就是为此设的护栏——栏宽差异大的双栏块会直接在这里被拦截。
段落边界怎么判定:行尾标点 + 几何特征四维度
段落切分的判定并不神秘,本质是问「上一块的最后一行,句子写完了吗」,用四个维度交叉验证:
- 行尾标点:末 span 以中英文句末标点(
LINE_STOP_FLAG)收尾 ⇒ 句子完整,保持独立;反之进入合并候选。 - 缩进与分组:
__process_blocks先用标题、行间公式等「硬分隔块」把连续文本块切成 group,只有同组内的块才互为合并候选——缩进层级不同、中间隔了标题的块永远不会被拼接。 - 几何连续性:满行检测(末行右缘贴齐块右边界)+ 宽度相当 + 多行要求,排除「恰好整句收尾」「两栏误配」「单行小标题」三类假阳性。
- 跨页标志:页码不同即打
CROSS_PAGE,供下游输出层感知这是一次跨页缝合。
行级聚合后的 span 粒度:红框为文本 span,蓝框为公式/特殊 span。所有边界判定都建立在这个粒度上。
说明一点:语义连续性目前是用「首字符大写/数字 ⇒ 新句」这类规则近似实现的,深度语义判定不在现有规则内(见文末展望)。
列表结构怎么认:靠缩进的「狗牙状」几何特征
列表在视觉上的指纹是「狗牙边」:每个条目首行左顶格,换行行向右缩进,右侧因此留出不规则的空白。__is_list_or_index_block统计的正是这组几何量:左顶格行数、右侧不顶格行数、行首/行尾数字占比、各行中点是否对齐。
| 列表类型 | 识别特征(视觉指纹) | Markdown 输出格式 |
|---|---|---|
| 有序列表 | ≥80% 行以数字开头或结尾,且左/右边缘基本顶格 | 1. item保留编号 |
| 无序列表 | 左顶格条目行 ≥2 行,换行行右侧留白(狗牙边) | - item每项一段 |
| 多级 / 居中列表 | 各行中点 x 基本对齐,两侧均不贴边 | 逐行保留,维持层级 |
| 目录型(INDEX) | 左右顶格 + 数字规则(条目 + 页码) | 逐行输出,禁止合并 |
两类块一旦确认为 LIST / INDEX,同类型块之间走专门的__merge_2_list_blocks:无条件并入、跨页同样打CROSS_PAGE标记。列表项换行行因此不会再被拆成孤立段落。
多语言细节处理:中文标点、英文大小写与日文混排
规则引擎在不同语言下的「敏感点」差异不小,代码里做了三处针对性处理:
- 双套句末标点:
LINE_STOP_FLAG同时收录中英文两套(。!?与.!?等),中文文档按。判句末,英文文档按.判,一套条件覆盖两种语言。 - 大写首字母是英文专属的「合并拦截器」:下一块首字符是大写即视为新句,拒绝合并。中文没有大小写之分,该条件自然失效——等效于中文文档合并更激进、英文文档更保守,恰好符合两种语言的行文习惯。
- 右侧留白阈值按语言分档:判定「换行行右侧是否留白」时,中文 / 日文 / 韩文用固定阈值
0.26 × 块宽(这类语言没有超长单词,留白稳定),其他语言则按块宽占页宽比例在0.26 ~ 0.36 × 块宽之间浮动。块的语言由detect_lang对块文本做检测后选定阈值档。
此外,竖排文本(VERTICAL_TEXT)走独立的合并分支__merge_2_vertical_text_blocks:把「行高」判据换成「行宽」判据,其余逻辑与横排对称——横竖排混排的旧文档由此也能正确拼接。
配置与调优:关键 yaml 项与性能优化
关键配置项示例(示意):
processing: max_batch_size: 10 # 批量页数:吞吐与显存之间的权衡 gpu_memory_limit: 8G # 显存上限 language: auto # 自动语言检测(决定上述阈值档位) output_format: markdown paragraph: cross_page: true # 启用跨页段落合并 two_column: true # 启用双栏阅读顺序处理性能侧的几个实际做法(可在 mineru/backend/pipeline/ 中对照):
- 批量推理:布局、OCR、公式识别均按批(
batch_*)推理,而非逐页调用,显存利用率与吞吐同时受益。 - 按语言分组批量 OCR:识别请求先按检测到的语言分桶再批量下发,避免同批内语言混杂导致的模型切换开销。
- 显存管理:阶段间主动清理不再需要的中间张量(
clean_vram),长文档不爆显存。 - 规则层零成本:段落合并本身是纯几何 + 标点规则,不引入额外模型推理,是整条流水线中最便宜的一段。
效果验证:双栏论文与多语言手册转换案例
案例 1:双栏学术论文(30 页,含跨页公式)
- 输入:第 3 页左栏末行「…the total cost, which」(逗号结尾,无句号);第 4 页首行「can be approximated in closed form.」;同页含双栏与编号公式。
- 输出:
…The total cost, which can be approximated in closed form.两行被拼回一句完整话,CROSS_PAGE标记让输出层知道此处发生过缝合;同页内容仍按「左栏 → 右栏」输出,公式编号与正文位置关系保持不变。
案例 2:中英混排技术手册
- 输入:中文段落跨页截断(断在逗号处);英文段落页尾断行、页首续行以小写字母开头;一个换行三行的无序列表项。
- 输出:
部署前需要准备模型缓存目录,再配置解析服务。 ← 中文跨页按。/,判定拼接完成 You can enable batch inference to improve throughput. ← 小写续行正确并入 - 一条换行三行的列表项保持为单项,不再拆成三个段落边界与展望
⚠️ 当前的合并判定是「标点 + 几何规则」体系,没有引入真正的语义理解,边界情况仍会误判:比如页尾行恰好以逗号结尾但句子其实完整、英文新段落以小写字母开头(某些排版的 continuation 风格)等。竖排长文、三栏及杂志式混排也不是现有规则的重点覆盖区。
后续可以推进的方向:用语义模型判断段落连续性来替换部分硬阈值(大写、宽度比例等),为极端版式提供兜底;跨页图像的切分与还原;以及面向长文档的增量解析,只重算发生变化的页面。
【免费下载链接】MinerUTransforms complex documents like PDFs and Office docs into LLM-ready markdown/JSON for your Agentic workflows.项目地址: https://gitcode.com/GitHub_Trending/mi/MinerU
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考