by 雪隐_上班了 from juejin.cn/user/143341...
欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权。
前言:我拿 VLM 扫了 600 页书,然后悟了
先说结论------Unlimited-OCR 是我目前用过单页效果最强的文档解析工具,没有之一。 但如果你听信某些教程,去试它所谓的"多页批处理"接口,那大概率会在第 3 页就看着显存飙红、程序崩掉。正确且唯一有效的姿势,就是写个 for 循环,一页一页地喂,让它每页都"失忆"一次。 我就是用这种"笨方法",在 5060 Ti 16G 上成功啃完了 600 多页排版爆炸的技术书,输出 Markdown 质量吊打 PaddleOCR 十条街。
下面把我的血泪经验全摊开,包括为什么必须逐页循环、怎么把 600 页跑稳、以及合并脚本怎么写,顺便吐槽一下那些"伪多页"的坑。
第一铁律:OCR 就是 OCR,别让它干编辑的活
这条是我用爆显存换来的教训,必须写在最前面:
OCR 只负责"抄写",别在 prompt 里加"总结"、"纠错"、"翻译"等任何私货。
你敢让它多嘴,它就敢让你显存原地升天。
PaddleOCR 这么干过,崩了;Unlimited-OCR 我也试过,加了 "请修正错别字",单页推理从 5 秒变 30 秒,显存从 10G 飙到 15.8G,第三页直接 OOM。所以我的 prompt 永远焊死成官方原版:
python
prompt='<image>document parsing.' # 多一个单词都不要!
错别字、逻辑问题,那是人眼和 LLM 的事,别在 OCR 这一步瞎操心。
为什么我抛弃 PaddleOCR,死磕 Unlimited-OCR?
PaddleOCR 快是快,但面对多栏、表格、公式,它吐出来的就是一坨"线性文字"------左栏读到一半跳右栏,表格变成空格分隔的灾难,公式全成乱码。我花在"修复结构"上的时间,比跑 OCR 本身还多。
而 Unlimited-OCR 是端到端的 VLM,直接输出带 Markdown 结构的文本:
- 标题自动分层(
#、##、###) - 表格转成标准 Markdown 表格
- 公式转成 LaTeX 风格(
$...$) - 多栏自动分左右,不串行
单页效果简直是艺术品。但问题来了------怎么把艺术品安全地拼成 600 页的"连环画"?
大坑:官方"多页模式"是个纸老虎
我在网上看到有人吹 Unlimited-OCR 可以直接传 PDF 路径,内部自动处理多页。于是我也试了试,结果:
- 显存爆炸:一次性加载所有页面,16G 根本 hold 不住,跑到第 10 页就 OOM。
- 输出混乱:即使侥幸跑完,输出的 Markdown 把所有页面揉在一起,标题序号错乱、表格断页、公式跨页分裂,根本没法用。
- 速度感人:内部 batch 推理没做优化,比单页循环还慢。
后来翻源码才明白,官方那个"多页"其实就是把 PDF 转成图片列表,然后 for 循环调用同一个模型------但它没有做任何显存管理和输出聚合,效果还不如我们自己写循环。所以我的结论是:
Unlimited-OCR 的"单页模式"才是唯一正确的打开方式。多页场景请自己写循环,别指望官方黑盒。
我的"笨方法":逐页循环,稳如老狗
既然官方靠不住,那就自己动手。我的完整流水线如下:
1. PDF 转图片(用 PyMuPDF 或 pdf2image)
python
import fitz # PyMuPDF
pdf = fitz.open("book.pdf")
for page_num in range(len(pdf)):
page = pdf[page_num]
pix = page.get_pixmap(dpi=200)
pix.save(f"page_{page_num+1:03d}.png")
2. 逐页调用 Unlimited-OCR(千万别开任何"批处理"参数)
python
for page_num in range(1, total_pages+1):
img_path = f"page_{page_num:03d}.png"
model.infer(
tokenizer,
prompt='<image>document parsing.', # 焊死!
image_file=img_path,
output_path=f"./output/page_{page_num:03d}",
base_size=1024,
image_size=640,
crop_mode=False, # 书本排版整齐,关掉更省显存
max_length=32768,
no_repeat_ngram_size=35,
ngram_window=128,
save_results=True,
)
# 每页跑完清一下缓存,防止碎片堆积
torch.cuda.empty_cache()
关键点:
- 每页独立输出一个
.md文件,文件名带页码。 crop_mode我关掉了,因为书本排版规整,切块反而容易出边界幻觉,而且费显存。- 每页推理完手动清缓存,保证显存曲线平稳。
3. 合并脚本(灵魂所在)
光输出一堆碎片文件没用,必须合并成一本完整的 Markdown。我的合并脚本做了这几件事:
- 按页码顺序读取所有
.md文件。 - 重新编号标题 :因为每页的
#都从 1 开始,所以要全局统计,把# 1.变成# 1.,第二页的# 1.自动变成# 2.,子标题同理。 - 拼接跨页表格 :如果上一页末尾是
|---且下一页开头是|---,说明表格被切断,合并成一个大表格。 - 处理图片引用 :把每页内的图片路径统一改成相对路径(比如
)。
整套脚本跑完,600 页直接变成一个 5MB 的 book.md,导入 Obsidian 后大纲清晰,搜索公式、跳转章节丝般顺滑。
为什么逐页循环反而比"批处理"更好?
- 显存可控:每页处理完释放,不会累积。
- 输出干净:每页独立,合并脚本可以精准控制标题编号和跨页元素。
- 容错性强:万一某页崩了,只需要重跑那一页,不用全部重来。
- 可以并行:如果你有多张卡,可以分页并行跑,但我一张 5060 Ti 串行也够用。
给后来者的终极忠告
- 千万别用官方任何"多页"或"批量"参数 ,那都是坑。老老实实
for循环。 - Prompt 打死不加戏 ,就
document parsing,别想着让 OCR 做总结。 - 合并脚本值得投资,写一次,以后所有书都能复用。
- 5060 Ti 16G 在这个场景下非常够用,别被那些说"必须 A100"的吓到。
最后,为"单页循环"正名
有人说"逐页循环太 low,不够智能",但我想说------在 GPU 资源有限的情况下,稳定、可控、高质量,比花里胡哨的"原生多页"重要一万倍。 我用这套流水线处理了 600 页,成果是一份可以直接出版级的 Markdown 文档,而某些"高级批处理"可能连 50 页都跑不下来。
所以,如果你也想用 Unlimited-OCR 处理多页文档,别犹豫,直接上循环。代码我都给你备好了,拿去抄就行。