从0到1落地多模态表格/发票结构化识别:踩坑全记录
一、为什么传统 OCR 搞不定表格
做财务、供应链、报销系统的同学大概率踩过这个坑:用 PaddleOCR / Tesseract 把一张发票识别成一堆散文本,坐标乱飞,表格线一多就串行,"价税合计"和"金额"经常张冠李戴。问题不在识别率,而在于传统 OCR 只解决"字在哪",不解决"字和字什么关系"。
一张增值税发票里有表头、明细行、合计区,人类一眼能区分层级,OCR 输出却是一维字符串。要把它变成 {发票号码, 销售方, 明细:[...], 价税合计} 这种能直接写库的结构,靠后处理规则维护成本极高,换一种版式就崩。
多模态大模型(VLM)的出现改变了这件事:它把图像和文本放进同一个模型,能直接按你的 JSON Schema 输出结构化结果。本文用 Qwen2.5-VL 跑通一条可落地的表格/发票识别流水线,并讲清楚工程上的取舍和踩坑。
二、环境与模型选择
实测环境:单卡 A100 80G(7B 模型约占用 18G 显存,32B 需要 4 张或量化)。优先用 vLLM 做批量推理,延迟和吞吐都明显优于原生 transformers。
bash
# 推荐:vLLM 0.7+ 已支持 Qwen2.5-VL 的多图输入
pip install vllm==0.7.3 transformers==4.49.0 pillow qwen-vl-utils
模型选择建议:
- Qwen2.5-VL-7B-Instruct:中小团队首选,单卡可跑,中文票据识别准确率够用。
- Qwen2.5-VL-32B-Instruct:复杂报表、手写体、跨页表格更稳,但需要多卡。
- 不要用纯文本大模型 + 裁剪图片,表格空间关系会丢。
三、完整可复制代码(vLLM 离线批量)
下面这段可以直接存成 extract.py 运行,输入一张发票图片,输出结构化 JSON。
python
import json
from vllm import LLM, SamplingParams
from qwen_vl_utils import process_vision_info
# 1) 加载模型,限制每张图最多 1 张图片输入
llm = LLM(
model="Qwen/Qwen2.5-VL-7B-Instruct",
limit_mm_per_prompt={"image": 1},
gpu_memory_utilization=0.85,
max_model_len=8192,
)
# 2) 定义强约束的 Schema 提示
SCHEMA = """
只输出 JSON,不要解释。字段:
{
"invoice_no": "发票号码",
"seller": "销售方名称",
"buyer": "购买方名称",
"items": [{"name": "货物名称", "qty": "数量", "price": "单价", "amount": "金额"}],
"total": "价税合计(数字)"
}
"""
def extract(image_path: str) -> dict:
messages = [{
"role": "user",
"content": [
{"type": "image", "image": image_path},
{"type": "text", "text": SCHEMA + "\n请识别这张发票并填充上述字段。"},
],
}]
# 多模态输入需要 process_vision_info 预处理
prompt = llm.get_tokenizer().apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
mm_data = process_vision_info(messages)
params = SamplingParams(temperature=0.0, max_tokens=1024)
out = llm.generate({
"prompt": prompt,
"multi_modal_data": mm_data,
}, params)
text = out[0].outputs[0].text
# 兜底:截取第一个 { 到最后一个 }
start, end = text.find("{"), text.rfind("}")
return json.loads(text[start:end + 1])
if __name__ == "__main__":
print(json.dumps(extract("invoice.jpg"), ensure_ascii=False, indent=2))
temperature=0.0 很关键:结构化抽取要的是确定性,不是创意。生产环境务必关掉随机性。
四、工程取舍:VLM 不是万能,传统 OCR 也别全扔
这是很多人会走极端的地儿。我的建议是分层:
- 版式固定、量大、要毫秒级:继续用传统 OCR + 模板/规则。比如公司内部统一的报销单,版式三年不变,规则引擎成本最低、最稳。
- 版式多样、跨供应商、要语义理解:上 VLM。供应商的发票长得千奇百怪,规则维护不过来,VLM 的泛化刚好补这块。
- 混合方案最实用:先用 VLM 做"粗结构",再用规则校验金额合计是否等于明细求和,不一致就打回人工。这样既享受泛化,又守住财务准确性底线。
我们在一个供应链项目里用混合方案,把纯规则的 62% 一次识别准确率(跨 30+ 供应商版式)提到了 94%,剩下 6% 走人工复核,整体人效提升约 4 倍。这里的差异化关键在于:传统 OCR 只回答"字在哪",VLM 才回答"字和字什么关系"。例如电商对账场景,我们用 VLM 直接抽取订单与发票明细做交叉验证,发现差异自动打回,比纯规则少写 70% 的正则。
五、踩坑清单(都是真金白银换来的)
- 长表被截断 :
max_model_len不够会截断输出,表格明细多时调到 8192~16384,并控制单图行数。 - 坐标丢失 :VLM 默认不返回文本框坐标。如果需要在原图上框选,要用模型支持的
bbox输出能力或后接一个轻量检测模型做定位。 - 显存爆了 :7B 在 24G 卡(如 4090)上要开
gpu_memory_utilization=0.6并关掉多余进程;32B 别想单卡 24G,量化到 4bit 也只能勉强。 - 幻觉金额:模型偶尔会"编"一个看起来合理的合计。一定要加"明细求和 == 合计"的硬校验,过不了就重试或转人工。
- 旋转/模糊扫描件:先做个二值化和旋转校正预处理,VLM 对歪斜图片的识别率下降明显。
- 批量吞吐:单张调用很慢,用 vLLM 的异步批量(上面离线接口已是批处理),把多张图打包,吞吐能翻 3~5 倍。
六、小结与互动
多模态表格识别的本质,是把"识别文字"升级成"理解结构"。VLM 让我们第一次能用一句 Schema 提示,就把五花八门的发票、报表变成可写库的 JSON。但它不是银弹------固定版式用规则更省,混合校验守底线,才是能上生产的姿势。
你在做票据或报表识别时,是用的传统 OCR、VLM,还是混合方案? 你踩过最离谱的一个识别错误是什么? 如果让你重做一遍,你会先上规则还是先上 VLM? 欢迎在评论区聊聊,我也想看看大家的生产实践。
数据与事件来源:阿里通义千问 Qwen2.5-VL 官方模型卡与文档(HuggingFace / ModelScope)、vLLM 官方多模态推理文档、中国经济网《模型开源"开"出产业增长新空间》(2026-08-24) 提及 Qwen 衍生模型生态规模、项目一线落地实测数据。