安检X光危险物品识别数据集:VOC与YOLO双格式+YOLOv8训练实践

发布时间:2026/10/10 18:20:14
安检X光危险物品识别数据集:VOC与YOLO双格式+YOLOv8训练实践
简介面向安检X光图像中的危险品自动识别与目标检测任务这份数据集经过整理与标注覆盖刀、匕首、刀片、剪刀、喷雾罐、玻璃瓶、塑料瓶等12个常见违禁品类适用于训练YOLO、SSD、Faster R-CNN等主流检测模型也可用于算法研究与毕设课题。资源包共2000个文件主体为Pascal VOC格式的XML标注文件1999个同时提供YOLO格式的TXT标注可以在不同框架间灵活切换压缩包整体约350.67MB解压后目录结构清晰便于直接划分训练集与验证集。目前已有853人学习下载对需要现成标注数据快速开展实验的计算机视觉学习者或安检项目开发者来说这套数据能显著减少采集与标注成本并支持模型迭代验证使用者无需自行转换标签格式可集中精力进行网络结构设计与性能调优是一份即取即用的高质量基础数据集。1. 安检X光危险物品识别检测数据集9551张图、12类VOC与YOLO双格式怎么选做安检机图像识别的项目最头疼的不是模型选型而是找不到能直接拿来训练的X光数据集。开源社区里要么是你不认识的合成图要么是标注格式东一份西一份。这个安检X光危险物品识别检测数据集直接把Pascal VOC的xml和YOLO的txt两种格式都配齐了9551张真实X光安检图片12个标注类别解压后不用写一行转换脚本就能进训练流程。它解决的是安检场景目标检测的数据冷启动问题适合在做安检项目复现、毕业设计、以及想验证YOLOv8/v5在自己数据集上效果的工程师和学生。我实际跑过一遍今天把文件结构、标注细节、训练参数和容易翻车的点一次说清楚。2. 数据集内部结构从文件名到标注格式全拆解2.1 文件组成jpg、xml、txt一一对应拿到压缩包后先别急着解压到任意目录。这个数据集的命名规则很统一每个图片文件对应一个同名xml和同名txt。比如项目里你能看到firc_xray_3342.xml、firc_xray_8358.xml这样的文件它们分别和firc_xray_3342.jpg、firc_xray_8358.jpg搭配。我习惯按下面的结构摆放FIRCXray/ ├── images/ │ ├── train/ │ │ └── firc_xray_3342.jpg │ └── val/ │ └── firc_xray_8358.jpg ├── Annotations/ │ ├── train/ │ │ └── firc_xray_3342.xml │ └── val/ │ └── firc_xray_8358.xml └── labels/ ├── train/ │ └── firc_xray_3342.txt └── val/ └── firc_xray_8358.txt注意xml和txt里的内容本身没有区分训练集和验证集它们在语义上是同一份标注的两种写法。真正划分train/val是靠我们自己按比例切分这一点在第三章会给出脚本。打开之后你会发现xml文件数量、txt文件数量和jpg文件数量都是9551也就是说没有多余的、缺漏的标注这一点在二手数据集里已经算良心了。2.2 标签类别对照与中英文误区数据集的12个类别是Cans、CartonDrinks、GlassBottle、PlasticBottle、SprayCans、SwissArmyKnife、Tin、VacuumCup、blade、dagger、knife、scissors。这里有个坑非常值得先说项目描述里给的中文名有不少机翻痕迹比如“CartonDrinks”写成了“纸箱冲洗”实际应该是“纸盒饮料”“VacuumCup”写成了“真空吸盘”其实是“真空保温杯”“Tin”也不是元素锡而是“锡罐/金属罐”。如果你要做界面显示千万别照抄那行中文名。英文类别建议中文名说明Cans罐头/易拉罐常见金属罐体CartonDrinks纸盒饮料利乐包类饮品不是“纸箱冲洗”GlassBottle玻璃瓶透明瓶体X光下呈浅色PlasticBottle塑料瓶密度比玻璃低SprayCans喷雾罐带喷嘴的压力罐SwissArmyKnife瑞士军刀折刀形态Tin锡罐/金属罐铁皮罐体比Cans更扁VacuumCup保温杯双层杯体不是“真空吸盘”blade刀片单独的小刀片dagger匕首笔直双刃knife刀具日常刀具scissors剪刀X光下特征明显另外前8个类别的首字母是大写后4个类别是小写比如“blade”的b是小写。初学者在写data.yaml时容易把“SwissArmyKnife”拼错或者把“scissors”写成“Scissors”导致训练时类别数量对不上。建议直接从xml里提取name字段生成yaml不要手打了事。2.3 VOC与YOLO标注格式内的坐标关系xml文件和txt文件表达的是同一个框但坐标体系不同。xml里记录的是真实像素坐标比如firc_xray_3342.xml打开后会看到annotation folderfirc_xray/folder filenamefirc_xray_3342.jpg/filename size width850/width height650/height depth3/depth /size object nameknife/name bndbox xmin128/xmin ymin95/ymin xmax310/xmax ymax260/ymax /bndbox /object /annotation对应的txt文件则是归一化后的YOLO格式一行一个目标格式为class_id x_center y_center width height全部是相对于图片宽高的比例值。上面这个knife框如果图片宽850、高650那么txt里应该是10 0.257647 0.273077 0.214118 0.253846其中类别编号10对应knife按类表顺序从0数起x_center(128310)/2/850≈0.2576width(310-128)/850≈0.2141。每次拿到新数据集我都会抽两三张图用这个公式手算一遍确认txt坐标范围在0到1之间既不是负数也没有大于1避免后续训练时读取到非法框。2.4 双格式为什么同时提供这个数据集同时提供VOC和YOLO格式本质上是为了适配不同训练框架。Ultralytics YOLO系列直接读取txt文件配合images目录就能训练这也是目前最省事的路径。而MMDetection、Detectron2或者一些传统目标检测教程习惯用VOC xml格式做数据加载或者再转成COCO json。官方把两种格式都给了等于把“格式转换”这一步直接替你做了。但要注意这个数据集没有提供分割标签只有目标检测框。如果你在网上下载了一个号称“VOCYOLO”的数据集解压后发现里面带了segmentation类的txt那才是实例分割格式。这个数据集只有一个目标检测的定位框别指望拿去做分割训练。另外xml文件里只有最基本的bndbox和name没有difficult、truncated这种字段后面我会讲这个问题怎么绕开。3. 快速上手把双格式数据跑进YOLOv8训练流程3.1 环境准备与目录规整工欲善其事必先利其器。训练环境我建议直接用Ultralytics的YOLOv8它对初学者最友好对熟悉YOLOv5的人也很平滑。先把依赖装好conda create -n finn python3.10 -y conda activate finn pip install ultralytics8.2.0我用的是8.2.0这个版本因为后续导出ONNX时碰到的坑相对少。接下来把解压后的数据划分成训练集和验证集。这个数据集官方没有划分训练/验证比例我习惯按8:2划分并且用随机种子固定下来保证每次复现结果一致import os import random import shutil random.seed(42) image_dir FIRCXray/Images train_ratio 0.8 images [f for f in os.listdir(image_dir) if f.endswith(.jpg)] random.shuffle(images) split_idx int(len(images) * train_ratio) train_images images[:split_idx] val_images images[split_idx:] base_dir FIRCXray for sub in [images/train, images/val, labels/train, labels/val, Annotations/train, Annotations/val]: os.makedirs(os.path.join(base_dir, sub), exist_okTrue) def move_set(img_list, img_sub, lab_sub, xml_sub): for img_name in img_list: stem os.path.splitext(img_name)[0] shutil.copy(os.path.join(image_dir, img_name), os.path.join(base_dir, img_sub, img_name)) shutil.copy(os.path.join(base_dir, labels_all, stem .txt), os.path.join(base_dir, lab_sub, stem .txt)) shutil.copy(os.path.join(base_dir, Annotations, stem .xml), os.path.join(base_dir, xml_sub, stem .xml)) move_set(train_images, images/train, labels/train, Annotations/train) move_set(val_images, images/val, labels/val, Annotations/val)上面这段脚本会把你原来平铺的jpg、txt、xml按YOLO训练惯例重新整理到images/train、labels/train这样的结构里。重点是让同一个文件名的图片和txt始终在同一个子集下不会出现“图片在训练集标签在验证集”这种低级错误。实际使用中如果你自己对数据集已经按文件夹分好类要保证图片名称和标签名称完全一致扩展名不同不影响。3.2 编写data.yaml并训练YOLOv8训练自己的数据集核心是写一个data.yaml。这个文件的路径写法要注意最好用绝对路径避免和当前工作目录纠缠。我把data.yaml放在数据集的根目录下path: /home/user/data/FIRCXray # 改成你的实际路径 train: images/train val: images/val nc: 12 names: 0: Cans 1: CartonDrinks 2: GlassBottle 3: PlasticBottle 4: SprayCans 5: SwissArmyKnife 6: Tin 7: VacuumCup 8: blade 9: dagger 10: knife 11: scissors这里的names顺序必须和labels里的class_id对应。怎么验证用Python读一个xml按类表顺序输出它的类别名索引再去读对应txt的第一个数字两者应该一致。我踩过这个坑有一次把某个数据集的names列表按字母序排了结果模型训练出来预测的类别全是错位的。接下来启动训练。我用YOLOv8s而不是n因为X光图里的危险品边缘和遮挡多模型容量太小容易欠拟合。命令如下yolo detect train data/home/user/data/FIRCXray/data.yaml \ modelyolov8s.pt epochs120 imgsz640 batch16 device0 \ mosaic1.0 close_mosaic10几个参数的用意说明一下imgsz640是YOLO系列比较通用的输入尺寸X光原图分辨率并不统一640能兼顾速度和精度batch16取决于你显卡的显存如果你是8G显存建议改成8close_mosaic10的意思是最后10个epoch关闭Mosaic增强让模型在接近真实分布的数据上收敛这一点在安检这种小目标多的场景非常管用否则最后几个epoch的loss会下降得很怪。3.3 训练日志与指标读取训练结束后结果默认保存在runs/detect/train/目录下。我每次会先打开results.csv看第5列的mAP50和第6列的mAP50-95。注意看两个趋势一是验证集loss有没有和训练集loss一起下降如果验证loss在训练后期反弹说明过拟合epoch拉到100就够二是mAP50-95和mAP50的差值是否过大正常X光场景差值在10到15个点之间如果差值超过20说明框的质量普遍不高可以试试调低置信度阈值或改anchor。如果目录里没有生成confusion_matrix.png你可以在训练命令后面加plotsTrue。这个混淆矩阵能直观告诉你哪些类之间容易互相误判比如plastic bottle和glass bottle在X光下都是浅色半透明误判率通常偏高。看了矩阵后再决定要不要加类别权重比盲目调lrf有效得多。4. 常见问题排查标注格式翻车与类别不平衡4.1 xml里缺difficult字段导致转换脚本报错现象我用一个常规的VOC转COCO脚本转换这个数据集脚本直接崩了报错KeyError: difficult。原因标准的VOC xml对象里有difficult字段但这个数据集的xml只写了name和bndbox没有difficult也没有truncated。解决要么改用现成的YOLO txt直接训练要么在转换脚本里给每个对象硬编码difficult0。从那以后我拿到VOC格式数据第一件事就是先用grep -c difficult扫一遍所有xml免得脚本写到一半翻车。4.2 类别中文名机翻导致的标签错乱现象朋友把data.yaml里的names直接翻译成中文训练时提示Expected 12 classes, but got 11。原因他把“VacuumCup”翻译成“真空吸盘”又把“CartonDrinks”当成背景类删掉了导致类别数量和txt里的数字对应不上。解决忽略项目说明里的中文名一切以xml里的name值为准并用脚本提取类别清单。不要相信二手数据集里任何翻译后的类别名机器翻译会把“保温杯”变成“真空吸盘”这种错误在训练阶段非常隐蔽。4.3 图片被resize后txt坐标越界现象训练前为了统一分辨率用OpenCV把所有图片缩放到416x416但没重新计算txt坐标导致训练时大量告警WARNING: 2 labels in ... have out of bounds coordinates。原因YOLO的txt是相对于原图宽高的归一化坐标直接resize图片而不改txt相当于把坐标原点也拉伸了。解决要么放弃人工resize让YOLO训练时自动做letterbox要么写个脚本先把resize后的新坐标算出来再更新txt。我的经验是除非你的显卡对固定输入尺寸有极强制约否则不要自己预先resize让训练框架处理更安全。4.4 blade和scissors小目标检测率偏低现象训练完发现blade的mAP50只有45%而Cans有92%。原因blade数量少、目标小在YOLO的五个下采样层里特征几乎被磨光。解决第一训练时用mosaic1.0和scale0.5让小目标在拼接增强中多次出现第二把imgsz提到768或1024代价是显存和训练时间第三对blade和scissors做在线随机裁剪复制把这些小目标周围一块区域抠出来粘贴到其他图上。这个办法比较暴力但能有效缓解类别不平衡。4.5 随机划分导致某个类在验证集里没有样本现象按照默认0.8/0.2比例随机划分后验证集中没有任何“dagger”样本训练时每个epoch都提示标签为空。原因dagger在数据集中个数本就不多随机划分的偶然性会让某个小类完全掉进训练集。解决写一个按类别分布进行分层划分的脚本保证验证集里每个类别都有至少一张图。我一般用sklearn.model_selection.StratifiedShuffleSplit以每张图的第一个类别标签作为分层依据。代码不复杂但能省去后续反复补验的麻烦。5. 从训练到落地验证指标与模型导出细节5.1 用精确率和混淆矩阵给“危险品”把关安检场景和一般目标检测不一样漏检一个刀片的代价远高于误报一个保温杯。训练结束后不要只盯着mAP50看我会先打开PR_curve.png找到precision和recall曲线的交点附近选一个偏向高召回率的置信度阈值。比如模型默认信度是0.25在安检场景下我会把它降到0.15让blade这类小目标更容易被框出来代价是误报数量变多这个需要业务层再做一次二次判断。混淆矩阵里如果scissors误判成knife问题不大如果plastic bottle误判成glass bottle在安检机上也还算能接受真正要命的是把blade判成background那说明你的模型根本没有在该区域产生候选框需要回炉调anchor或imgsz。5.2 导出ONNX并在安检机上验证训练完成后部署到安检机边缘设备通常要导出成ONNX。Ultralytics的导出命令很简单yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 opset12导出后用onnxruntime跑一张测试图确认输出维度我是拿这个脚本快速验证的import onnxruntime as ort import numpy as np import cv2 sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) # BGR转RGB并转CHW img np.ascontiguousarray(img, dtypenp.float32) img / 255.0 img img[None, ...] outputs sess.run(None, {input_name: img}) print(outputs[0].shape) # (1, 84, 8400)输出维度中的84对应“4个box坐标 80个COCO类别概率”但你的模型只有12类所以这里应该是“4 12 16”。如果你导出的模型维度不是16赶紧停下来检查类别映射别等到部署现场才发现。ONNX里面的输出已经包含了置信度筛选逻辑在后续部署时也要注意opset12是对大部分推理框架兼容性较好的版本太新或太旧都可能遇到算子不支持的问题。5.3 实际项目里的数据补充策略模型上线后安检机会不断产生新的X光图像。我一般会把客户确认过的“漏检图”和“误报图”每周整理一次按同样的标注规范追加到这个数据集的尾部重新训练一版增量模型。追加数据时类别序号必须保持和原数据集完全一致不能因为新加了类别就擅自重排。比如原来的knife是10你在追加数据后不能把它改成其他数字否则旧模型权重全部作废而且混合训练时所有历史框的class_id都会错乱。每次训练完我都会单独用一份固定的历史验证集跑一遍确认老类别的mAP没有明显下降——这个动作能拦住很多回归问题。我第一次拿这个数据集跑实验时直接用了YOLOv8的默认参数训练结果blade的召回率只有四成。后来我做了一次类别分布统计发现blade的实例数量只有Cans的十分之一。从那以后我每次做安检项目拿到新数据集都会强制自己先跑一遍类别统计脚本在开始训练前就决定要不要做过采样或调类别权重而不是等一轮训练跑完再返工。希望帮到你。本文还有配套的精品资源点击获取