OCR + LLM复合系统如何实现票据解析高精度字段结构化

OCR + LLM 复合系统:票据解析高精度字段结构化

大家好,今天我们来深入探讨如何利用 OCR(光学字符识别)和 LLM(大型语言模型)构建复合系统,实现票据解析的高精度字段结构化。在数字化转型的浪潮下,票据电子化已经成为必然趋势。然而,大量的历史票据和现实场景中,票据格式复杂多样,人工处理效率低下且容易出错。因此,构建一个能够自动、准确地解析票据并提取关键信息的系统至关重要。

1. 问题定义与挑战

票据解析的目标是从图像或 PDF 形式的票据中提取关键字段,例如发票号码、日期、金额、供应商信息等,并将这些信息结构化地存储,以便后续的业务处理和分析。这个过程面临以下挑战:

  • 版面复杂性: 票据的版面设计千差万别,字段位置不固定,表格结构复杂。
  • 图像质量: 扫描质量、光照条件、倾斜角度等因素都会影响 OCR 的识别精度。
  • 噪声干扰: 票据上可能存在水印、盖章、手写批注等噪声,干扰字段提取。
  • 语义理解: 有些字段的识别需要结合上下文信息进行语义理解,例如判断 "总计" 后面跟随的数字为金额。
  • 多语言支持: 票据可能包含多种语言,需要支持多语言 OCR 和 NLP 处理。

2. 系统架构设计

为了应对上述挑战,我们设计如下的 OCR + LLM 复合系统架构:

graph LR
    A[输入图像/PDF] --> B(图像预处理)
    B --> C(OCR引擎)
    C --> D(文本块及位置信息)
    D --> E(版面分析)
    E --> F(LLM字段定位)
    F --> G(LLM字段内容提取与校正)
    G --> H(结构化数据)
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style H fill:#f9f,stroke:#333,stroke-width:2px

该架构主要包含以下几个模块:

  1. 图像预处理: 负责对输入图像进行清洗、增强和校正,提高 OCR 的识别精度。
  2. OCR 引擎: 将图像中的文本转换为可编辑的文本数据,并提供每个文本块的位置信息。
  3. 版面分析: 分析文本块之间的位置关系,识别表格、标题、段落等结构化元素。
  4. LLM 字段定位: 利用 LLM 理解票据的语义信息,确定目标字段在版面中的位置。
  5. LLM 字段内容提取与校正: 提取目标字段的内容,并利用 LLM 对提取结果进行校正和补全。
  6. 结构化数据: 将提取的字段信息按照预定义的格式进行结构化存储。

3. 关键技术实现

接下来,我们将详细介绍每个模块的关键技术实现,并提供相应的代码示例。

3.1 图像预处理

图像预处理的目标是改善图像质量,提高 OCR 的识别精度。常用的预处理技术包括:

  • 灰度化: 将彩色图像转换为灰度图像,减少颜色信息对 OCR 的干扰。
  • 二值化: 将灰度图像转换为黑白图像,突出文本信息。
  • 降噪: 消除图像中的噪声,例如高斯噪声、椒盐噪声等。
  • 倾斜校正: 校正图像的倾斜角度,使文本水平对齐。
  • 对比度增强: 增强图像的对比度,使文本更加清晰。
import cv2
import numpy as np

def preprocess_image(image_path):
    """
    图像预处理函数
    """
    image = cv2.imread(image_path)

    # 灰度化
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # 二值化 (使用 Otsu's method)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

    # 降噪 (使用中值滤波)
    denoised = cv2.medianBlur(thresh, 3)

    # 倾斜校正 (使用霍夫变换)
    coords = np.column_stack(np.where(denoised > 0))
    angle = cv2.minAreaRect(coords)[-1]
    if angle < -45:
        angle = -(90 + angle)
    else:
        angle = -angle

    (h, w) = image.shape[:2]
    center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, angle, 1.0)
    rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)

    return rotated

# 示例
preprocessed_image = preprocess_image("invoice.jpg")
cv2.imwrite("preprocessed_invoice.jpg", preprocessed_image)

3.2 OCR 引擎

OCR 引擎负责将图像中的文本转换为可编辑的文本数据,并提供每个文本块的位置信息。常用的 OCR 引擎包括:

  • Tesseract OCR: 开源 OCR 引擎,支持多种语言。
  • Google Cloud Vision API: 谷歌云提供的 OCR 服务,具有较高的识别精度。
  • Azure Computer Vision API: 微软云提供的 OCR 服务,功能丰富。
  • PaddleOCR: 百度飞桨提供的 OCR 工具包,支持多种模型和语言。

这里我们以 Tesseract OCR 为例,演示如何使用 Python 进行 OCR 识别。

import pytesseract
from PIL import Image

def ocr_image(image_path):
    """
    使用 Tesseract OCR 识别图像中的文本
    """
    image = Image.open(image_path)
    text = pytesseract.image_to_string(image, lang='chi_sim') # 可更改lang参数支持其他语言
    return text

# 获取文本块及其位置信息
def get_bounding_boxes(image_path):
    """
    获取文本块及其位置信息
    """
    image = Image.open(image_path)
    data = pytesseract.image_to_data(image, output_type=pytesseract.Output.DICT, lang='chi_sim')
    return data

# 示例
text = ocr_image("preprocessed_invoice.jpg")
print(text)

bounding_boxes = get_bounding_boxes("preprocessed_invoice.jpg")
print(bounding_boxes)

get_bounding_boxes 函数返回一个字典,包含文本块的 level(层级,如 paragraph, word, symbol)、page_numblock_numpar_numline_numword_numlefttopwidthheightconf(置信度)和 text 等信息。

3.3 版面分析

版面分析的目标是分析文本块之间的位置关系,识别表格、标题、段落等结构化元素。常用的版面分析技术包括:

  • 基于规则的方法: 根据文本块的位置、大小和对齐方式等特征,定义一系列规则来识别版面结构。
  • 基于聚类的方法: 将文本块按照位置关系进行聚类,例如将相邻的文本块聚类成行或列。
  • 基于深度学习的方法: 使用深度学习模型学习版面结构,例如使用 CNN 或 Transformer 模型进行版面分割和识别。

这里我们以基于规则的方法为例,演示如何识别表格结构。

def detect_tables(bounding_boxes, threshold_x=10, threshold_y=5):
    """
    基于规则检测表格
    """
    words = []
    n_boxes = len(bounding_boxes['level'])
    for i in range(n_boxes):
        if bounding_boxes['level'][i] == 5: # Word level
            words.append({'text': bounding_boxes['text'][i],
                          'left': bounding_boxes['left'][i],
                          'top': bounding_boxes['top'][i],
                          'width': bounding_boxes['width'][i],
                          'height': bounding_boxes['height'][i]})

    # 按行分组
    rows = {}
    for word in words:
        row_id = word['top'] // threshold_y # Use integer division to group words into rows
        if row_id not in rows:
            rows[row_id] = []
        rows[row_id].append(word)

    # 对每一行中的词按列排序
    for row_id in rows:
        rows[row_id].sort(key=lambda x: x['left'])

    # 检测列
    columns = []
    for row_id in rows:
        for i, word in enumerate(rows[row_id]):
            if i == 0:
                columns.append({'left': word['left'], 'right': word['left'] + word['width']})
            else:
                merged = False
                for j, col in enumerate(columns):
                    if abs(word['left'] - col['right']) < threshold_x:
                        columns[j]['right'] = word['left'] + word['width']
                        merged = True
                        break
                if not merged:
                    columns.append({'left': word['left'], 'right': word['left'] + word['width']})

    columns.sort(key=lambda x: x['left'])

    # 组装表格数据
    table = []
    for row_id in sorted(rows.keys()):
        row_data = []
        for col in columns:
            cell_text = ""
            for word in rows[row_id]:
                if col['left'] <= word['left'] and word['left'] + word['width'] <= col['right']:
                    cell_text += word['text'] + " "
            row_data.append(cell_text.strip())
        table.append(row_data)

    return table

# 示例
table = detect_tables(bounding_boxes)
for row in table:
    print(row)

3.4 LLM 字段定位

LLM 字段定位的目标是利用 LLM 理解票据的语义信息,确定目标字段在版面中的位置。具体实现步骤如下:

  1. 构建提示词 (Prompt): 根据目标字段的语义信息,构建一个提示词,例如 "请找出发票号码" 或 "提取总金额"。
  2. 输入 LLM: 将 OCR 识别结果和提示词输入 LLM。
  3. 获取字段位置: LLM 根据提示词,分析 OCR 识别结果,输出目标字段在版面中的位置信息,例如文本块的坐标或文本内容。

我们可以使用 OpenAI 的 GPT 模型或开源的 LLM 模型,例如 Llama 2 或 ChatGLM。这里我们以 OpenAI 的 GPT-3.5 模型为例,演示如何使用 Python 进行字段定位。

import openai
import os

# 设置 OpenAI API 密钥
openai.api_key = os.getenv("OPENAI_API_KEY")

def locate_field_with_llm(ocr_text, field_name):
    """
    使用 LLM 定位字段
    """
    prompt = f"从以下文本中提取 {field_name}:n{ocr_text}n{field_name}:"
    response = openai.Completion.create(
        engine="text-davinci-003", # 或者选择其他的模型,比如"gpt-3.5-turbo-instruct"
        prompt=prompt,
        max_tokens=50, # 根据需要调整
        n=1,
        stop=None,
        temperature=0.2, # 降低temperature可以提高一致性
    )
    extracted_text = response.choices[0].text.strip()
    return extracted_text

# 示例
ocr_text = ocr_image("preprocessed_invoice.jpg")
invoice_number = locate_field_with_llm(ocr_text, "发票号码")
total_amount = locate_field_with_llm(ocr_text, "总金额")

print(f"发票号码: {invoice_number}")
print(f"总金额: {total_amount}")

使用 GPT-3.5 模型需要付费,你可以根据自己的需求选择合适的模型。同时,为了提高字段定位的精度,可以对提示词进行优化,例如添加更多的上下文信息或约束条件。

3.5 LLM 字段内容提取与校正

LLM 字段内容提取与校正的目标是提取目标字段的内容,并利用 LLM 对提取结果进行校正和补全。具体实现步骤如下:

  1. 提取字段内容: 根据字段位置信息,从 OCR 识别结果中提取字段内容。
  2. 输入 LLM: 将提取的字段内容输入 LLM,并提供上下文信息,例如字段类型、单位等。
  3. 校正和补全: LLM 根据上下文信息,对提取结果进行校正和补全,例如将 "123.4" 校正为 "123.40",或将 "2023/12/3" 补全为 "2023年12月03日"。
def correct_and_complete_field(field_text, field_type, context=""):
    """
    使用 LLM 校正和补全字段内容
    """
    prompt = f"请校正和补全以下 {field_type}:n{field_text}n上下文:{context}n校正后的{field_type}:"
    response = openai.Completion.create(
        engine="text-davinci-003",
        prompt=prompt,
        max_tokens=50,
        n=1,
        stop=None,
        temperature=0.2,
    )
    corrected_text = response.choices[0].text.strip()
    return corrected_text

# 示例
invoice_number = "123456789"
corrected_invoice_number = correct_and_complete_field(invoice_number, "发票号码")

total_amount = "123.4"
corrected_total_amount = correct_and_complete_field(total_amount, "总金额", "单位:元")

print(f"校正后的发票号码: {corrected_invoice_number}")
print(f"校正后的总金额: {corrected_total_amount}")

3.6 结构化数据

结构化数据的目标是将提取的字段信息按照预定义的格式进行存储,例如 JSON、CSV 或数据库。可以根据具体的业务需求选择合适的存储格式。

import json

def save_to_json(data, output_path="output.json"):
    """
    将数据保存为 JSON 文件
    """
    with open(output_path, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=4)

# 示例
data = {
    "invoice_number": corrected_invoice_number,
    "total_amount": corrected_total_amount,
    # 其他字段...
}

save_to_json(data)

4. 优化策略

为了进一步提高票据解析的精度和效率,可以采用以下优化策略:

  • 模型微调: 使用特定领域的票据数据对 OCR 模型和 LLM 模型进行微调,提高模型在特定场景下的性能。
  • 数据增强: 通过对图像进行旋转、缩放、裁剪等操作,增加训练数据的多样性,提高模型的鲁棒性。
  • 集成学习: 将多个 OCR 引擎和 LLM 模型进行集成,利用集体的智慧提高整体性能。
  • 规则引擎: 结合规则引擎和 LLM,利用规则引擎处理简单的场景,利用 LLM 处理复杂的场景,提高整体效率。

5. 一些思考

通过结合 OCR 技术和 LLM 的强大能力,我们能够构建高精度的票据解析系统。不过,这仍然是一个不断发展的领域。以下是一些值得思考的方向:

  • 少样本学习: 如何在只有少量标注数据的情况下,快速构建高精度的票据解析系统?
  • 零样本学习: 如何在没有标注数据的情况下,利用 LLM 的知识迁移能力,实现票据解析?
  • 多模态学习: 如何结合图像、文本和布局信息,提高票据解析的精度和鲁棒性?
优化策略 描述 适用场景
模型微调 使用特定领域的票据数据对 OCR 模型和 LLM 模型进行微调,提高模型在特定场景下的性能。 票据类型固定,有一定量的标注数据。
数据增强 通过对图像进行旋转、缩放、裁剪等操作,增加训练数据的多样性,提高模型的鲁棒性。 图像质量不稳定,存在倾斜、模糊等问题。
集成学习 将多个 OCR 引擎和 LLM 模型进行集成,利用集体的智慧提高整体性能。 需要更高的识别精度,对计算资源要求不高。
规则引擎 结合规则引擎和 LLM,利用规则引擎处理简单的场景,利用 LLM 处理复杂的场景,提高整体效率。 票据结构相对固定,但存在一些复杂的语义信息需要处理。
Prompt优化 精心设计提示词,提供足够的上下文信息,引导 LLM 更好地理解和提取信息。 提升LLM的效果,不需要额外训练数据,成本较低。

未来,随着技术的不断发展,我们可以期待更加智能、高效的票据解析系统的出现,为企业的数字化转型提供强有力的支持。

提升系统的鲁棒性和泛化性

构建一个鲁棒性强、泛化性好的系统,需要考虑以下几个方面:

  • 多样化的训练数据: 使用来自不同来源、不同格式、不同质量的票据数据进行训练,使模型能够适应各种复杂的场景。
  • 领域自适应: 采用领域自适应技术,将模型从一个领域迁移到另一个领域,例如从发票解析迁移到银行账单解析。
  • 对抗训练: 使用对抗训练技术,生成对抗样本,提高模型的鲁棒性。

总而言之,构建一个高精度的票据解析系统是一个复杂而富有挑战性的任务,需要综合运用 OCR、LLM 和其他相关技术。希望今天的分享能够帮助大家更好地理解和应用这些技术,构建出更加智能、高效的票据解析系统。

代码的模块化与可维护性

为了保证代码的可维护性和可扩展性,我们需要对代码进行模块化设计,将不同的功能封装成独立的模块,并提供清晰的接口。例如,可以将图像预处理、OCR 识别、版面分析、LLM 字段定位和结构化数据存储等功能分别封装成独立的模块。

结论:迈向智能票据处理的下一步

通过结合 OCR 与 LLM 技术,我们能够显著提升票据解析的精度。模型微调、数据增强以及集成学习等策略能够进一步优化系统性能,应对各种复杂场景。未来,随着技术的持续演进,智能票据处理将更加高效、准确,为企业数字化转型提供有力支撑。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注