没有真实数电发票数据集?看我自己造

0 阅读14分钟

没有电子发票数据集?我用30张发票模板生成3000张训练数据

项目地址:gitee.com/she521lin/i…

适用人群:做OCR/目标检测的个人开发者、学生、研究者


一、为什么要写这个项目?

在开发发票 OCR 识别功能时,我遇到了一个难题:没有数据集。

发票属于敏感财务凭证,不可能像猫狗图片那样随手下载。现实情况是:

困境具体表现
无公开数据集网上找不到任何完整的最新的数电票版式的发票真实数据集
真实发票难获取发票包含企业名称、纳税人识别号、金额等敏感信息,个人开发者无法合法获取大量真实发票
网络图片不可用搜到的少量真实发票图片基本都打了马赛克,关键字段被遮挡,模糊不清,根本没法用来训练
标注成本高昂就算拿到了真实发票,逐张人工标注工作量极其庞大

没有数据,模型就没法训练。没有模型,项目就没法继续。

这个局怎么破?

我的思路是:既然找不到真实数据,那就自己造。但造的数据必须"以假乱真"——版面、字体、间距、数据内容都要高度还原真实发票,否则训练出的模型在真实场景中根本不能用。

经过反复迭代,我最终用 30 张真实发票模板 生成了 3000 张带 YOLO 标注的训练数据集,并成功训练了 YOLOv8 模型。本文分享完整的实现过程。


二、整体思路

核心管线只有一条:

真实票样模板 (PNG)
    ↓ labelImg 人工标注
YOLO 标注文件 (.txt)
    ↓ Python 脚本读取标注,计算字段坐标
生成 JSON 配置 (字段位置 + 类型)
    ↓ 在模板上填充假数据
模拟票据 (30 张)
    ↓ 数据增强 + 随机数据变体
训练数据集 (3000 张 + 3000 个标注)
    ↓ YOLOv8 训练
发票字段检测模型

一句话概括:用真实模板 + 程序化填充假数据 + 数据增强,以极低的标注成本(30 张人工标注)生成大规模训练数据集(3000 张)。


三、效果预览

先上效果图,看下模拟数据的真实感和标注质量。

3.1 模拟票据生成效果

下图是通过 _batch_gen_all.py 自动填充的模拟发票,公司名、金额、商品明细、税额、价税合计等都是程序随机生成并自动计算的。

增值税专用发票(模拟填充) 普通发票(模拟填充)

可以看到:

  • 发票的真实版式完全保留(表格、底色、字体、间距)
  • 顶部发票代码、发票号码、开票日期、校验码等都有填充
  • 购买方/销售方的名称、纳税人识别号、地址电话、开户行账号完整
  • 明细表随机生成了多行商品,规格型号、单位、数量、单价、金额、税率、税额都有值
  • 价税合计的大小写金额自动转换并对齐

3.2 YOLO 标注可视化效果

下图是 _draw_yolo_vis.py 在模拟票据上叠加绘制的 YOLO 标注框,不同颜色代表不同字段类别。

增值税专用发票(标注框) 普通发票(标注框)

每张票据有 23~36 个标注框,覆盖了:

  • 发票号码、开票日期、校验码
  • 购买方/销售方信息(名称、税号、地址、银行等)
  • 明细表表头 + 明细列 + 金额合计行
  • 价税合计(大小写)
  • 备注、收款人、复核、开票人
  • 销售方(章)、二维码

四、发票模板从哪来?

模板来自全国增值税发票查验平台的公开票样:

inv-veri.chinatax.gov.cn/fpcygzfw/in…

该平台提供了各类型发票的标准版式预览,包括字段布局、字号、间距、表格结构等。我将各票样保存为高清 PNG 图片作为模板底图,保留了真实发票的完整版面样式。

共 30 种发票模板,涵盖:

  • 增值税专用发票 / 普通发票 / 成品油发票 / 稀土发票 / 光伏收购发票
  • 建筑服务发票 / 通行费发票 / 不动产销售发票 / 汇总代开发票
  • 医疗服务(门诊/住院)发票
  • 旅客运输服务发票 / 货物运输服务发票
  • 机动车销售统一发票 / 二手车销售统一发票
  • 航空运输电子客票行程单

五、标注:80 个字段类别

5.1 标注工具

使用 labelImg,经典的 YOLO 标注工具:

pip install labelImg
labelImg

5.2 标注原则

经过多次踩坑,总结出以下标注原则:

原则说明
框值不框标签框住字段的具体数值区域,不框"名称:"这个标签文字
备注整体框备注作为整体一个框,不拆分
不画表格线不把表格线作为标注框
明细列单独框明细表的每一列单独框,方便后续按列填充
CID 严格对应标注时 classes.txt 必须加载成功,否则 CID 会错位

5.3 类别清单 (classes.txt)

共定义 80 个类别,按功能分组:

类别组CID 范围示例字段
通用字段0-10invoice_number, invoice_date, buyer_name, seller_name, qr_code
明细表11-18detail_header, detail_row_demo, detail_cols, detail_footer
辅表19-21aux_row_header, aux_row_demo, aux_cols
买方扩展37-43buyer_id, buyer_addr, buyer_phone, buyer_bank
航空行程44-65air_detail_cols, fare, fuel_surcharge, e_ticket_no
机动车22-31vehicle_type, brand_model, vin_slot, engine_no
二手车69-79buyer_addr, buyer_phone, market_name, market_bank

完整的 classes.txt 见项目仓库 invoice_templates/classes.txt

5.4 不同模板的列顺序差异

这是标注过程中最容易踩的坑。不同发票模板的明细表列顺序是不同的,必须逐张核对:

模板列数列顺序
标准发票8项目名称、规格型号、单位、数量、单价、金额、税率、税额
旅客运输6项目名称、单价、数量、金额、税率、税额(注意:单价在数量前面!)
通行费8项目名称、车牌号、车辆类型、通行日期起、通行日期止、金额、税率、税额
建筑服务6项目名称、金额、税率、税额、建筑服务发生地、建筑项目名称
不动产8项目名称、产权证书号、面积单位、数量、单价、金额、税率、税额
货物运输7项目名称、单位、数量、单价、金额、税率、税额(无规格型号)

完整的标注规范见项目仓库 标注清单.md


六、模拟票据生成:核心代码详解

6.1 整体流程

_batch_gen_all.py(约 970 行)是整个项目的核心脚本,完成以下工作:

读取 YOLO 标注 → 推断模板类型 → 构建 JSON 配置 → 填充假数据 → 输出模拟票据

6.2 核心函数

def load_yolo_boxes(txt_path, img_w, img_h):
    """读取 .txt 标注文件,解析 YOLO 归一化坐标为像素坐标"""
    # YOLO 格式: class_id x_center y_center width height (归一化)
    # 转为像素: x1 = (xc - w/2) * img_w, y1 = (yc - h/2) * img_h
    ...

def detect_template_type(boxes, template_name):
    """通过检测标注框中的字段名推断模板类型"""
    # 检测到 passenger_name → 旅客运输
    # 检测到 goods_transport → 货物运输
    # 检测到 air_detail_cols → 航空行程单
    # 检测到 vin_slot → 机动车
    ...

def get_col_semantic(template_name, n_cols):
    """按模板名匹配明细列顺序"""
    # 旅客运输 6 列: 项目名称、单价、数量、金额、税率、税额
    # 货物运输 7 列: 项目名称、单位、数量、单价、金额、税率、税额
    # 通行费 8 列: 项目名称、车牌号、车辆类型...
    ...

def inject_rows(pil_img, cfg):
    """在明细表区域注入 2~N 行随机商品数据"""
    # 计算可用行数 = (footer_y - first_row_y) / row_height
    # 随机生成 2~max_rows 行
    # 按列顺序填充: 商品名、规格、单位、数量、单价、金额、税率、税额
    # 金额 = 数量 × 单价,税额 = 金额 × 税率,自动计算
    ...

def inject_aux_rows(pil_img, cfg, template_type):
    """为旅客/货物运输发票的辅表填充数据"""
    # 旅客运输辅表: 出行人、证件号、出行日期、出发地、到达地、等级、交通工具类型
    # 货物运输辅表: 运输工具种类、牌号、起运地、到达地、货物名称
    ...

def fill_text(draw, box, text, font):
    """文本填充,自动缩放字体适应框宽,垂直居中"""
    # 先尝试默认字号,如果文字超出框宽则逐步缩小
    # 文字垂直居中在框内
    ...

def big_amount(num):
    """金额大写转换"""
    # 1234.56 → 壹仟贰佰叁拾肆元伍角陆分
    ...

6.3 假数据池

为了模拟数据真实多样,内置了多个数据池:

COMPANY_NAMES = [
    "北京华夏科技有限公司", "上海腾飞实业有限公司",
    "深圳创新科技有限公司", "广州永信贸易有限公司",
    # ... 共 12 家公司
]

GOODS = [
    ("计算机", "台", "联想扬天M4000", 5600.00),
    ("打印机", "台", "HP LaserJet Pro", 2300.00),
    ("办公椅", "把", "人体工学椅", 890.00),
    # ... 共 21 种商品
]

PASSENGERS = [
    ("张明", "110101199003071234", "北京-上海", "2024-03-15", "一等座", 553.00),
    ("李华", "310104199506152345", "上海-广州", "2024-03-16", "二等座", 273.00),
    # ... 共 12 条旅客信息
]

VEHICLES = [
    ("货车", "京A12345", "钢材", "32.5吨", "120m³"),
    ("卡车", "沪B67890", "水泥", "15.0吨", "45m³"),
    # ... 共 8 条运输信息
]

6.4 生成效果

运行 python _batch_gen_all.py 后,30 张模拟票据生成到 mock_invoices/ 目录:

  • 每张票据都填入了完整的假数据(公司名、金额、日期、商品明细等)
  • 明细表随机生成 2~N 行商品
  • 金额自动计算(数量×单价=金额,金额×税率=税额)
  • 价税合计自动汇总
  • 大写金额自动转换

七、数据集生成:30 → 3000

7.1 为什么要从 30 张扩展到 3000 张?

30 张模拟票据只是预览效果,作为训练数据集远远不够。YOLOv8 训练一般需要至少 1000+ 张图片。_gen_dataset.py 通过以下方式将 30 张扩展为 3000 张:

每张模板生成 100 张变体,每张变体包含:

  • 不同的假数据(随机公司名、金额、商品等)
  • 随机数据增强(亮度、对比度、旋转、模糊、色调)

7.2 数据增强策略

针对票据场景定制的增强策略:

增强方式参数概率说明
亮度调整0.8~1.2100%模拟不同光照
对比度调整0.8~1.2100%模拟不同扫描质量
旋转±2°100%模拟拍照倾斜
高斯模糊radius 0.3~0.830%模拟拍摄模糊
色调微调0.9~1.120%模拟不同扫描仪

关键禁用项

fliplr: 0.0    # 票据不能水平翻转(翻转后文字镜像,不合理)
flipud: 0.0    # 票据不能垂直翻转
mixup: 0.0     # 票据不能 mixup(两张发票叠在一起不合理)
degrees: 2.0   # 只允许轻微旋转(大幅旋转后不像真实发票)

7.3 YOLO 标注自动生成

每张图片的标注 = 原始模板标注 + 注入的明细行标注 + 注入的辅表行标注:

def build_yolo_label(cfg, n_rows, n_aux_rows, img_w, img_h):
    """生成 YOLO 标注文件"""
    # 1. 原始模板的固定字段标注(发票号码、买方、卖方等)
    # 2. 注入的明细行标注(用 detail_row_demo 的 CID,按行高计算 y 坐标)
    # 3. 注入的辅表行标注(用 aux_row_demo 的 CID)
    ...

7.4 随机种子机制

seed = hash(img_name) % 10000 + i * 31 + 1

确保每张变体数据不同但可复现——同样的种子会生成完全一样的图片和标注。

7.5 数据集输出

dataset/
├── images/
│   ├── train/    # 2400 张训练图片
│   └── val/      # 600 张验证图片
├── labels/
│   ├── train/    # 2400 个标注文件
│   └── val/      # 600 个标注文件
└── data.yaml     # YOLO 训练配置(自动生成路径和类别)

八、YOLO 训练

8.1 模型选择

模型参数量是否推荐理由
yolov8n3.2M80 类容量不足,容易欠拟合
yolov8s11.2M首选票据场景标准化,s 模型容量足够
yolov8m25.9M⚠️ 备选s 的 mAP 不达标时升级
yolov8l/x52M+3000 张数据量偏少,容易过拟合

8.2 训练脚本

from ultralytics import YOLO

model = YOLO('yolov8s.pt')

model.train(
    data='dataset/data.yaml',
    epochs=100,
    batch=16,
    imgsz=640,
    optimizer='AdamW',
    lr0=0.001,
    cos_lr=True,
    patience=20,        # 20 轮无提升自动早停
    fliplr=0.0,         # 票据不水平翻转
    mixup=0.0,          # 票据不 mixup
    degrees=2.0,        # 轻微旋转增强
    scale=0.5,          # 缩放增强
)

8.3 显存适配

不同显卡的推荐配置:

GPU 显存推荐 batch推荐 imgsz命令
2GB (MX550)2416--batch 2 --imgsz 416
6GB8640--batch 8 --imgsz 640
8GB+16640--batch 16 --imgsz 640
CPU4640--device cpu --batch 4

8.4 训练结果

训练完成后,runs/detect/invoice_yolov8s/ 目录包含:

  • weights/best.pt — 最佳权重(验证集 mAP 最高)
  • weights/last.pt — 最后一个 epoch 的权重
  • results.png — 训练曲线(loss/mAP 随 epoch 变化)
  • confusion_matrix.png — 混淆矩阵
  • val_batch0_pred.jpg — 验证集预测结果可视化

九、YOLO 标注可视化

为了验证标注质量,编写了 _draw_yolo_vis.py,在模拟票据上叠加绘制 YOLO 标注框:

def draw_yolo_vis(img_path, txt_path, classes, out_path):
    """在图片上绘制 YOLO 标注框"""
    # 读取图片和标注
    # YOLO 归一化坐标 → 像素坐标
    # 绘制半透明矩形填充(alpha=40)
    # 绘制实线边框
    # 绘制类别名标签
    # 80 种类别用 HSV 色彩空间均匀分布颜色
    ...

效果:不同颜色代表不同字段类别,可以直观看到每个字段的标注框位置是否准确。


十、项目结构

InvoiceSimulation/
├── invoice_templates/              # 票样模板 + 标注 + 类别文件
│   ├── classes.txt                  # YOLO 类别清单 (80 类)
│   ├── 增值税专用发票.png            # 票样模板图片 (30 张)
│   ├── 增值税专用发票.txt            # YOLO 标注文件 (labelImg 输出)
│   └── ...
├── mock_invoices/                   # 模拟票据 (30 张预览图)
├── mock_yolo_vis/                   # YOLO 标注可视化 (30 张)
├── dataset/                         # 训练数据集 (3000 张)
│   ├── images/train/                # 训练图片 (2400 张)
│   ├── images/val/                  # 验证图片 (600 张)
│   ├── labels/train/                # 训练标注 (2400 个)
│   ├── labels/val/                  # 验证标注 (600 个)
│   └── data.yaml                    # YOLO 训练配置
├── runs/detect/                     # 训练输出 (权重/曲线/混淆矩阵)
├── _batch_gen_all.py                # [核心] 生成 JSON 配置 + 模拟票据
├── _draw_yolo_vis.py                # [核心] YOLO 标注可视化
├── _gen_dataset.py                  # [核心] 批量数据集生成
├── train.py                         # YOLOv8 训练脚本
├── 标注清单.md                       # 30 张票样的标注规范
├── 项目文档.md                       # 详细技术文档
└── README.md                        # 项目说明

十一、快速开始

# 1. 安装依赖
pip install Pillow ultralytics
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121  # GPU版

# 2. 生成模拟票据(预览效果)
python _batch_gen_all.py

# 3. 生成 YOLO 标注可视化(验证标注质量)
python _draw_yolo_vis.py

# 4. 生成训练数据集(3000 张)
python _gen_dataset.py

# 5. 启动训练
python train.py --device 0 --batch 16 --imgsz 640

十二、综合评价

优点

优势说明
真实模板基础30 张票样来自真实发票,布局、字体、间距都是真实的
标注体系完整80 个类别覆盖发票所有字段,标注规范严格
数据生成高效30 张模板 → 3000 张数据集,数据多样性好
管线设计合理标注 → 配置生成 → 模拟填充 → 数据增强 → YOLO 训练,全链路自动化
增强策略得当针对票据场景定制,正确禁用了不合理的增强方式

局限性

问题影响
文字渲染太干净PIL 绘制的文字是完美像素,真实发票有印刷噪点、扫描失真
缺少真实干扰没有印章、手写签名、折痕、阴影、拍照倾斜等
类别不平衡通用字段 3000 个样本,稀有字段仅 ~100 个

模型适用场景

场景能用吗说明
作为预训练模型加少量真实数据微调,效果远优于从头训练

十三、免责声明

  • 本项目中的发票模板图片来源于全国增值税发票查验平台的公开票样,仅用于技术学习与研究目的。
  • 模拟数据集中所有公司名称、纳税人识别号、金额、商品名称、人名、日期等信息均为程序随机生成的模拟数据,不指向任何真实企业或个人,不具备任何法律效力。
  • 如本项目内容侵犯了您的合法权益,请联系作者,确认后将在第一时间删除相关内容。

项目地址

Gitee 仓库gitee.com/she521lin/i…

如果对你有帮助,欢迎 Star ⭐