传统的 OCR 后处理是 Rule-based(基于规则) 的。 现在的 OCR 后处理是 Model-based(基于模型) 的。

为什么选 Qwen-2.5-7B?

  1. 中文理解强:处理国内票据、合同,Qwen 的中文语感目前开源界第一。
  2. 指令跟随(Instruction Following):你需要模型输出 JSON,而不是跟你聊天。Qwen-2.5 在 JSON Mode 上的表现极其稳定。
  3. 跑得动: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/0I/1 的混淆,还自动统一了日期的格式。这要是用正则写,得写到头秃。

4. 进阶玩法:JSON Mode (Structured Decoding)

如果你对输出格式有强迫症,可以用 OutlinesLMQL 这种库,强制 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?它更聪明。”

  1. 隐私 (Privacy):发票、合同、身份证,这些数据你敢传给 OpenAI 吗?本地部署 Qwen-2.5,数据不出内网。
  2. 成本 (Cost)
    • GPT-4o:处理 100 万张发票,Token 费用可能上千美元。
    • Local Qwen:买张 4060Ti (¥3000),可以用三年。电费忽略不计。
  3. 延迟 (Latency)
    • API 调用:网络波动 + 排队,平均 1s – 3s。
    • vLLM 本地:Qwen-7B 处理一条数据仅需 50ms – 100ms

总结

OCR 识别出来的只是字符,只有经过清洗和结构化,它才能变成数据

以前我们用正则表达式去“对抗”OCR 的错误,现在我们用 SLM(小语言模型)去“理解”OCR 的意图。

  • 对于 通用格式(身份证、车牌),继续用传统规则,那是毫秒级的,没必要上 LLM。
  • 对于 非标长尾(各国发票、古怪的合同、手写笔记),Qwen-2.5 + vLLM 是目前工程落地的最佳实践。

别再跟 re.compile 死磕了,把这活儿交给 7B 模型吧。