TL;DR:NaviDC-OCR 的关键不只是一个 1.2B 的 OCR/VLM 模型,而是一整套围绕“文档解析”重建的数据、训练和推理系统。论文层面,它用 Multi-node Consensus Voting、形变感知建模、CGDP 采样、自验证 VLM、四阶段渐进训练和内容-结构解耦学习,把数字文档与相机拍摄文档统一到一个框架里;代码层面,仓库把这套思路落到了一个清晰的推理链路:infer.py -> read_fn -> engine.py -> model_output_to_middle_json.py -> MagicModel -> vlm_middle_json_mkcontent.py。
1. 先说结论:NaviDC-OCR 解决的不是“识字”,而是“结构化还原”
很多人把 OCR 和文档解析混为一谈,但这篇工作实际瞄准的是更难的任务:
- 文本要读对;
- 阅读顺序要对;
- 表格拓扑要对;
- 公式语法要对;
- 图片、印章、页眉页脚不能乱;
- 数字文档和相机拍摄文档要统一处理。
现有方法要么强依赖 layout analysis,一遇到相机拍摄的几何畸变就级联失效;要么端到端生成看似省事,但容易冗余、幻觉、结构推理不足。NaviDC-OCR 的思路是把两类路线的优点拼起来:
- 保留 VLM 的统一生成能力;
- 引入几何感知,补两阶段方法最怕的畸变问题;
- 对表格和公式显式做内容-结构解耦;
- 用数据引擎和渐进训练把这件事做稳。
与主流模型在三个 Benchmark 上的对比指标如下:

2. 论文主线:一套不是“模型”,而是“系统”的方案
该项工作的整体主线如下:

2.1 模型结构:Qwen2.5-VL 视觉编码器 + Qwen3-0.6B 语言模型
NaviDC-OCR 不是直接拿完整的 Qwen2.5-VL 做 OCR 微调。 论文给出的模型组成更具体:它约有 1.2B 参数,由三部分构成:
| 组件 |
来源 / 结构 |
作用 |
| Vision Encoder |
inherited from Qwen2.5-VL |
提取页面图像中的视觉特征与版面线索 |
| Language Model |
Qwen3-0.6B |
生成文本、结构标记、坐标序列和任务输出 |
| Aligner |
从零训练的 MLP |
对齐视觉表示与语言模型表示空间 |
也就是说,NaviDC-OCR 更准确的结构是:Qwen2.5-VL 的视觉编码器 + Qwen3-0.6B 语言模型 + MLP Aligner。Qwen2.5-VL 系列模型中参数量最小的版本是 3B,在这里主要使用 3B 的模型提供视觉侧能力,而不是作为完整多模态模型被原样使用。
2.2 Multi-node Consensus Voting:伪标签不是“一个模型说了算”
论文的第一个关键点是 MCV。它不是单模型伪标签,也不是简单的多次推理投票,而是让异构模型互相投票,选择共识最高的结果。
核心形式可以写成:
[
si = \frac{1}{N-1}\sum{j \neq i} \text{sim}(p_i, p_j)
]
其中:
- (p_i):第 (i) 个模型的预测;
- (\text{sim}(\cdot)):不同任务对应的一致性度量;
- (s_i):该预测与其他模型的平均共识分数。
最后选:
[
p^* = \arg\max_i s_i
]
这个设计的意义很现实:文档解析里,错误常常不是随机噪声,而是系统性偏差。单模型生成的伪标签会把偏差一并放大;MCV 的目标,是尽量让“多模型都认可”的结果进训练集。
2.3 几何感知文档建模:把“矩形框”升级成“边界点”
这也是我感觉最有意思的一个点。传统两阶段方法最脆弱的一点,是默认布局区域近似矩形。但相机拍摄文档常常弯曲、折叠、透视畸变,矩形框会漏信息。NaviDC-OCR 的做法是:
- region-level:用多点边界表示局部区域;
- point-level:用控制点表示整页形变。
这相当于把 layout detection 变成了更适合畸变文档的 boundary / polygon 预测问题。这类工作在 PaddleOCRVL 中也有过,从文章对比来看 NaviDC-OCR 的效果更好。也就是说,这个模型在这种场景中更加擅长。
2.4 CGDP:NaviDC-OCR 的几何建模亮点
如果只看模型骨架,NaviDC-OCR 很容易被理解成“把 Qwen2.5-VL 用在 OCR / 文档解析上”。但真正让它区别于普通文档 VLM 的地方,是它认真处理了一个传统 OCR 系统经常绕开的细节:相机拍摄文档的版面区域并不总是矩形。
论文提出 Curvature-Guided Douglas-Peucker Sampling,CGDP,它是整篇文章里很值得重点看的设计。它解决的不是“识别什么文字”,而是“当页面被拍弯、拍皱、拍斜之后,模型应该如何用有限的坐标 token 表达一个变形区域的边界”。
传统 layout detection 常把区域表示成矩形框:
[x_min, y_min, x_max, y_max]
这对数字 PDF、扫描件或者轻微透视图像通常够用。但相机拍摄文档里经常出现:
- 页面整体弯曲,文字行呈弧形;
- 纸张局部折叠,边界出现尖角;
- 表格线、公式块或图片区域被非刚性拉伸;
- 页面边缘翘起,区域轮廓不再能被一个 bbox 贴合。
这时继续使用矩形 bbox,会把几何误差传递给后续流程:裁剪区域不准、阅读顺序错位、表格和公式结构重建失败。NaviDC-OCR 的做法是把 camera-captured layout segmentation 从矩形框检测扩展成 variable-length polygonal point sets:
<box:x1 y1 x2 y2 x3 y3 ...><label:category><rotate_dir>
问题随之变成:这些边界点应该怎么采? 这就是 CGDP 的位置。
蓝点表示普通轮廓采样点;红点表示因局部曲率较高而被 CGDP 提升优先级的候选点。
图:CGDP 将 DP distance 的大尺度轮廓保持能力,与 local curvature 的局部结构敏感性结合起来。
2.4.1 为什么 CGDP 可以成为文章亮点?
CGDP 的亮点不在于公式复杂,而在于它把 文档几何表达 和 VLM 生成成本 这两个原本分开的约束连在了一起。
如果边界点太少,模型看起来输出很简洁,但复杂区域会被粗糙近似:弯曲表格被裁成矩形,折痕附近的文本块边界被抹平。反过来,如果给所有区域都密集采样,简单页面也会产生大量坐标 token,VLM 的输出序列变长,格式错误、坐标漂移和截断风险都会上升。
所以 CGDP 实际上是在回答一个很工程的问题:
在固定的结构化输出预算里,哪些坐标点最值得让 VLM 学、让 VLM 生成?
这比“把 bbox 换成 polygon”更进一步。bbox 到 polygon 只是表示能力增强;CGDP 解决的是 polygon 表示里的 点预算分配问题。
2.4.2 均匀采样的问题:点数固定,但信息量不固定
最朴素的做法是沿区域边界等间隔采样。它有两个优点:实现简单,输出长度可控。但它隐含了一个很强的假设:边界上每个位置同等重要。这个假设在相机拍摄文档里并不成立:
| 边界区域 |
几何特征 |
均匀采样的问题 |
| 平直边缘 |
曲率低、形变弱 |
采太多点也只是重复描述直线 |
| 大范围弯曲 |
连续偏离矩形框 |
需要保留能表达整体弧度的点 |
| 折痕 / 尖角 |
局部方向突变、曲率高 |
等间隔点可能刚好错过关键拐点 |
| 局部翘边 |
空间范围小但影响裁剪 |
如果采样不足,后处理会裁偏 |
这也是论文批评 uniform boundary sampling 的核心:flat regions may contain redundant samples,而 high-curvature areas, such as corners and folds, may be under-sampled。
对文档解析来说,这不是一个“轮廓好不好看”的问题。边界点会影响后面的区域裁剪、layout 重建、阅读顺序组织,以及 Markdown / JSON 输出。因此,采样策略会间接影响最终文档结构质量。
2.4.3 bending 与 creasing
bending 和 creasing 是论文用来描述相机拍摄文档几何畸变的两种模式。

Bending 指的是大范围、连续、平滑的弯曲。可以理解为“整片区域被慢慢弯过去”:比如书页自然拱起、纸张整体弯曲、页面边缘形成平滑弧线,或者表格区域整体呈弧形偏移。它的空间范围通常较大,曲线相对首尾连线会产生明显偏离,因此更容易被 Douglas-Peucker distance 捕捉。
Creasing 指的是小范围、局部、方向突变的折痕或尖角。可以理解为“某个局部突然折了一下”:比如纸张折痕、页面局部翘起、边缘尖角、表格线局部急转。它的空间范围通常较小,即使局部曲率很高,相对首尾连线的最大距离也不一定大,因此普通 DP 可能把它当成可以忽略的细节。
| 维度 |
bending |
creasing |
| 直观理解 |
整段边界慢慢弯了 |
某个局部突然折了一下 |
| 空间范围 |
大范围 |
小范围 |
| 变化方式 |
连续、平滑 |
局部突变、方向不连续 |
| 几何表现 |
整体偏离矩形或直线 |
高曲率、尖角、折痕 |
| 更适合的度量 |
DP distance |
local curvature |
| 文档场景 |
书页拱起、纸张整体弯曲、边缘弧形 |
纸张折痕、局部翘边、表格线急转 |
所以,论文里的关键判断可以概括为:DP distance 更擅长捕捉 region-level bending;local curvature 更擅长捕捉 point-level creasing。 CGDP 的设计价值,就在于把这两个信号组合起来:用 DP distance 保留大尺度轮廓偏离,用 curvature 补上局部折痕和尖角。
2.4.4 DP 距离:擅长保留大尺度 bending
经典 Douglas-Peucker,DP 算法的目标是曲线简化。给定一段曲线的起点 (P_s) 和终点 (P_e),它用线段 (P_sP_e) 近似这段曲线,然后找出中间点里距离这条线段最远的点:
[
d_i = \text{dist}(P_i, P_sP_e)
]
如果最大距离超过阈值,就保留这个点,并以它为分割点递归处理左右两段;如果没有超过阈值,就认为这段曲线可以用首尾线段近似。
这套机制天然适合捕捉 bending:也就是论文所说的大尺度、连续形变。因为当页面边缘整体弯曲时,中间点会明显偏离首尾连线,DP distance 会变大,算法自然会保留这些点。但 DP distance 不是万能的。论文用一个近似关系说明了它的偏置:
[
d_{\text{DP}} \sim \kappa \cdot L^2
]
其中:
| 符号 |
含义 |
直观解释 |
| (d_{\text{DP}}) |
DP distance |
曲线相对首尾连线的最大偏离程度 |
| (\kappa) |
local curvature |
局部弯曲或方向变化强度 |
| (L) |
region scale |
当前递归段的尺度 / 弦长 |
这个公式最关键的地方是 (L^2)。它说明 DP distance 不只由曲率决定,还强烈受区域尺度影响。一个大范围缓慢弯曲的边界,即使曲率不高,也可能因为 (L^2) 很大而产生明显 DP distance;一个局部折痕即使曲率很高,也可能因为空间范围小而没有足够大的 DP error。
因此,DP 更偏向捕捉 region-level geometric deviations,也就是区域尺度上的整体偏离。
2.4.5 Creasing 的难点:局部很重要,但 DP error 可能不大
相机拍摄文档里还有另一类形变:creasing。它不是整页慢慢弯过去,而是局部方向突然发生变化,比如纸张折痕、尖角、边缘翘起、表格线局部弯折。
论文对 bending 和 creasing 的区分可以整理为:
| 形变类型 |
空间范围 |
几何表现 |
单靠 DP 是否可靠 |
更需要什么 |
| bending |
大 |
连续弯曲、整体偏移 |
通常可靠 |
DP distance |
| creasing |
小 |
局部方向不连续、高曲率 |
可能漏掉 |
local curvature |
这就是 CGDP 要补的缺口:DP 看的是“离当前线段有多远”,但 creasing 更关心“这里方向变得有多急”。
换句话说,DP distance 衡量的是区域级偏离,curvature 衡量的是点级结构重要性。两者不是替代关系,而是互补关系。
2.4.6 CGDP 公式:用曲率调制 DP 点的重要性
基于这个观察,论文提出 CGDP,用 curvature-aware modulation 调整 DP 点的重要性:
[
S_i = d_i \cdot (1 + \lambda \cdot \hat{\kappa}_i)
]
其中:
| 符号 |
含义 |
在算法里的作用 |
| (S_i) |
第 (i) 个候选点的采样重要性分数 |
用来决定该点是否值得成为递归节点 |
| (d_i) |
DP distance |
衡量相对当前线段的大尺度偏离 |
| (\hat{\kappa}_i) |
normalized local curvature |
衡量局部折痕、尖角、方向突变的重要性 |
| (\lambda) |
curvature modulation weight |
控制曲率项对采样优先级的放大程度 |
这个公式可以拆成两层理解:
- 基础分数是 (d_i):CGDP 没有丢掉 DP,因此仍然保留对大尺度 bending 的敏感性;
- 乘上 ((1 + \lambda \hat{\kappa}_i)):高曲率点会获得额外加权,从而提高 creasing、corners、folds 被采到的概率。
它有一个很好的退化性质:当局部曲率较低时,(\hat{\kappa}_i \to 0),于是:
[
S_i \approx d_i
]
这时 CGDP 近似退化为标准 DP。
递归节点的选择条件是:
[
\max_i S_i > \tau
]
当当前曲线段中最大的 (S_i) 超过阈值 (\tau),对应点就被选为新的 recursive node,然后继续递归分割边界。阈值 (\tau) 控制采样密度:阈值越低,越容易保留点;阈值越高,边界越容易被简化。
2.4.7 一个小例子:为什么高曲率点会被“救回来”
假设一段边界里有三个候选点:
| 候选点 |
场景 |
DP distance (d_i) |
normalized curvature (\hat{\kappa}_i) |
直觉 |
| A |
大范围弯曲中点 |
高 |
中 |
DP 本来就容易选中 |
| B |
平直边缘上的普通点 |
低 |
低 |
没必要保留 |
| C |
局部折痕尖点 |
中 |
高 |
普通 DP 可能漏掉,CGDP 会提高优先级 |
普通 DP 只比较 (d_i),因此更偏向 A。CGDP 比较的是:
[
S_i = d_i \cdot (1 + \lambda \hat{\kappa}_i)
]
如果 C 的曲率足够高,那么即使它的 DP distance 不是最大,也可能因为曲率调制项被提升为高优先级点。这个机制正好对应相机拍摄文档里的局部折痕:它的空间范围不大,但对区域裁剪和结构还原很重要。
2.4.8 算法流程:它本质上还是递归曲线简化
从算法上看,CGDP 可以理解成“把 DP 的最大距离准则替换成最大曲率调制分数准则”。伪代码如下:
def cgdp(points, start, end, tau, lambda_):
# 1. 用当前首尾点构造近似线段
line = segment(points[start], points[end])
best_score = -float("inf")
best_idx = None
# 2. 遍历当前曲线段中的候选边界点
for i in range(start + 1, end):
# region-level deviation: 大尺度几何偏离
d_i = distance_to_line(points[i], line)
# point-level structural importance: 局部结构重要性
kappa_hat_i = normalized_curvature(points, i)
# curvature-aware modulation
score_i = d_i * (1 + lambda_ * kappa_hat_i)
if score_i > best_score:
best_score = score_i
best_idx = i
# 3. 分数没有超过阈值,则当前段可被首尾线段近似
if best_score <= tau:
return [points[start], points[end]]
# 4. 否则保留最高分点,并递归处理左右两段
left = cgdp(points, start, best_idx, tau, lambda_)
right = cgdp(points, best_idx, end, tau, lambda_)
return merge_without_duplicate(left, right)

这段算法的复杂之处不在递归本身,而在评分函数的设计:它让一个点能否被保留,不再只取决于“离线段多远”,还取决于“是否处在局部结构变化剧烈的位置”。
2.4.9 它在 NaviDC-OCR 数据构造中的位置
用 CGDP 生成 region boundary points,让模型在训练阶段学习这种自适应 polygon 边界表达。到了推理阶段,模型不是再跑一遍 CGDP 去采样边界,而是直接生成已经学过的 variable-length boundary points。
NaviDC-OCR 会把模型输出转换成中间结构,再进入版面重建:
model output -> middle_json -> MagicModel -> markdown / layout pdf / content
在这个链路中,多点边界不是装饰信息,而会影响:
model_output_to_middle_json.py 如何承接模型生成的结构化区域;
cut_image_and_table 如何根据区域边界裁剪图像、表格等块;
MagicModel 如何组织 page_info、spans、para_blocks;
vlm_middle_json_mkcontent.py 如何把结构化中间结果还原成最终 Markdown。
因此,CGDP 的价值主要是在 训练层面,让模型学习更符合相机拍摄文档的 polygon 边界监督。
2.4.10 三种采样策略的差别
| 采样方式 |
采样依据 |
主要优点 |
主要问题 |
在 NaviDC-OCR 语境中的意义 |
| 均匀采样 |
等间隔点位 |
简单、稳定、实现成本低 |
平坦区域浪费点,高曲率区域可能采样不足 |
适合规则边界,不适合复杂相机拍摄形变 |
| DP |
点到线段距离 (d_i) |
能保留大尺度轮廓偏离 |
小尺度高曲率折痕可能被忽略 |
能表达 bending,但对 creasing 不够敏感 |
| CGDP |
(S_i = d_i(1+\lambda \hat{\kappa}_i)) |
同时关注全局轮廓和局部结构 |
依赖曲率估计,超参需要控制 |
更适合 variable-length polygonal point sets |
更直白地说:DP 关心“偏离直线多少”,CGDP 额外关心“这里弯得有多急”。
2.4.11 工程边界与局限
CGDP 的设计很有启发,但也不能过度解读。它至少有几个工程边界:
-
曲率估计依赖边界质量
如果原始边界点或合成形变边界本身有噪声,局部高曲率可能并不代表真实折痕,而只是标注抖动。此时曲率调制可能会放大噪声。
-
(\lambda)、(\tau) 和最大点数需要共同调节
(\lambda) 太小,CGDP 接近普通 DP;(\lambda) 太大,高曲率噪声会被过度保留。(\tau) 太低会导致 polygon 过长,增加 VLM 输出压力;(\tau) 太高又会丢失细节。
-
论文没有充分给出 uniform / DP / CGDP 的系统消融
从动机和公式看,CGDP 是合理的;但论文没有把采样策略单独做成非常完整的量化对比。因此更稳妥的表述是:CGDP 是一个强设计点,而不是已经被充分证明的唯一最优采样方案。
-
可变长度 polygon 对 VLM 解码仍然有压力
自适应采样减少了无效点,但复杂区域仍可能输出较长坐标序列。坐标格式、顺序一致性、截断控制和后处理鲁棒性仍然重要。
2.4.12 小结:CGDP 的一句话价值
CGDP 的本质是:
用 Douglas-Peucker 保留大尺度弯曲,用曲率项补足局部折痕和尖角,在有限坐标 token 预算下生成更有信息量的文档区域边界。
这也是为什么它值得作为亮点来讲。它把相机拍摄文档解析中的一个关键矛盾说清楚了:几何表达越细,VLM 生成成本越高;表示太粗,又会伤害裁剪和结构恢复。 CGDP 正是在这两者之间做了一个简洁而有效的折中。
论文中一些数据对比:


2.5 Self-Judgement VLM:把验证从 image-text 改成 image-to-image

这部分很容易被忽视,但很重要。作者发现,通用 VLM 做 zero-shot verification 并不可靠,所以把结构化输出先渲染成图像,再和原图做一致性判断。换句话说,验证对象从“文本”变成了“图像”。
这比直接问“这段 markdown 对不对”稳定得多,因为表格和公式的结构错误,最终都会反映到渲染结果里。
2.6 四阶段训练:先对齐,再感知几何,再解耦结构,再用 RL 收尾
主流模型训练的通用范式基础上,NaviDC-OCR 增加了几何改制的文档解析训练。NaviDC-OCR 的训练是四阶段:
- 基础 vision-language 对齐;
- 几何感知文档解析;
- 内容-结构解耦学习;
- GRPO 强化学习。
先让模型知道“页面里有什么”,再让它知道“页面怎么弯了”,再让它知道“表格和公式的结构怎么写”,最后再用任务指标去拧细节。

2.7 内容-结构解耦:表格和公式不是普通文本
这是论文另一个值得单独拿出来讲的点,这个做法也和现有的其他模型有所区别。
- 公式不能只学字符序列,还要学语法结构;
- 表格不能只学单元格文本,还要学拓扑;
- 科学图表到表格,本质上也是结构恢复。
这也是为什么它在 Sci-ImageMiner Challenge 上能拿到第一:模型不是把图里字抄出来,而是把结构一起拉回来。从开源代码来看主要是在训练阶段做了增强,训练端对现有的公式和 OTSL 语言进行拆分,就比较容易构建对应的结构性训练数据。
3. 从论文到代码:仓库到底把哪些东西落地了?
3.1 infer.py:最外层入口很朴素,但足够清楚
infer.py 做的事情非常工程化,构建了 CLI:
- 读取输入目录;
- 过滤掉
.json 和 .html;
- 通过
read_fn 统一把图片或 PDF 变成 PDF bytes;
- 调用
do_parse 或 aio_do_parse;
- 把结果写到输出目录。
3.2 NaviOCR/config.py:默认值就是服务端的现实约束
默认配置很能说明作者怎么想:
BACKEND = "vllm-async-engine"
LAYOUT_MODE = "Detection"
MAX_MODEL_LEN = 16384
GPU_MEMORY_UTILIZATION = 0.95
PDF_TOOLS = "pypdfium2"
PDF_TOOLS_WORKER_MAX_NUM = 4
MAX_PIXELS = 8000 * 8000
明确实际服务部署里的现实约束:长上下文、大图、PDF 解析、多线程 worker,都在这里落成了明确参数。
read_fn 逻辑很简单,也很关键:
- 如果是图片格式,就转成 PDF bytes;
- 如果本来就是 PDF,就直接读 bytes;
- 其他格式直接报错。
这样做的好处是,后面的解析管线只需要面对一种输入抽象:PDF bytes。
3.4 NaviOCR/engine.py:真正的解析主流程
doc_analyze / aio_doc_analyze 这两条路径几乎是整个系统的骨架:
_init_model() 从 NaviOCRMODEL_SERVICE 里取模型;
load_images_from_pdf(...) 把 PDF 变成 page images;
predictor.batch_two_step_extract(...) 做页面级推理;
result_to_middle_json(...) 把模型输出转成统一中间结构;
- 再由后处理导出 Markdown、middle json 和 layout bbox pdf。
3.5 NaviOCR/src/model_output_to_middle_json.py:把模型输出变成页面信息
这一步最有信息量。它做了三件事:
- 用
MagicModel(page_blocks, width, height) 规范化版面块;
- 对 IMAGE/SEAL/CHAR span 调
cut_image_and_table,裁剪出页面局部图;
- 组装出
page_info,最后形成 middle_json。
也就是说,模型输出不是最终结果,只是进入结构化后处理的原材料。
3.6 NaviOCR/src/vlm_middle_json_mkcontent.py:把结构化块拼回 Markdown
这是一个很有意思的地方,因为它不炫技,但很懂文档。
它对不同 block 的处理很细:
- 普通文本:
merge_para_with_text
- 标题:按层级前缀
#
- 列表:逐 item 拼接
- 图片:caption、body、footnote 三段合成
- 表格:优先保留 HTML
- 代码:用 fenced code block 包起来,并带上猜测语言
merge_para_with_text 里还有几个很实际的小细节:
- 全角转半角,但保留标点;
- 自动做语言检测;
- CJK 语言不乱塞空格;
- 英文遇到行尾连字符时尝试去断词;
- 行间公式和内联公式分开处理。
这类规则看起来琐碎,但恰恰决定了最终 Markdown 是否“像人写出来的”。
4. MagicModel:这份代码里最像“版面重建引擎”的东西
engine.py 像是流程调度,MagicModel 像是结构修复核心。
可以概括成三层:
- 坐标标准化:把归一化 bbox 转成像素坐标;
- 类型标准化:把原始 block 映射到内部
BlockType / ContentType;
- 结构重建:把 body、caption、footnote、list 重新绑定。
4.1 caption / footnote / body 的重组
仓库里的 magic_model_utils.py 用的是基于 index 的关联策略,再配合 bbox distance 作为 tie-breaker。这个设计比纯 IoU 更适合文档,因为很多 caption 并不严格和 body 重叠,而是沿阅读顺序邻近。
4.2 列表不是普通文本
fix_list_blocks 会把落在 list bbox 内、且 overlap 足够高的 text/ref_text 拉回列表块里。这个动作很重要,因为很多模型会把列表渲染成一堆散行正文;没有这一步,最终 Markdown 的结构感会差很多。
4.3 图片、表格、代码都被提升成“两层结构”
fix_two_layer_blocks 会把:
image_body + image_caption + image_footnote
table_body + table_caption + table_footnote
code_body + code_caption + code_footnote
重新绑定成一个语义单元。
这一步很适合文档解析:图片、表格、代码本来就不是孤立块,它们和说明文字、注脚是一体的。
5. 代码层最值得盯住的两个小细节
5.1 cut_image_and_table:裁图前先修 bbox
NaviOCR/tools/cut_image.py 里先检查 bbox 是否有效,再决定要不要裁图。若 bbox 超过 4 个点,会先合并成外接矩形。这意味着它能同时兼容普通框和多点轮廓。
这和论文里的“几何感知”是能对上的:多点边界不是摆设,后处理真的要吃得进去。
5.2 NaviOCR/vlm_utils/NaviOCR_model.py:推理后端是可切换的
模型服务封装支持:
transformers
vllm-engine
vllm-async-engine
而默认很明显偏向 vLLM 异步引擎。
6. vLLM 插件告诉我们什么?
由于模型是组合的(qwen2.5vl 视觉模块 + align mlp + qwen3 0.6b),对于 vllm 这类框架就没有对应的模型,需要进行定制化。仓库里的 NaviOCR-vllm/NaviOCR_vllm/qwen2_5_vl.py 不是单纯复制原版模型,而是一个 out-of-tree plugin。它的关键变化是:
- 保留 Qwen2.5-VL 的视觉塔和多模态处理;
- 语言模型侧接到
Qwen3ForCausalLM;
- 适配多模态占位符、权重映射和异步推理。
7. 结果怎么看才不失真?
论文和 README 的结果很强,但解读时要分场景。先看看几个指标:



7.1 数字文档:整体很稳
论文在 OmniDocBench v1.6 上的 Overall 是 96.87,文本、公式、表格、阅读顺序都很强。这说明它不是只偏某一项,而是整体结构还原能力比较均衡。
7.2 相机拍摄文档:明显比很多 pipeline 更抗畸变
在 Wild-OmniDocBench 上它拿到 88.53,说明几何感知和自适应采样确实在起作用。
7.3 真实退化:不是绝对无敌
在 PureDocBench 的 Real track 上,NaviDC-OCR 不是全场第一。这很重要,因为它提醒我们:
- 端到端方案很强;
- 但对真实拍摄退化、极端噪声、复杂手写场景,还不是终局答案。
7.4 复杂表格:这是它最有说服力的胜点之一
Complex-table subset 上,它的 missing rate 很低,说明端到端建模确实减少了 layout 漏检带来的不可恢复错误。从 OmniDocBench v1.6 表格结构和表格内容指标来看很强。与一些同类模型对比图如下:



8. 总结
NaviDC-OCR 的真正贡献,不是“又做了一个 OCR 模型”,而是把文档解析拆成了一条完整链路:
- 用 MCV 和合成数据把训练标签做稳;
- 用几何感知和 CGDP 让模型读得懂相机拍摄文档;
- 用内容-结构解耦让表格和公式不再只是字符序列;
- 用
MagicModel、middle_json 和 Markdown 重建把模型输出变成工程可用结果。
所以它的本质更接近一句话:
不是把整页图像“识字”,而是把整页文档“恢复成结构”。
References
- NaviDC-OCR 论文:arXiv:2608.12898
- NaviDC-OCR 仓库:https://github.com/caipeng328/NaviDC-OCR
- 论文摘要与 HTML 正文中提取的 MCV、CGDP、Self-Judgement、四阶段训练与内容-结构解耦信息
- 仓库代码文件:
infer.py、NaviOCR/engine.py、NaviOCR/src/model_output_to_middle_json.py、NaviOCR/src/vlm_middle_json_mkcontent.py、NaviOCR/src/vlm_magic_model.py、NaviOCR/tools/cut_image.py、NaviOCR/vlm_utils/NaviOCR_model.py、NaviOCR-vllm/NaviOCR_vllm/qwen2_5_vl.py