
简介面向安检场景的EDS X光危险物品识别检测数据集共4718张图片适用于机场、火车站智能安检及智能安防项目可帮助算法工程师快速验证与迭代目标检测模型。数据集涵盖笔记本电脑、手机平板、打火机、剪刀、压力罐、充电宝、雨伞、玻璃瓶、塑料瓶、刀具等10类常见违禁物品标注精确数据量充足多种检测算法可直接使用。压缩包共14155个文件其中4718张jpg原图对应4718个xmlVOC格式与4719个txtYOLO格式同时提供json标签满足不同训练框架的数据导入需求包体约499.67MB目录结构清晰。已有1547人学习下载该数据集源于实际安检落地项目算法拟合效果好数据质量可靠适合从事智能安防、目标检测研究的学生和工程师用于模型训练、效果评估与算法优化。1. EDS-安检X光危险物品识别检测数据集安检场景为什么缺的正是这一份做安检X光危险品检测最难的不是模型选型而是找不到像样的训练数据。X光图像和自然图像差异很大物体相互叠压、颜色单调、透视重叠直接拿COCO预训练权重往下套效果常常一言难尽。EDS-安检X光危险物品识别检测数据集正好补了这个缺口4718张安检X光场景图像配上VOC、YOLO、JSON三种格式的标签文件覆盖常见危险物品类别。对要快速验证检测算法或做安检场景定制化训练的工程师来说这份数据能从数据集准备阶段直接跳到模型训练阶段省掉大量标注和格式转换的时间。新手拿来学YOLO训练流程熟手拿来当迁移学习或格式转换的试验场都比较顺手。2. 三种标签格式结构拆解VOC、YOLO、JSON的边界与选型拿到数据集先别急着训练把三种格式各自的结构和边界搞清楚后面能少走很多弯路。VOC、YOLO、JSON虽然描述的是同一批标注信息但组织方式和适用框架完全不同。2.1 VOC一个XML文件描述一张图像的全部标注信息VOCPascal VOC是目标检测生态里最通用的数据组织方式之一特点是一张图对应一个同名XML文件。在EDS-安检X光数据集中每张X光图像的标注内容都记录在XML里包括图片尺寸、目标类别以及 节点下的四个坐标。一个典型XML标注长这样annotation folderEDS/folder filenameimg_0047.jpg/filename size width1024/width height768/height depth3/depth /size object namegun/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin290/xmin ymin210/ymin xmax520/xmax ymax430/ymax /bndbox /object object nameknife/name bndbox xmin180/xmin ymin330/ymin xmax350/xmax ymax490/ymax /bndbox /object /annotation里的四组坐标是图像像素绝对值 是类别名字符串正好是检测算法最需要的原始信息。VOC的优点在于坐标直观人工复核方便配合labelImg打开XML还能直接回改标注缺点是一张图一个文件批量解析速度比YOLO的TXT慢做数据统计也不方便。读取VOC标注时常用的是Python标准库xml.etree.ElementTree按节点逐层取值。需要注意的是 坐标范围是闭区间还是开区间。多数标注工具按闭区间存xmin90、xmax120时实际占用像素是90到119。做归一化转换时按xmax-xmin计算宽度会多算1个像素对640分辨率来说误差约0.0016虽然不影响训练但在做像素级裁剪或拼接验证时这1像素偏差会让边界出现肉眼可见的不对齐。我一般会在转换代码里统一按xmax-xmin计算宽度保证VOC转YOLO和YOLO转VOC用同一套规则避免来回转换导致边界漂移。2.2 YOLO一行文本一个目标训练读取效率最高YOLO格式是YOLOv5、YOLOv8、YOLOv9这些框架训练时最顺手的格式。EDS数据集里YOLO格式的TXT文件和图像同名每一行代表一个目标格式为“类别序号 中心x 中心y 宽度 高度”四个坐标全部相对图像宽高做了归一化取值范围在0到1之间。例如2.1里那张图对应的YOLO标注是两行0 0.403 0.417 0.225 0.286 1 0.259 0.534 0.166 0.208第一列类别序号由data.yaml里的names列表决定后面四列分别是x_center、y_center、w、h。和VOC比YOLO格式文件小、读取快训练代码拿到就能用不需要解析XML再换算坐标。但缺点也明显坐标是归一化浮点数人工核对不好读类别只有数字没有文字一旦names顺序写错整份标注全乱且不报错。YOLO格式的TXT对编码也比较敏感遇到中文系统默认的GBK写入容易乱码建议保存时统一指定UTF-8。另一个细节是TXT文件不要带BOM头Ultralytics在读取带BOM的TXT时偶尔会把第一列解析错现象是第一个目标的类别序号异常。检查方法很简单用Python的open(..., rb)读一下文件头三个字节出现EF BB BF就说明带BOM存盘时用encodingutf-8且不写BOM重新保存即可。2.3 JSONCOCO风格的annotations数组集中管理全部标注信息JSON格式是COCO数据集原生的标注结构在Faster R-CNN、Mask R-CNN、DETR以及mmdetection这类框架里最常见。它不像VOC那样一张图一个XML而是把整个数据集的图像信息、标注信息、类别信息集中到一个文件里大概结构如下{ images: [ {id: 1, file_name: img_0047.jpg, width: 1024, height: 768} ], annotations: [ {id: 1, image_id: 1, category_id: 1, bbox: [290, 210, 230, 220], area: 50600} ], categories: [ {id: 1, name: gun}, {id: 2, name: knife} ] }三个数组各有分工images记录图像id和真实尺寸annotations记录每个目标从属于哪张图、类别id、bbox和面积categories定义类别映射。用pycocotools计算mAP时这种格式是直接对接的不需要额外写转换器。比较关键的是COCO的bbox是[x, y, width, height]而VOC的bndbox是[xmin, ymin, xmax, ymax]一个是“起点加宽高”一个是“对角点”。做互转时不注意这个差异坐标就会整体错位。另外area字段在COCO评估时会直接使用如果转换时area算错mAP计算结果会异常但不会报错这种隐性错误很难排查。我一般会在转换后重新按bbox计算area覆盖写回不用原标注里的值。2.4 选格式的实用标准先看训练框架再看前后处理三种格式各有适用场景一上来就全部统一成一种格式反而给自己挖坑。我的取舍逻辑有三条线。训练YOLOv5、YOLOv8、YOLOv9系列时直接用YOLO格式目录不需要碰XML和JSON。准备用mmdetection或其他带COCO接口的框架时用JSON格式最省事因为框架自带的CocoDataset就是读这个格式。VOC格式则用来做原始标注保存和人工复核尤其是需要二次修标注、标注团队交接的场景VOC的可读性最有价值。所以我的习惯是三份都留着VOC当唯一原始标注源YOLO当训练主格式JSON当评估与迁移格式。后面做格式互转时也建议以VOC为转出源头而不是YOLO和JSON之间直接互转避免两套转换逻辑不一致导致的双向误差。3. 用YOLO格式训练安检模型目录整理、data.yaml和必调参数这一章直接落到YOLO训练。4718张图的数据量不大但足够走完一轮完整的训练、验证、调参流程。前提是目录结构、类别配置和训练参数别出岔子。3.1 按YOLO习惯整理数据集目录YOLOv5/8/9的默认约定是images目录放图片、labels目录放TXT标签各自再按train和val分成两个子集。EDS数据集拿到手后先按这个习惯规整目录dataset/ ├── images/ │ ├── train/ │ │ ├── img_0001.jpg │ │ └── ... │ └── val/ │ ├── img_0045.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── img_0001.txt │ │ └── ... │ └── val/ │ ├── img_0045.txt │ └── ... └── data.yaml如果原始目录不是这个结构写个脚本按比例拆分。4718张按8:2分大约3775张训练、943张验证足够支撑一轮完整实验。我常用的拆分脚本长这样import os import random import shutil random.seed(42) img_src raw_images # 原始图片目录 label_src raw_labels # 原始TXT标签目录 out_root dataset split_ratio 0.8 images [f for f in os.listdir(img_src) if f.endswith(.jpg)] random.shuffle(images) split_idx int(len(images) * split_ratio) for phase, subset in [(train, images[:split_idx]), (val, images[split_idx:])]: os.makedirs(f{out_root}/images/{phase}, exist_okTrue) os.makedirs(f{out_root}/labels/{phase}, exist_okTrue) for img in subset: txt img.replace(.jpg, .txt) shutil.copy(os.path.join(img_src, img), f{out_root}/images/{phase}/{img}) # 关键用同名替换生成txt路径保证jpg和txt永远一起走 shutil.copy(os.path.join(label_src, txt), f{out_root}/labels/{phase}/{txt})拆分逻辑的核心是先对全部图片名单做shuffle再按同一份名单复制图片和标签。txt路径用jpg名单replace后缀得到从机制上避免图片标签顺序错位。random.seed(42)保证每次跑同一个脚本得到完全相同的拆分结果复现实验时很重要。还有个细节数据集里如果存在没有标注文件的图片上面的脚本直接复制会报错。写正式流程前最好加一个os.path.exists判断把没有对应txt的图片单独挑出来否则训练时会遇到“孤儿图片”报错样式多种多样不好排查。注意拆分完成后跑一条统计命令对比train/images和train/labels两个目录的文件数数量不一致就先别训练。3.2 data.yaml类别顺序千万别改YOLO格式训练时data.yaml承担着把TXT里的数字序号映射为具体类别的职责。一个基本配置如下类别名以数据集实际标注为准path: ./dataset train: images/train val: images/val nc: 4 names: 0: gun 1: knife 2: bottle 3: explosivekey point是names的顺序必须和TXT文件里的第一列数字严格对应。TXT里的类别序号不是类别名如果只改names顺序不改TXT内容训练不会报错但所有框的类别整体错位。更隐蔽的是如果换了一组类别名但数量不变loss曲线看起来依然正常只有混淆矩阵能看出类别互相串位。检查方法很直接拆分完成后随机抽几个TXT把第一列数字在names里查一遍再对照原图视觉确认类别是否一致。如果是JSON或VOC转过来的YOLO建议转换脚本里顺便打印一次完整映射表转换时不打印映射表等于给自己留隐患。3.3 YOLOv8训练命令从预训练权重到自训练用Ultralytics框架训练最小命令只需要这一条pip install ultralytics yolo detect train \ modelyolov8n.pt \ datadataset/data.yaml \ epochs100 \ imgsz640 \ batch16 \ device0如果想从零训练、不加载任何预训练权重把model参数换成yolov8n.yamlyolo detect train \ modelyolov8n.yaml \ datadataset/data.yaml \ epochs100 \ imgsz640 \ batch16建议先用yolov8n.pt做迁移学习。安检X光图像和自然图像差异大但COCO预训练的基础特征提取能力依然能显著加快收敛。4类目标、4718张图的数据量用n级模型跑100个epoch训练成本和推理速度都比较划算s级模型精度更高但显存需求也大根据自己显卡情况权衡。关键参数逐个说imgsz640是默认值如果原图是1024以上提到768或1024对小目标和密集目标有帮助但训练时间几乎翻倍。batch先从16试显存溢出就降到8或4别硬撑。epochs不建议一上来就调大先跑100看loss曲线形态再决定加练还是提前收。X光成像里存在大量半透明叠影和低对比度目标对分辨率的需求比自然图像更高。如果在val集上频繁漏检小物件比如刀具的刀刃第一个该动的是imgsz而不是直接换更大模型。这个顺序调错会浪费很多时间。3.4 训练结果怎么看认准best.pt和三条曲线训练结束后成果默认落在runs/detect/train/目录下weights/best.pt和weights/last.pt是两个核心文件。best.pt按验证集指标选取last.pt是最后一个epoch状态。实际部署优先选best.pt不要因为last.pt训练loss更低就换它训练loss低不代表泛化好。训练过程中重点盯三条曲线val/box_loss、metrics/precision、metrics/recall。正常节奏是前20到30个epoch快速下降之后缓慢收敛。如果precision和recall从头到尾抖动不收敛优先检查两类问题一是data.yaml类别顺序和TXT不一致二是train/val拆分时图片和标签走散。4718张图不算大但足够检验一个检测模型在安检场景下的基础能力。第一次训练结果如果mAP50能到0.7以上说明数据质量和训练流程没问题下一步重点是调参数和扩难例。如果mAP50在0.5以下基本可以断定存在数据或配置层面的系统性问题先回头复查前两章的内容别急着换模型。4. 三种格式互转的脚本写法与避坑清单数据集同时提供VOC、YOLO、JSON三种格式本意是让你直接按需取用。但实际项目里经常会遇到格式不匹配的情况比如YOLO格式的类别顺序和你的需求不同或者你想用mmdetection接收JSON但缺几个字段。这一章讲清楚互转脚本怎么写以及我自己踩过的一堆坑。4.1 VOC转YOLO中心点和宽高的换算公式别记反VOC转YOLO是最高频的需求。VOC里四个值是xmin、ymin、xmax、ymaxYOLO要的是中心点坐标和宽高再除以图像宽高做归一化x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h完整转换脚本import xml.etree.ElementTree as ET import os xml_dir VOC/xmls # VOC格式XML目录 txt_dir YOLO/txts # YOLO格式TXT输出目录 os.makedirs(txt_dir, exist_okTrue) # 类别名到序号映射按实际数据集的类别定义调整 class_map { gun: 0, knife: 1, bottle: 2, explosive: 3, } def voc_to_yolo(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): # 注意是findall不是find name obj.find(name).text if name not in class_map: continue cls_id class_map[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n) with open(txt_path, w, encodingutf-8) as f: f.writelines(lines) for file in os.listdir(xml_dir): if file.endswith(.xml): voc_to_yolo( os.path.join(xml_dir, file), os.path.join(txt_dir, file.replace(.xml, .txt)) )逻辑上重点检查三点类别映射表是否完整、图片尺寸是否取的真实图像尺寸、输出浮点数精度是否够。6位小数对640分辨率足够误差在千分之一像素级别如果只保留3位小数会在高分辨率训练时造成框的轻微抖动影响结果复现性。脚本里我特意用了findall而不是find用find会只取第一个object转换后TXT行数明显少于XML里的目标总数这种错误很隐蔽。4.2 JSON转VOCcategory_id和坐标格式是两个不对齐的坑从COCO风格的JSON向VOC转最大的坑不是坐标换算而是category_id的语义。COCO的categories数组id从1开始VOC里类别名没有数字序号YOLO的类别序号习惯从0开始。直接把category_id当YOLO类别号用所有类别统一偏移一位训练结果全错。另一个容易错的是把COCO的bbox直接抄进XML。bndbox需要xmin、ymin、xmax、ymaxCOCO bbox给的是x、y、width、height转换时要xmaxxwidth、ymaxyheight# JSON转VOC的核心片段 for ann in annotations: x, y, w, h ann[bbox] xmin, ymin int(x), int(y) xmax, ymax int(x w), int(y h) cat_id ann[category_id] cls_name categories[cat_id] # categories是id-name的映射字典 # 接下来用cls_name和xmin/ymin/xmax/ymax生成XML节点category_id对齐的做法是转换开始前先把categories数组打印一遍确认id是从1开始还是从0开始有没有跳号。很多COCO风格数据集的annotations对象category_id都做了归一化但也有例外。稳妥做法是把类别表改成以id为key的字典不要用数组下标访问这样即使id从200开始也能准确对应类别名。4.3 JSON转YOLO和YOLO转JSON归一化和反归一化的基线要一致数据集同时提供三种格式意味着你转换时有一个天然的标准答案可以用来校准。比如YOLO转回JSON时归一化坐标要乘回图像宽高这里的宽高必须来自images数组里记录的原始尺寸而不是训练时的resize尺寸。反过来JSON转YOLO时归一化的除数也要用同一份原始尺寸。隐藏的坑是有的X光数据集在导出JSON时images字段里的width/height记录的是原始大图尺寸但annotations里的bbox坐标却是基于缩略图标注的。这种情况下直接按images宽高做归一化出来的YOLO框全部变形。最稳妥的兜底做法是转换后用PIL打开原图核对真实尺寸from PIL import Image img Image.open(path/to/img_0047.jpg) real_w, real_h img.size如果JSON元数据不可信就用真实尺寸做归一化。但要注意如果图像在数据集生成阶段确实被压缩过且标注是压缩后画的那用原图尺寸反而不对这时要以标注时的尺寸为准。判断方法是对照同一张图的VOC标注如果VOC和YOLO两个坐标系都能和图像内容对齐说明尺寸选对了对不齐就说明基准错了。地面真实情况是这种“元数据和实际标注脱节”的问题很难提前发现只能靠转换后再人工抽几张图像核对。省掉这一步后面训练出了问题都不知道是模型问题还是数据问题。4.4 转换和训练过程中的6个真实踩坑前面几节是转换逻辑这一节把常见现象按“现象→原因→解决”拆开方便直接对照。坑1转换后所有框都不在原位现象用转换后的YOLO标注训练预测框位置整体偏移看起来每框都画在物体旁边但文件读取和训练log都正常。原因VOC转YOLO时使用了XML里的size做归一化但XML尺寸和图像实际尺寸不一致常见于数据集被压缩resize后没同步更新XML。解决转换代码里直接读图像文件获取真实尺寸或至少加断言检查XML里的size和实际图片尺寸一致不一致就停止转换并打印警告。坑2类别全部错位一位现象训练不收敛混淆矩阵中预测类别集中在相邻几个类别上本来完全不同的两类却经常互相混淆。原因JSON的category_id从1开始YOLO的类别序号从0开始转换时没有做偏移。解决建立显式的id映射字典转换完成后随机抽3个样本把TXT里数字查表得到的类别名与原图对照。坑3TXT里出现小于0或大于1的坐标现象训练日志大量输出warn部分样本的验证指标异常波动。原因标注框本身超出图像边界或者转换脚本用了错误的尺寸导致归一化后坐标越界。解决转换后加一道clip操作把坐标限制到[0,1]。同时把越界原始框单独列出来确认是标注问题还是转换问题。框超出边界不多就手动clip掉超出很多多半是尺寸用错了。坑4train/val拆完发现图片和标签对不上现象训练时报大量“No labels found”验证集标签文件夹几乎是空的但目录里看文件确实都在。原因拆分脚本分别获取了jpg和txt两个列表各自shuffle后按各自顺序截取两边文件名没有配对。解决拆分逻辑改成以图片为基准用jpg名replace出txt名同一循环里移动配对文件。拆分结束后统计train/images和train/labels的文件数不等就重拆。坑5一个XML里多个同类目标转换后漏掉一部分现象转换出来的TXT行数明显小于XML里object节点数量。原因转换脚本用了find(object)只取了第一个目标应该用findall(object)遍历全部。解决统一用findall转换后做计数校验每个TXT的行数要等于对应XML里的object总数不等就报错。坑6loss掉得很快但mAP一直上不去现象val loss低precision和recall持续偏低训练曲线看起来很健康。原因类别分布严重不均占多数的类别主导了loss下降少数类别几乎没学到特征。解决先统计每个类别的样本量对样本少的类别调整cls loss权重或做上采样也可以用更轻量的模型配合更长epochs训练。5. 验证模型别只盯着mAP安检场景的三个额外检查mAP是评估模型的核心指标但安检X光危险品检测里只看mAP容易漏掉真实问题。安检图像目标互相遮挡、透明叠影多模型可能在mAP上表现不错一到实际图像就频频漏检。建议在模型验证阶段加上三个额外检查项。第一项是recall优先。安检场景的核心诉求是不漏检误报可以被复检环节过滤漏报直接造成安全风险。用验证集画出P-R曲线找到recall稳定在0.9以上的置信度点做阈值再评估这个阈值下的precision。如果业务方对误报率高敏感再加一层二次过滤处理低置信度的false positive。这个取舍逻辑比单纯追求mAP贴近业务实际。第二项是难例切片验证。从验证集里挑出背包多物品互相重叠、目标密集的图像单独归一个hard子集专门跑一轮模型验证。这类图像最容易暴露模型短板。如果hard子集上recall跌得厉害优先尝试提高imgsz或做tile切片推理这两种做法在小范围子集上可以快速对比不需要重训整个模型。第三项是数据闭环。4718张图对安检场景的复杂性来说不算大跑完第一轮后把验证集里预测失败但置信度高的图像捞出来二次标注后并回训练集。这样迭代两到三轮的收益通常会大于调参和换模型结构的收益。我自己盯这些检查项时习惯把每一轮的混淆矩阵和hard子集recall单独存档回看前后改动到底提升在哪。安检场景的模型能不能上线最终不看单个指标而是看典型失败样本有没有被一个个消灭。这份EDS-安检X光数据集本身是个合适的试验场把流程跑通后再切回自己的业务图像剩下的只是数据适配问题。希望这一路踩坑记录能帮到你。本文还有配套的精品资源点击获取