Files
invoice-master-poc-v2/README.md
Yaojia Wang 58bf75db68 WIP
2026-01-27 00:47:10 +01:00

23 KiB
Raw Blame History

Invoice Master POC v2

自动发票字段提取系统 - 使用 YOLOv11 + PaddleOCR 从瑞典 PDF 发票中提取结构化数据。

项目概述

本项目实现了一个完整的发票字段自动提取流程:

  1. 自动标注: 利用已有 CSV 结构化数据 + OCR 自动生成 YOLO 训练标注
  2. 模型训练: 使用 YOLOv11 训练字段检测模型
  3. 推理提取: 检测字段区域 → OCR 提取文本 → 字段规范化

当前进度

指标 数值
已标注文档 9,738 (9,709 成功)
总体字段匹配率 94.8% (82,604/87,121)

各字段匹配率:

字段 匹配率 说明
supplier_accounts(Bankgiro) 100.0% 供应商 Bankgiro
supplier_accounts(Plusgiro) 100.0% 供应商 Plusgiro
Plusgiro 99.4% 支付 Plusgiro
OCR 99.1% OCR 参考号
Bankgiro 99.0% 支付 Bankgiro
InvoiceNumber 98.9% 发票号码
InvoiceDueDate 95.9% 到期日期
InvoiceDate 95.5% 发票日期
Amount 91.3% 金额
supplier_organisation_number 78.2% 供应商组织号 (CSV 数据质量问题)

运行环境

本项目必须WSL + Conda 环境中运行。

系统要求

环境 要求
WSL WSL 2 + Ubuntu 22.04
Conda Miniconda 或 Anaconda
Python 3.10+ (通过 Conda 管理)
GPU NVIDIA GPU + CUDA 12.x (强烈推荐)
数据库 PostgreSQL (存储标注结果)

功能特点

  • 双模式 PDF 处理: 支持文本层 PDF 和扫描图 PDF
  • 自动标注: 利用已有 CSV 结构化数据自动生成 YOLO 训练数据
  • 多策略字段匹配: 精确匹配、子串匹配、规范化匹配
  • 数据库存储: 标注结果存储在 PostgreSQL支持增量处理和断点续传
  • YOLO 检测: 使用 YOLOv11 检测发票字段区域
  • OCR 识别: 使用 PaddleOCR v5 提取检测区域的文本
  • 统一解析器: payment_line 和 customer_number 采用独立解析器模块
  • 交叉验证: payment_line 数据与单独检测字段交叉验证,优先采用 payment_line 值
  • 文档类型识别: 自动区分 invoice (有 payment_line) 和 letter (无 payment_line)
  • Web 应用: 提供 REST API 和可视化界面
  • 增量训练: 支持在已训练模型基础上继续训练
  • 内存优化: 支持低内存模式训练 (--low-memory)

支持的字段

类别 ID 字段名 说明
0 invoice_number 发票号码
1 invoice_date 发票日期
2 invoice_due_date 到期日期
3 ocr_number OCR 参考号 (瑞典支付系统)
4 bankgiro Bankgiro 号码
5 plusgiro Plusgiro 号码
6 amount 金额
7 supplier_organisation_number 供应商组织号
8 payment_line 支付行 (机器可读格式)
9 customer_number 客户编号

DPI 配置

重要: 系统所有组件统一使用 150 DPI,确保训练和推理的一致性。

DPI每英寸点数设置必须在训练和推理时保持一致否则会导致

  • 检测框尺寸失配
  • mAP显著下降可能从93.5%降到60-70%
  • 字段漏检或误检

配置位置

组件 配置文件 配置项
全局常量 src/config.py DEFAULT_DPI = 150
Web推理 src/web/config.py ModelConfig.dpi (导入自 src.config)
CLI推理 src/cli/infer.py --dpi 默认值 = DEFAULT_DPI
自动标注 src/config.py AUTOLABEL['dpi'] = DEFAULT_DPI
PDF转图 src/web/api/v1/admin/documents.py 使用 DEFAULT_DPI

使用示例

# 训练使用默认150 DPI
python -m src.cli.autolabel --dual-pool --cpu-workers 3 --gpu-workers 1

# 推理默认150 DPI与训练一致
python -m src.cli.infer -m runs/train/invoice_fields/weights/best.pt -i invoice.pdf

# 手动指定DPI仅当需要与非默认训练DPI的模型配合时
python -m src.cli.infer -m custom_model.pt -i invoice.pdf --dpi 150

安装

# 1. 进入 WSL
wsl -d Ubuntu-22.04

# 2. 创建 Conda 环境
conda create -n invoice-py311 python=3.11 -y
conda activate invoice-py311

# 3. 进入项目目录
cd /mnt/c/Users/yaoji/git/ColaCoder/invoice-master-poc-v2

# 4. 安装依赖
pip install -r requirements.txt

# 5. 安装 Web 依赖
pip install uvicorn fastapi python-multipart pydantic

快速开始

1. 准备数据

~/invoice-data/
├── raw_pdfs/
│   ├── {DocumentId}.pdf
│   └── ...
├── structured_data/
│   └── document_export_YYYYMMDD.csv
└── dataset/
    └── temp/  (渲染的图片)

CSV 格式:

DocumentId,InvoiceDate,InvoiceNumber,InvoiceDueDate,OCR,Bankgiro,Plusgiro,Amount
3be53fd7-...,2025-12-13,100017500321,2026-01-03,100017500321,53939484,,114

2. 自动标注

# 使用双池模式 (CPU + GPU)
python -m src.cli.autolabel \
    --dual-pool \
    --cpu-workers 3 \
    --gpu-workers 1

# 单线程模式
python -m src.cli.autolabel --workers 4

3. 训练模型

# 从预训练模型开始训练
python -m src.cli.train \
    --model yolo11n.pt \
    --epochs 100 \
    --batch 16 \
    --name invoice_fields \
    --dpi 150

# 低内存模式 (适用于内存不足场景)
python -m src.cli.train \
    --model yolo11n.pt \
    --epochs 100 \
    --name invoice_fields \
    --low-memory \
    --workers 4 \
    --no-cache

# 从检查点恢复训练 (训练中断后)
python -m src.cli.train \
    --model runs/train/invoice_fields/weights/last.pt \
    --epochs 100 \
    --name invoice_fields \
    --resume

4. 增量训练

当添加新数据后,可以在已训练模型基础上继续训练:

# 从已训练的 best.pt 继续训练
python -m src.cli.train \
    --model runs/train/invoice_yolo11n_full/weights/best.pt \
    --epochs 30 \
    --batch 16 \
    --name invoice_yolo11n_v2 \
    --dpi 150

增量训练建议:

场景 建议
添加少量新数据 (<20%) 继续训练 10-30 epochs
添加大量新数据 (>50%) 继续训练 50-100 epochs
修正大量标注错误 从头训练
添加新的字段类型 从头训练

5. 推理

# 命令行推理
python -m src.cli.infer \
    --model runs/train/invoice_fields/weights/best.pt \
    --input path/to/invoice.pdf \
    --output result.json \
    --gpu

# 批量推理
python -m src.cli.infer \
    --model runs/train/invoice_fields/weights/best.pt \
    --input invoices/*.pdf \
    --output results/ \
    --gpu

推理结果包含:

  • fields: 提取的字段值 (InvoiceNumber, Amount, payment_line, customer_number 等)
  • confidence: 各字段的置信度
  • document_type: 文档类型 ("invoice" 或 "letter")
  • cross_validation: payment_line 交叉验证结果 (如果有)

6. Web 应用

在 WSL 环境中启动:

# 方法 1: 从 Windows PowerShell 启动 (推荐)
wsl bash -c "source ~/miniconda3/etc/profile.d/conda.sh && conda activate invoice-py311 && cd /mnt/c/Users/yaoji/git/ColaCoder/invoice-master-poc-v2 && python run_server.py --port 8000"

# 方法 2: 在 WSL 内启动
conda activate invoice-py311
cd /mnt/c/Users/yaoji/git/ColaCoder/invoice-master-poc-v2
python run_server.py --port 8000

# 方法 3: 使用启动脚本
./start_web.sh

服务启动后:

  • 访问 http://localhost:8000 使用 Web 界面
  • 服务会自动加载模型 runs/train/invoice_fields/weights/best.pt
  • GPU 默认启用,置信度阈值 0.5

Web API 端点

方法 端点 描述
GET / Web UI 界面
GET /api/v1/health 健康检查
POST /api/v1/infer 上传文件并推理
GET /api/v1/results/{filename} 获取可视化图片

API 响应格式

{
  "status": "success",
  "result": {
    "document_id": "abc123",
    "document_type": "invoice",
    "fields": {
      "InvoiceNumber": "12345",
      "Amount": "1234.56",
      "payment_line": "# 94228110015950070 # > 48666036#14#",
      "customer_number": "UMJ 436-R"
    },
    "confidence": {
      "InvoiceNumber": 0.95,
      "Amount": 0.92
    },
    "cross_validation": {
      "is_valid": true,
      "ocr_match": true,
      "amount_match": true
    }
  }
}

训练配置

YOLO 训练参数

python -m src.cli.train [OPTIONS]

Options:
  --model, -m        基础模型 (默认: yolo11n.pt)
  --epochs, -e       训练轮数 (默认: 100)
  --batch, -b        批大小 (默认: 16)
  --imgsz            图像尺寸 (默认: 1280)
  --dpi              PDF 渲染 DPI (默认: 150)
  --name             训练名称
  --limit            限制文档数 (用于测试)
  --device           设备 (0=GPU, cpu)
  --resume           从检查点恢复训练
  --low-memory       启用低内存模式 (batch=8, workers=4, no-cache)
  --workers          数据加载 worker 数 (默认: 8)
  --cache            缓存图像到内存

训练最佳实践

  1. 禁用翻转增强 (文本检测):

    fliplr=0.0, flipud=0.0
    
  2. 使用 Early Stopping:

    patience=20
    
  3. 启用 AMP (混合精度训练):

    amp=True
    
  4. 保存检查点:

    save_period=10
    

训练结果示例

最新训练结果 (100 epochs, 2026-01-22):

指标
mAP@0.5 93.5%
mAP@0.5-0.95 83.0%
训练集 ~10,000 张标注图片
字段类型 10 个字段 (新增 payment_line, customer_number)
模型位置 runs/train/invoice_fields/weights/best.pt

各字段检测性能:

  • 发票基础信息 (InvoiceNumber, InvoiceDate, InvoiceDueDate): >95% mAP
  • 支付信息 (OCR, Bankgiro, Plusgiro, Amount): >90% mAP
  • 组织信息 (supplier_org_number, customer_number): >85% mAP
  • 支付行 (payment_line): >80% mAP

模型文件:

runs/train/invoice_fields/weights/
├── best.pt          # 最佳模型 (mAP@0.5 最高) ⭐ 推荐用于生产
└── last.pt          # 最后检查点 (用于继续训练)

注:目前仍在持续标注更多数据,预计最终将有 25,000+ 张标注图片用于训练。

项目结构

invoice-master-poc-v2/
├── src/
│   ├── cli/              # 命令行工具
│   │   ├── autolabel.py  # 自动标注
│   │   ├── train.py      # 模型训练
│   │   ├── infer.py      # 推理
│   │   └── serve.py      # Web 服务器
│   ├── pdf/              # PDF 处理
│   │   ├── extractor.py  # 文本提取
│   │   ├── renderer.py   # 图像渲染
│   │   └── detector.py   # 类型检测
│   ├── ocr/              # PaddleOCR 封装
│   │   └── machine_code_parser.py  # 机器可读付款行解析器
│   ├── normalize/        # 字段规范化
│   ├── matcher/          # 字段匹配
│   ├── yolo/             # YOLO 相关
│   │   ├── annotation_generator.py
│   │   └── db_dataset.py
│   ├── inference/        # 推理管道
│   │   ├── pipeline.py               # 主推理流程
│   │   ├── yolo_detector.py          # YOLO 检测
│   │   ├── field_extractor.py        # 字段提取
│   │   ├── payment_line_parser.py    # 支付行解析器
│   │   └── customer_number_parser.py # 客户编号解析器
│   ├── processing/       # 多池处理架构
│   │   ├── worker_pool.py
│   │   ├── cpu_pool.py
│   │   ├── gpu_pool.py
│   │   ├── task_dispatcher.py
│   │   └── dual_pool_coordinator.py
│   ├── web/              # Web 应用
│   │   ├── app.py        # FastAPI 应用入口
│   │   ├── routes.py     # API 路由
│   │   ├── services.py   # 业务逻辑
│   │   └── schemas.py    # 数据模型
│   ├── utils/            # 工具模块
│   │   ├── text_cleaner.py      # 文本清理
│   │   ├── validators.py        # 字段验证
│   │   ├── fuzzy_matcher.py     # 模糊匹配
│   │   └── ocr_corrections.py   # OCR 错误修正
│   └── data/             # 数据处理
├── tests/                # 测试文件
│   ├── ocr/              # OCR 模块测试
│   │   └── test_machine_code_parser.py
│   ├── inference/        # 推理模块测试
│   ├── normalize/        # 规范化模块测试
│   └── utils/            # 工具模块测试
├── docs/                 # 文档
│   ├── REFACTORING_SUMMARY.md
│   └── TEST_COVERAGE_IMPROVEMENT.md
├── config.py             # 配置文件
├── run_server.py         # Web 服务器启动脚本
├── runs/                 # 训练输出
│   └── train/
│       └── invoice_fields/
│           └── weights/
│               ├── best.pt      # 最佳模型
│               └── last.pt      # 最后检查点
└── requirements.txt

多池处理架构

项目使用 CPU + GPU 双池架构处理不同类型的 PDF

┌─────────────────────────────────────────────────────┐
│              DualPoolCoordinator                     │
│  ┌─────────────────┐    ┌─────────────────┐        │
│  │   CPU Pool      │    │   GPU Pool      │        │
│  │  (3 workers)    │    │  (1 worker)     │        │
│  │                 │    │                 │        │
│  │  Text PDFs      │    │  Scanned PDFs   │        │
│  │  ~50-87 it/s    │    │  ~1-2 it/s      │        │
│  └─────────────────┘    └─────────────────┘        │
│                                                     │
│  TaskDispatcher: 根据 PDF 类型分配任务               │
└─────────────────────────────────────────────────────┘

关键设计

  • spawn 启动方式: 兼容 CUDA 多进程
  • as_completed(): 无死锁结果收集
  • 进程初始化器: 每个 worker 加载一次模型
  • 协调器持久化: 跨 CSV 文件复用 worker 池

配置文件

config.py

# 数据库配置
DATABASE = {
    'host': '192.168.68.31',
    'port': 5432,
    'database': 'docmaster',
    'user': 'docmaster',
    'password': '******',
}

# 路径配置
PATHS = {
    'csv_dir': '~/invoice-data/structured_data',
    'pdf_dir': '~/invoice-data/raw_pdfs',
    'output_dir': '~/invoice-data/dataset',
}

CLI 命令参考

autolabel

python -m src.cli.autolabel [OPTIONS]

Options:
  --csv, -c          CSV 文件路径 (支持 glob)
  --pdf-dir, -p      PDF 文件目录
  --output, -o       输出目录
  --workers, -w      单线程模式 worker 数 (默认: 4)
  --dual-pool        启用双池模式
  --cpu-workers      CPU 池 worker 数 (默认: 3)
  --gpu-workers      GPU 池 worker 数 (默认: 1)
  --dpi              渲染 DPI (默认: 150)
  --limit, -l        限制处理文档数

train

python -m src.cli.train [OPTIONS]

Options:
  --model, -m        基础模型路径
  --epochs, -e       训练轮数 (默认: 100)
  --batch, -b        批大小 (默认: 16)
  --imgsz            图像尺寸 (默认: 1280)
  --dpi              PDF 渲染 DPI (默认: 150)
  --name             训练名称
  --limit            限制文档数

infer

python -m src.cli.infer [OPTIONS]

Options:
  --model, -m        模型路径
  --input, -i        输入 PDF/图像
  --output, -o       输出 JSON 路径
  --confidence       置信度阈值 (默认: 0.5)
  --dpi              渲染 DPI (默认: 150, 必须与训练DPI一致)
  --gpu              使用 GPU

serve

python run_server.py [OPTIONS]

Options:
  --host             绑定地址 (默认: 0.0.0.0)
  --port             端口 (默认: 8000)
  --model, -m        模型路径
  --confidence       置信度阈值 (默认: 0.3)
  --dpi              渲染 DPI (默认: 150)
  --no-gpu           禁用 GPU
  --reload           开发模式自动重载
  --debug            调试模式

Python API

from src.inference.pipeline import InferencePipeline

# 初始化
pipeline = InferencePipeline(
    model_path='runs/train/invoice_fields/weights/best.pt',
    confidence_threshold=0.25,
    use_gpu=True,
    dpi=150,
    enable_fallback=True
)

# 处理 PDF
result = pipeline.process_pdf('invoice.pdf')

# 处理图片
result = pipeline.process_image('invoice.png')

# 获取结果
print(result.fields)
# {
#   'InvoiceNumber': '12345',
#   'Amount': '1234.56',
#   'payment_line': '# 94228110015950070 # > 48666036#14#',
#   'customer_number': 'UMJ 436-R',
#   ...
# }

print(result.confidence)   # {'InvoiceNumber': 0.95, 'Amount': 0.92, ...}
print(result.to_json())    # JSON 格式输出

# 访问交叉验证结果
if result.cross_validation:
    print(f"OCR match: {result.cross_validation.ocr_match}")
    print(f"Amount match: {result.cross_validation.amount_match}")
    print(f"Details: {result.cross_validation.details}")

统一解析器使用

from src.inference.payment_line_parser import PaymentLineParser
from src.inference.customer_number_parser import CustomerNumberParser

# Payment Line 解析
parser = PaymentLineParser()
result = parser.parse("# 94228110015950070 # 15658 00 8 > 48666036#14#")
print(f"OCR: {result.ocr_number}")
print(f"Amount: {result.amount}")
print(f"Account: {result.account_number}")

# Customer Number 解析
parser = CustomerNumberParser()
result = parser.parse("Said, Shakar Umj 436-R Billo")
print(f"Customer Number: {result}")  # "UMJ 436-R"

测试

测试统计

指标 数值
测试总数 688
通过率 100%
整体覆盖率 37%

关键模块覆盖率

模块 覆盖率 测试数
machine_code_parser.py 65% 79
payment_line_parser.py 85% 45
customer_number_parser.py 90% 32

运行测试

# 运行所有测试
wsl bash -c "source ~/miniconda3/etc/profile.d/conda.sh && conda activate invoice-py311 && cd /mnt/c/Users/yaoji/git/ColaCoder/invoice-master-poc-v2 && pytest"

# 运行并查看覆盖率
wsl bash -c "source ~/miniconda3/etc/profile.d/conda.sh && conda activate invoice-py311 && cd /mnt/c/Users/yaoji/git/ColaCoder/invoice-master-poc-v2 && pytest --cov=src --cov-report=term-missing"

# 运行特定模块测试
wsl bash -c "source ~/miniconda3/etc/profile.d/conda.sh && conda activate invoice-py311 && cd /mnt/c/Users/yaoji/git/ColaCoder/invoice-master-poc-v2 && pytest tests/ocr/test_machine_code_parser.py -v"

测试结构

tests/
├── ocr/
│   ├── test_machine_code_parser.py   # 支付行解析 (79 tests)
│   └── test_ocr_engine.py            # OCR 引擎测试
├── inference/
│   ├── test_payment_line_parser.py   # 支付行解析器
│   └── test_customer_number_parser.py # 客户编号解析器
├── normalize/
│   └── test_normalizers.py           # 字段规范化
└── utils/
    └── test_validators.py            # 字段验证

开发状态

已完成功能:

  • 文本层 PDF 自动标注
  • 扫描图 OCR 自动标注
  • 多策略字段匹配 (精确/子串/规范化)
  • PostgreSQL 数据库存储 (断点续传)
  • 信号处理和超时保护
  • YOLO 训练 (93.5% mAP@0.5, 10 个字段)
  • 推理管道
  • 字段规范化和验证
  • Web 应用 (FastAPI + REST API)
  • 增量训练支持
  • 内存优化训练 (--low-memory, --resume)
  • Payment Line 解析器 (统一模块)
  • Customer Number 解析器 (统一模块)
  • Payment Line 交叉验证 (OCR, Amount, Account)
  • 文档类型识别 (invoice/letter)
  • 单元测试覆盖 (688 tests, 37% coverage)

进行中:

  • 完成全部 25,000+ 文档标注
  • 多源融合增强 (Multi-source fusion)
  • OCR 错误修正集成
  • 提升测试覆盖率到 60%+

计划中:

  • 表格 items 提取
  • 模型量化部署 (ONNX/TensorRT)
  • 多语言支持扩展

关键技术特性

1. Payment Line 交叉验证

瑞典发票的 payment_line (支付行) 包含完整的支付信息OCR 参考号、金额、账号。我们实现了交叉验证机制:

Payment Line: # 94228110015950070 # 15658 00 8 > 48666036#14#
             ↓                     ↓            ↓
           OCR Number            Amount     Bankgiro Account

验证流程:

  1. 从 payment_line 提取 OCR、Amount、Account
  2. 与单独检测的字段对比验证
  3. payment_line 值优先 - 如有不匹配,采用 payment_line 的值
  4. 返回验证结果和详细信息

优势:

  • 提高数据准确性 (payment_line 是机器可读格式,更可靠)
  • 发现 OCR 或检测错误
  • 为数据质量提供信心指标

2. 统一解析器架构

采用独立解析器模块处理复杂字段:

PaymentLineParser:

  • 解析瑞典标准支付行格式
  • 提取 OCR、Amount (包含 Kronor + Öre)、Account + Check digits
  • 支持多种变体格式

CustomerNumberParser:

  • 支持多种瑞典客户编号格式 (UMJ 436-R, JTY 576-3, FFL 019N)
  • 从混合文本中提取 (如地址行中的客户编号)
  • 大小写不敏感,输出统一大写格式

优势:

  • 代码模块化、可测试
  • 易于扩展新格式
  • 统一的解析逻辑,减少重复代码

3. 文档类型自动识别

根据 payment_line 字段自动判断文档类型:

  • invoice: 包含 payment_line 的发票文档
  • letter: 不含 payment_line 的信函文档

这个特性帮助下游系统区分处理流程。

4. 低内存模式训练

支持在内存受限环境下训练:

python -m src.cli.train --low-memory

自动调整:

  • batch size: 16 → 8
  • workers: 8 → 4
  • cache: disabled
  • 推荐用于 GPU 内存 < 8GB 或系统内存 < 16GB 的场景

5. 断点续传训练

训练中断后可从检查点恢复:

python -m src.cli.train --resume --model runs/train/invoice_fields/weights/last.pt

技术栈

组件 技术
目标检测 YOLOv11 (Ultralytics)
OCR 引擎 PaddleOCR v5 (PP-OCRv5)
PDF 处理 PyMuPDF (fitz)
数据库 PostgreSQL + psycopg2
Web 框架 FastAPI + Uvicorn
深度学习 PyTorch + CUDA 12.x

常见问题

Q: 为什么必须在 WSL 环境运行?

A: PaddleOCR 和某些依赖在 Windows 原生环境存在兼容性问题。WSL 提供完整的 Linux 环境,确保所有依赖正常工作。

Q: 训练过程中出现 OOM (内存不足) 错误怎么办?

A: 使用 --low-memory 模式,或手动调整 --batch--workers 参数。

Q: payment_line 和单独检测字段不匹配时怎么处理?

A: 系统默认优先采用 payment_line 的值,因为 payment_line 是机器可读格式,通常更准确。验证结果会记录在 cross_validation 字段中。

Q: 如何添加新的字段类型?

A:

  1. src/inference/constants.py 添加字段定义
  2. field_extractor.py 添加规范化方法
  3. 重新生成标注数据
  4. 从头训练模型

Q: 可以用 CPU 训练吗?

A: 可以,但速度会非常慢 (慢 10-50 倍)。强烈建议使用 GPU 训练。

许可证

MIT License