简介:一套面向发票字段识别的目标检测数据集,专为文档结构识别与OCR场景设计,适合AI开发者在自动化发票处理、财务系统或文档智能研究中训练YOLOv12等主流模型。数据源自真实发票图像,覆盖账单地址、发票号码、总金额、GST等17个关键字段,可按训练集393张、验证集89张、测试集45张划分进行模型训练与评估,配套YOLO格式边界框和类别标签,无需额外转换即可切入现有检测流水线。压缩包共1056个文件,以527张jpg原图、527个txt标注、1个yaml配置和1个docx说明为主体,整体仅18.14MB,目录清晰便于快速复用。利用该数据集可构建自动化发票录入、实时字段校验与财务审计辅助功能,有效减少人工核对成本并提升结构化信息提取精度。目前已有179人学习下载,适合需要高质量字段级标注、快速验证检测方案的中高级算法工程师与文档智能研究者。
1. 发票字段检测数据集:把票据抽取从“玄学”变成可复现的起点
做票据自动化处理的同行都有这个体会:通用 OCR 能读字,但读不准确“哪行是发票代码、哪个区域是价税合计”。你要的是字段级定位,而不是整页识别。这份发票字段检测数据集,解决的正是“字段在哪里”的问题。它按目标检测思路做了文档结构识别标注,可以直接喂给 YOLO 系列(从 YOLOv5 到 YOLOv12)做训练和验证。如果你正在做票据识别、财务自动化、报销单据结构化,或者单纯想找一个行业数据集来评估检测模型的泛化能力,这份资源很值得先看一眼再决定怎么用。
和随手爬来的图片集不同,这类行业数据集最值钱的部分是标注一致性:字段口径统一、边界框贴合字段区域,而不是把整张票圈进去。下面我从数据构成、格式转换、模型训练到踩坑记录,按实操顺序把这套流程拆开讲。
2. 数据集的底层构成:字段类别、标注格式与目录组织
2.1 发票字段到底检测什么:从文档结构识别说起
发票字段检测在技术上属于 document layout analysis 的细分场景。目标不是把整页按表格区域分割,而是定位到“文本字段实例级”的边界框。常见做法是每个字段一个类别,比如代码/号码、开票日期、购买方名称、销售方名称、价税合计、金额、税额、收款人、复核人、密文区等。检测模型输出的每个框对应一个字段区域,后面再接 OCR 或直接裁剪送识别。
这份数据集通常包含的是增值税发票样式,字段数量随票面排版不同略有差异。做检测时,关键是类别粒度:粒度太粗,把整个购买方信息块圈成一个框,后续 OCR 还是要二次解析;粒度太细,把“购买方名称”和“购买方纳税人识别号”拆成两个类别,数据标注成本高且容易混淆。合格的发票字段数据集一般会把高频字段独立成类,低频字段合并,保证每个类别有足够的正样本。
2.2 字段类别表与标注样本盘点
拿到数据集先不要急着训练,第一步是把类别和样本量摸清楚。典型的字段类别分布如下:
| 字段类别 | 含义 | 训练难度 | 典型样本占比 |
|---|---|---|---|
| 发票代码 | 发票左上角代码 | 中(字密且小) | 每张必有 |
| 发票号码 | 票面右上角号码 | 中 | 每张必有 |
| 开票日期 | 票面右上方日期 | 低 | 每张必有 |
| 购买方名称 | 购买方栏第一行 | 低 | 每张必有 |
| 购买方纳税人识别号 | 购买方栏第二行 | 中 | 每张必有 |
| 销售方名称 | 销售方栏第一行 | 低 | 每张必有 |
| 金额 | 价税合计下方金额行 | 高(与税率行相邻) | 每张必有 |
| 税额 | 金额右侧税额列 | 高 | 每张必有 |
| 价税合计 | 票面底部合计大写区域 | 中(长文本) | 每张必有 |
| 备注 | 票面备注区 | 低 | 约六成 |
| 收款人 | 票面下方人名 | 高(字体小且重叠) | 约八成 |
先做一个类别统计脚本,把标注文件里的类别频次拉出来,确认是不是长尾分布。如果某个类别只有几十个样本,后面训练时要么做重采样,要么接受较低的召回率,不要硬撑。
2.3 标注格式三件套:VOC、COCO 与 YOLO TXT 的差异
行业数据集常见的标注格式有三种,彼此的坐标体系和存储结构完全不同。拿到数据第一步就是识别格式,再决定转换路径:
| 格式 | 坐标体系 | 存储方式 | 特点 |
|---|---|---|---|
| VOC XML | 像素绝对值(xmin, ymin, xmax, ymax) | 每张图一个 xml | 直观,但单图多文件难管理 |
| COCO JSON | 像素坐标(x, y, w, h),类别是 id | 整个集合一个 json | 适合大规模训练,但切分麻烦 |
| YOLO TXT | 归一化相对坐标(cx, cy, w, h) | 每张图一个 txt | 训练效率高,不规范写法容易出错 |
如果你拿到的是 COCO JSON,转成 YOLO TXT 时最容易犯的错误是把 (x, y, w, h) 直接当作中心点,实际上 COCO 的 x, y 是左上角坐标。这类错误不会让训练直接崩溃,但会让 mAP 低到令人怀疑人生。
2.4 目录组织与快速核验清单
一份标准化的数据集目录长这样:
invoice_fields/ ├── images/ │ ├── train/ # 训练图(约 80%) │ └── val/ # 验证图(约 20%) ├── labels/ │ ├── train/ # 与 images/train 一一对应,同名 txt │ └── val/ ├── classes.txt # 类别列表,每一行一个类别名 ├── data.yaml # YOLO 训练配置文件 └── stats.json # 类别频次统计拿到数据集的第一件事是按清单核对:train/val 是否完全分开、图片和标签是否同名匹配、classes.txt 的行顺序是否和标注文件中的 class id 一致。我一般会用下面这个命令快速检查:
find images/train -name "*.jpg" | wc -l find labels/train -name "*.txt" | wc -l # 两个数字必须一致,差一张都要回头查文件数量对不上是最常见的情况,通常是标注时漏了空图或漏了空文本文件。空文本文件(没有检测目标)实际是“背景样本”,在训练时也有价值,不该直接删掉,但要在训练配置里确认空样本的处理方式。
3. 踩平格式鸿沟:把标注转成 YOLO 可用的 TXT 与训练配置
3.1 COCO JSON 转 YOLO TXT:坐标体系的换算细节
拿到 COCO 标注后,我通常先写一个转换脚本,把 JSON 里的字段级标注转成 YOLO 训练直接吃的 txt。下面这个脚本可以按需改路径和类别表:
import json import os # 假设 coco.json 里包含 images 和 annotations 两个数组 coco_path = "coco_annotations.json" label_dir = "labels/train" classes = ["invoice_code", "invoice_number", "invoice_date", "buyer_name", "seller_name", "total_amount", "tax_amount", "total_in_words"] with open(coco_path, "r", encoding="utf-8") as f: coco = json.load(f) # 建立 image_id -> 文件名的映射 id_to_name = {} for img in coco["images"]: img_id = img["id"] file_name = img["file_name"].split("/")[-1].replace(".jpg", ".txt") id_to_name[img_id] = file_name os.makedirs(label_dir, exist_ok=True) # 逐张图片处理 for ann in coco["annotations"]: image_id = ann["image_id"] category_id = ann["category_id"] # 对应 classes 里从 1 开始的编号 img_info = next(img for img in coco["images"] if img["id"] == image_id) img_w = img_info["width"] img_h = img_info["height"] x, y, w, h = ann["bbox"] # 注意:x, y 是左上角坐标 cx = (x + w / 2) / img_w cy = (y + h / 2) / img_h nw = w / img_w nh = h / img_h out_path = os.path.join(label_dir, id_to_name[image_id]) # class id 从 0 开始,coco 的 category_id 一般从 1 开始 class_id = category_id - 1 line = f"{class_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n" with open(out_path, "a", encoding="utf-8") as f: f.write(line)代码里最关键的是两个细节:一是 COCO 的 bbox 格式是 [x, y, width, height],其中 x, y 是左上角坐标,转成 YOLO 格式时要先算出中心点;二是类别的编号从 0 开始,COCO 的 category_id 通常从 1 开始,直接搬过去会让所有类号偏移一位,训练出来全部预测错类。这个脚本适合一次性转换,如果数据集后续有更新,建议在转换时打印几张样例的 txt 内容做人工核对。
3.2 划分训练集和验证集:按票分组,不能按行随机
很多人在做数据集划分时直接按图片随机分配,这在发票场景可能出问题:同一张发票可能被多次拍照扫描,或者相同的票面排版在训练集和验证集里各出现一次,导致验证指标虚高。更稳妥的做法是按票据的唯一标识分组,再按组划分。如果数据集没有提供票据 ID,可以用文件名前缀来推断。以下是按前缀分组的划分脚本:
python split_by_prefix.py --image-dir images --label-dir labels --split-ratio 0.8 --output-dir invoice_fields这个脚本里做两件事:先用正则提取图片文件名的前缀(通常是发票代码或编号的一部分),然后按前缀整体划分到 train 或 val,确保同一前缀的票据不会同时出现在两个集合里。划分完成后,用同一份前缀清单去移动 labels 目录下对应的 txt。如果文件名的前缀规则不统一,就退化成按图片哈希做去重,再划分。
3.3 配置文件写法:class id 顺序与图片路径是两大雷区
YOLO 系列的 data.yaml 写法看着简单,但坑多在隐处。下面是一个发票字段检测的可直接参考配置:
path: ./invoice_fields train: images/train val: images/val names: 0: invoice_code 1: invoice_number 2: invoice_date 3: buyer_name 4: seller_name 5: total_amount 6: tax_amount 7: total_in_words一个容易触发问题的点是:names 里的类名顺序必须严格和 labels/train 里 txt 的 class id 对应,不是按字母序,而是按转换脚本输出的顺序。另一个容易踩的坑是 path 用相对路径还是绝对路径。如果在同一台机器训练,建议直接写绝对路径;如果要换机器跑,path 写相对路径,依赖当前工作目录,这在某些版本里解析会不一致。我在本地一般先写绝对路径,等确定能跑通再改成相对路径。
3.4 数据质量初筛:坏图、空标注和边界框越界
转完格式后,不要急着开训练。先跑一个全量校验,把明显有问题的样本筛掉或在训练时排除。常见问题有:图片本身损坏导致解码失败、标注框的数值超出图片边界、归一化后宽高为负数(坐标反了)、同一张图有重复标注。我习惯写一个快速检查脚本:
python check_labels.py --image-dir images/train --label-dir labels/train --min-size 2这个脚本会逐张读取 txt 里的坐标,检查 cx, cy, w, h 是否都在 (0, 1] 区间,w、h 是否大于 min-size(小于 2 像素的框对检测基本没意义)。发现问题后按文件名记录,统一决定是删除还是重新标注。发票字段里有一些特别小的字段(比如发票号码),如果脚本发现大量小于 2 像素的边界框,说明原标注的分辨率偏低,这时优先考虑整体调大输入尺寸,而不是删除这些样本。
4. 模型选型与训练控制:从 YOLOv8 到 YOLOv12 怎么试
4.1 不同 YOLO 版本对票据场景的适配性对比
发票字段检测的难点不在“类别多”,而在“目标小、文本细、背景干扰强”。选模型时我一般按下面的对比表来定基准:
| 模型版本 | 参数量级 | 适合场景 | 踩坑点 |
|---|---|---|---|
| YOLOv5s | 约 7M | 快速验证,CPU 也能跑 | 小目标字段(发票号码)召回偏低 |
| YOLOv8n | 约 3M | 轻量部署,边缘设备 | 细长文本字段可能断检 |
| YOLOv8s | 约 11M | 平衡精度与速度 | 需要调大输入分辨率 |
| YOLOv11n | 约 2.6M | 追求推理速度 | 小字段提升有限 |
| YOLOv12n | 约 2.4M | 探索新注意力结构 | 需要一个相对干净的训练环境 |
我通常用 YOLOv8s 作为基线模型,它在大众 GPU 上训练快,参数量适中,在发票这类文本区域检测上比 v5 的颈部结构更稳。如果目标是部署在低算力设备上,再退到 YOLOv8n 做蒸馏或直接训练,但不要一上来就用 n 系列,小模型对标签噪声更敏感,训练集本身有标注抖动时容易学偏。
4.2 训练超参设定:输入尺寸、batch 和早停策略
发票字段中“发票号码”这个字段宽度可能只有整张图的十分之一不到,输入尺寸设小了直接漏检。合理的区间如下:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| img_size | 1280 | 低于 640 时小字段严重漏检,1280 是平衡点 |
| batch | 8 ~ 16 | 取决于显存,先用 8 验证稳定再加大 |
| epochs | 300 | 配合早停使用,不要一口气跑满 |
| patience | 30 | 连续 30 轮验证集 mAP 无提升就停 |
| mosaic | 0.5 或关掉 | 发票是结构化文档,过度拼图会破坏布局语义 |
| hsv_h / hsv_s | 轻微增强 | 仅做明暗变化,不要动色相 |
有一点特别提醒:发票图像是强结构化文本,旋转超过 15 度的增强基本是负作用。因为票据本身是水平摆正拍摄的,如果训练时把图旋转 45 度,模型看到的“颠倒文本”和真实场景不一致,验证集上表现会变差。我一般只保留 hsv 明暗扰动和轻微平移,关掉旋转和透视。
训练命令可以长这样:
yolo train \ model=yolov8s.pt \ data=invoice_fields/data.yaml \ imgsz=1280 \ batch=8 \ epochs=300 \ patience=30 \ mosaic=0.5 \ seed=42等训练结束时,不要只看最后的 mAP,要打开 results.png 看 loss 曲线是否平滑收敛。发票场景如果 loss 曲线在训练后期反复震荡,多半是标注噪声在起作用,优先检查 batch 内是否有明显标注错误的样本,而不是加正则。
4.3 模型剪裁与知识蒸馏的边界
如果训练完发现模型部署时帧率不够,不要立刻重训,先把训练好的模型做 ONNX 导出和 INT8 量化,观察 mAP 掉点。发票字段检测的实际推理瓶颈往往是“小目标的锚框分配”,而不是网络深度。通常 n 系列模型在量化后 mAP 掉 2-3 个点,s 系列在批量归一化合并后接近无损。如果量化后小字段(如税额列)掉点超过 5 个点,说明模型本身在困难样本上没有收敛到位,先回去调数据,而不是继续量化。
5. 训练与评估中最容易翻车的五个坑:现象、原因与解法
5.1 全部漏检“发票号码”这个小字段
现象:训练几十轮后其他类 mAP 都在 0.85 以上,但发票号码的召回率只有 0.3 左右,单独看推理结果,这个字段完全没有框。
原因分析:发票号码在票面上是细长数字串,像素高度占整图比例不到 5%,如果 imgsz 设在 640,下采样 32 倍后该区域只占几个特征点,正样本极其稀缺。
解决:把 imgsz 提到 1280,同时检查数据增强里的 scale 参数,避免把图缩得太小。另一个备选方案是切图训练,把原图按上下半区切成两块,分别训练,推理时也切图,但这个方案只有当整图分辨率超过 4000 像素时才值得,否则直接用大输入更省事。
5.2 训练集和验证集存在“同票不同帧”导致 mAP 虚高
现象:本地验证 mAP 能到 0.93,但拿到新拍的真实票据上一测,漏检直接多出一倍。
原因分析:同一张发票被扫描了多次,或者同一版式发票在数据集里重复出现。随机按图片划分时,这些重复图同时进入训练集和验证集,模型相当于被“透题”了。
解决:划分数据集时按票据 ID 前缀分组,前缀相同的所有图片要么全在训练集、要么全在验证集。这个坑最容易漏,因为在文件层面看不出问题,只有按票据归属统计才能发现。从那以后我每次拆发票数据集都会先跑一个重复度检查,把重复图片的哈希找出来,再决定要不要去重。
5.3 数据增强把方向搞反:旋转增强拖垮文本检测
现象:训练 loss 降得很顺利,但 val 上的 mAP 一路走低,或者训练后期 mAP 曲线剧烈抖动。
原因分析:开启了大角度的旋转、透视和上下翻转增强。发票文字是有方向性的,翻转后模型的语义特征被带偏,它在训练里看到的“倒着的发票”在实际场景永远不会出现,白白消耗模型容量。
解决:在增强配置里把 degrees 设为 0,perspective 设为 0,mosaic 调低到 0.5 并观察效果。文档类检测任务和自然场景目标检测不一样,自然的增强策略是“模拟拍摄角度变化”,而票据检测的输入往往已经是裁剪正好的图像,增强重点应放在亮度、对比度、模糊和轻微缩放上。
5.4 标注坐标错位导致训练时 loss 不减反增
现象:训练 50 轮后 box_loss 不降,val 上的精确率接近零,可视化预测时发现预测框全体偏移到字段上方。
原因分析:原始标注是 VOC XML 格式,转换时把 xmin、ymin、xmax、ymax 直接当作 YOLO 的 cx、cy、w、h 来用,坐标体系没换算。这类错误在单一格式转换脚本里反复出现,特别容易发生在手动改了标注文件的情况下。
解决:转换后抽 3-5 张图,用绘图脚本把边界框画回原图人工核对,确认框的位置是否贴合字段区域。我在转换后必做的第一步就是这个,绝不直接进训练。把验证脚本写进流程里,视觉检查通过后才算“格式转换完成”。
5.5 类别不平衡:价税合计检测好,收款人一塌糊涂
现象:价税合计的 mAP 能到 0.9,但收款人这个类别 recall 不到 0.4,而且经常把备注文本误检成收款人。
原因分析:收款人字体小,且票面上经常和复核人、开票人挨在一起,标注时框与框重叠度高,正样本占比低,模型没有足够的区分能力。
解决:做类别重采样。训练时把低频类别的图片多重复几次,或者在数据加载器里按类别权重采样。YOLO 系列没有内置的类别重采样开关,我一般是复制低频样本的 txt 和图片到额外目录,以提升它们在每个 epoch 中的出现比例。实践下来,把低频类样本权重提高到 1.5-2 倍通常能显著改善 recall,但代价是高频类别的精确率轻微下降。
6. 验证与进阶:用 mAP 兜底,用难例重采样止损
训练结束后,第一个动作不是看训练日志,而是跑一套独立的验证流程。先把 best.pt 在 val 集上做一次完整评估,把每个类别的 AP 单独打出来。发票场景里不要只看整体 mAP,因为整体指标会被高频类拉高。重点看金额、税额、发票号码这三个类的 AP,它们是后续财务自动化的核心字段,漏一个等于整条流程出错。
如果这三个类里有一个明显低于其他类,先做难例可视化:把 val 集里漏检和误检的样本导出,拼成一张图,逐张看是标注问题还是边界模糊问题。这一步看起来费时间,但能帮你少做很多无用调参。比如税额类的 AP 低,往往是因为购买方一栏和销售方一栏在票面上的结构相似,模型把销售方名称的尾部误检成税额,这种情况调增强没有用,需要靠难例样本重采样来纠正。
进阶操作建议按以下顺序走:
第一步,做难例重采样。把验证集里预测失败的图片收集起来,从原始数据集和外部采集里找同类的更多样本,补充进训练集。如果补充不了,就把这些失败样本提升采样权重,强制模型每个 epoch 多看几遍。
第二步,做多尺度推理测试。YOLO 训练时 imgsz 设为 1280,推理时测试 960、1280、1600 三个尺寸,观察小字段的召回率变化。很多场景下训练时 imgsz 不需要太大,但推理时放大输入可以显著提高小字段的召回。
第三步,输出结构化结果。检测框拿到之后,把每个字段裁出来做透视校正,再送进 OCR。发票场景常用的是把框内图像先做灰度化和二值化,再用 OCR 模型识别。检测模型的目标是“定位”,不要把识别职责也压给它。
第四步,如果是跨设备部署,把模型导出为 ONNX 或 TensorRT,并在目标设备上做精度校准。发票字段检测的推理环境如果是扫描仪采集的灰度图,注意在导出前统一输入通道和归一化参数,否则灰度图和训练时的三通道图之间的差异会造成精度骤降。
说个我自己踩过的坑:有一次为了省时间,我把同一张发票的正反面扫描图同时丢进了训练集和验证集,结果 mAP 报 0.92,我差点就以这个指标交付了。后来在真票测试里召回率跌了快一半,查了三天才发现是数据集划分污染。从那以后我每次做票据数据划分都强制走一遍按票据 ID 分组,并且划分完用重复图片哈希检查确认没有泄漏,已经成了习惯。这份发票字段检测数据集本身质量不错,但再好的数据也要过一遍格式校验和划分检查,否则训练出的模型最多只能在自家验证集上好看。希望帮到你。
本文还有配套的精品资源,点击获取