Invoice Master POC v2
自动发票字段提取系统 - 使用 YOLOv11 + PaddleOCR 从瑞典 PDF 发票中提取结构化数据。
项目概述
本项目实现了一个完整的发票字段自动提取流程:
- 自动标注: 利用已有 CSV 结构化数据 + OCR 自动生成 YOLO 训练标注
- 模型训练: 使用 YOLOv11 训练字段检测模型
- 推理提取: 检测字段区域 → 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 | 客户编号 |
安装
# 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 缓存图像到内存
训练最佳实践
-
禁用翻转增强 (文本检测):
fliplr=0.0, flipud=0.0 -
使用 Early Stopping:
patience=20 -
启用 AMP (混合精度训练):
amp=True -
保存检查点:
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 (默认: 300)
--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
验证流程:
- 从 payment_line 提取 OCR、Amount、Account
- 与单独检测的字段对比验证
- payment_line 值优先 - 如有不匹配,采用 payment_line 的值
- 返回验证结果和详细信息
优势:
- 提高数据准确性 (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:
- 在
src/inference/constants.py添加字段定义 - 在
field_extractor.py添加规范化方法 - 重新生成标注数据
- 从头训练模型
Q: 可以用 CPU 训练吗?
A: 可以,但速度会非常慢 (慢 10-50 倍)。强烈建议使用 GPU 训练。
许可证
MIT License