传统的 OCR 后处理是 Rule-based(基于规则) 的。 现在的 OCR 后处理是 Model-based(基于模型) 的。
为什么选 Qwen-2.5-7B?
- 中文理解强:处理国内票据、合同,Qwen 的中文语感目前开源界第一。
- 指令跟随(Instruction Following):你需要模型输出 JSON,而不是跟你聊天。Qwen-2.5 在 JSON Mode 上的表现极其稳定。
- 跑得动:7B 模型(Int4 量化)只需要 6G 显存。一张 T4 或者 4060 就能跑得飞起。
1. 核心思路:从 “Raw Text” 到 “Structured JSON”
我们不再把 OCR 结果看作字符串,而是把它看作 “含噪的语义流”。 我们的任务不是“替换字符”,而是 “提取实体”。
场景:你有一张名片,OCR 识别结果是一坨带换行符的烂泥。 目标:提取 {"name": "...", "phone": "...", "email": "..."}。
2. Prompt Engineering:不仅是提示,是约束
写给数据清洗的 Prompt,和写给 ChatGPT 聊天的 Prompt 完全不同。我们需要 Few-shot(少样本) + Constraints(约束)。
Prompt 模板示例:
Plaintext
你是一个专业的 OCR 数据清洗助手。
你的任务是根据输入的 OCR 原始文本,提取关键信息并修复明显的 OCR 错误(如把 1 识别成 l,把 0 识别成 O)。
约束:
1. 严格输出合法的 JSON 格式。
2. 如果字段不存在,返回 null,不要编造。
3. 自动修正电话号码和邮箱中的错别字。
4. 不要输出任何解释性文字,只输出 JSON。
示例输入:
"姓名:张三 电i话:I38-OOO-8888 邮@箱:zhang#example.c0m"
示例输出:
{
"name": "张三",
"phone": "1380008888",
"email": "zhang@example.com"
}
当前输入:
{ocr_raw_text}
3. 代码实战:使用 vLLM 进行极速推理
在生产环境,千万别用 HuggingFace 原生的 model.generate(),那太慢了。 我们要用 vLLM。它能把推理吞吐量提升 10 倍以上。
环境准备:
Bash
pip install vllm
Python 清洗脚本:
Python
from vllm import LLM, SamplingParams
import json
# 1. 加载模型 (Qwen-2.5-7B-Instruct-GPTQ-Int4)
# 显存占用 < 6GB,推理速度极快
llm = LLM(model="Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4", trust_remote_code=True)
# 2. 定义采样参数
# temperature=0 是必须的!数据清洗容不得随机性
sampling_params = SamplingParams(temperature=0, max_tokens=512)
def clean_ocr_data(raw_text_batch):
prompts = []
for text in raw_text_batch:
# 构造 Prompt (使用上面的模板)
prompt = f"""
任务:提取并清洗 OCR 文本。输出 JSON 包含 keys: ["invoice_code", "amount", "date"]。
修正 OCR 错误(如 'O'->'0', 'I'->'1', '¥'->'')。
输入文本:
{text}
JSON 输出:
"""
prompts.append(prompt)
# 3. 批量推理 (Batch Inference)
# vLLM 最强的地方在于 Continuous Batching
outputs = llm.generate(prompts, sampling_params)
results = []
for output in outputs:
generated_text = output.outputs[0].text
try:
# 尝试解析 JSON
# 实际工程中可能需要用正则表达式提取 {} 之间的内容再 parse
clean_json = json.loads(generated_text)
results.append(clean_json)
except json.JSONDecodeError:
# 兜底策略:如果 LLM 没吐出 JSON,记录日志人工处理
results.append({"error": "parse_failed", "raw": generated_text})
return results
if __name__ == "__main__":
# 模拟一批脏数据
dirty_data = [
"发票代码:O1234567 金额:¥I00.00 日期:2O23年IO月Ol日",
"No. 9876S432 Total: 5O.OO Date: 2023-11-II"
]
cleaned = clean_ocr_data(dirty_data)
import pprint
pprint.pprint(cleaned)
输出结果:
JSON
[
{
"invoice_code": "01234567",
"amount": "100.00",
"date": "2023-10-01"
},
{
"invoice_code": "98765432",
"amount": "50.00",
"date": "2023-11-11"
}
]
看,它不仅修复了 O/0 和 I/1 的混淆,还自动统一了日期的格式。这要是用正则写,得写到头秃。
4. 进阶玩法:JSON Mode (Structured Decoding)
如果你对输出格式有强迫症,可以用 Outlines 或 LMQL 这种库,强制 LLM 的输出符合特定的 Pydantic 结构。
这相当于给 LLM 戴上了“镣铐”,它想乱输出都不行,Token 的概率分布会被强制修正为符合 JSON 语法的 Token。
Python
# 伪代码:使用 outlines 强制结构化输出
import outlines
model = outlines.models.transformers("Qwen/Qwen2.5-7B-Instruct")
# 定义 schema
schema = """
{
"title": "Invoice",
"type": "object",
"properties": {
"amount": {"type": "number"},
"date": {"type": "string", "format": "date"}
},
"required": ["amount", "date"]
}
"""
# 生成器会被锁定,只能生成符合 schema 的 JSON
generator = outlines.generate.json(model, schema)
result = generator("清洗这段文本: ...")
5. 性能与成本分析
有人会问:“为什么不用 GPT-4o?它更聪明。”
- 隐私 (Privacy):发票、合同、身份证,这些数据你敢传给 OpenAI 吗?本地部署 Qwen-2.5,数据不出内网。
- 成本 (Cost):
- GPT-4o:处理 100 万张发票,Token 费用可能上千美元。
- Local Qwen:买张 4060Ti (¥3000),可以用三年。电费忽略不计。
- 延迟 (Latency):
- API 调用:网络波动 + 排队,平均 1s – 3s。
- vLLM 本地:Qwen-7B 处理一条数据仅需 50ms – 100ms。
总结
OCR 识别出来的只是字符,只有经过清洗和结构化,它才能变成数据。
以前我们用正则表达式去“对抗”OCR 的错误,现在我们用 SLM(小语言模型)去“理解”OCR 的意图。
- 对于 通用格式(身份证、车牌),继续用传统规则,那是毫秒级的,没必要上 LLM。
- 对于 非标长尾(各国发票、古怪的合同、手写笔记),Qwen-2.5 + vLLM 是目前工程落地的最佳实践。
别再跟 re.compile 死磕了,把这活儿交给 7B 模型吧。