拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLO目标检测在X光安检场景的实战:数据格式、训练配置与避坑指南

YOLO目标检测在X光安检场景的实战:数据格式、训练配置与避坑指南

简介:面向YOLO系列目标检测算法训练与验证的X光安检数据集,收录5000张真实安检场景高清图片,覆盖常见违禁品与复杂背景,使用LabelImg仔细标注,标注框质量高,适合安检智能识别模型研发、目标检测课程实践与科研评测。压缩包内共2000个文件,主要由1986个xml标注文件、6个html教程、5个txt索引文件与3个py划分脚本组成,整体大小约321MB。标签提供VOC(xml)、COCO(json)与YOLO(txt)三种格式并分别存放,下载后可直接接入YOLOv5、YOLOv8等常见框架训练;随包附赠Linux与Windows双平台的环境搭建教程、基于案例修改训练自己的数据集的教程,以及划分训练集、验证集、测试集的Python脚本,便于灵活处理图片与标签、生成ImageSets索引。目前已有289人学习下载,适合需要高质量真实标注数据与完整配套指引的目标检测初学者、算法工程师及课题研究使用。

1. 为什么YOLO在X光安检场景上容易翻车:数据比模型更卡脖子

X光安检图像和自然场景图像是两种物种。自然图像有颜色、纹理、背景分离,X光图只有透视叠加的灰度轮廓,手机、充电宝、刀具、雨伞堆叠在一起,互相遮挡,小目标密集。用COCO上预训练的YOLO目标检测权重直接迁移到安检场景,掉点是常态——不是模型不行,是数据分布差太远。这份YOLO目标检测X光安检数据集正好补这个缺口:5000张真实场景X光图片,统一用LabelImg标注,同时导出VOC、COCO、YOLO三种格式标签,外加划分脚本和Windows/Linux两套环境搭建与训练教程。适合三类人:做安检、安防设备算法落地的工程师,刚入门想拿真实数据集练手的同学,以及需要处理多格式标签做数据工程的人。

2. 三种标签格式怎么选:从坐标表示差异到首遍核对清单

2.1 X光图像为什么让通用检测器失灵

先理解数据,再谈训练。X光安检图像的成像原理决定了它的三个特点:第一,物体透视叠加,同一像素上可能同时有多个物品的衰减信号;第二,整体是灰度图,没有颜色信息,模型学不到自然图像里那种“红色是苹果”的强先验;第三,背景复杂且不可控,行李箱里的衣物、电子元件、金属结构都会成为干扰。

这三个特点直接导致一个现象:COCO预训练权重在X光数据上迁移效果明显变差。很多同学第一反应是换更大的模型、调更强的数据增强,但我拆过这类项目后最大的感受是——先别折腾模型,先看数据本身够不够干净。5000张这个量级在X光场景下属于“能跑、但想涨点要靠数据质量”的档位,标注框有没有抖动、类别有没有漏标、边界是否贴住目标边缘,比模型选型的影响大得多。

这个数据集的说明里强调“标注框质量高”,拆开看指的应该是:框贴合目标轮廓,没有大面积外扩;同一类别命名一致,没有出现“knife”和“刀”这种混用;对遮挡严重的物体也做了标注而不是跳过。这些点在前几轮训练里看不出来,等到你想把mAP从80推到90以上时,全都会变成瓶颈。

2.2 VOC、COCO、YOLO格式的坐标表示差异

这个数据集把三种标签分别放在不同文件夹下,这个动作本身省掉了你写转换脚本的功夫,但三种格式的差异你还是得心里有数,后面核对、迁移、二次开发全用得上。

维度VOC(xml)COCO(json)YOLO(txt)
文件后缀.xml.json.txt
坐标内容bndbox里的xmin、ymin、xmax、ymaxannotations里bbox字段,格式为[x, y, width, height]每行一个目标:class x_center y_center width height
坐标基准原图像素绝对坐标原图像素绝对坐标归一化相对坐标,值在0到1之间
典型配套ImageSets/Main下的索引txt官方COCO训练管线ultralytics系列YOLO直接读的目录结构

VOC和COCO存的是像素坐标,你打开xml能看到<xmin>120</xmin>这种具体数值;YOLO的txt里所有数都在0到1之间,因为它做了归一化。转换关系是固定的公式:把VOC的xmin、xmax换算成中心点坐标,就是cx = (xmin + xmax) / 2 / image_width,宽度是w = (xmax - xmin) / image_width,y方向同理。COCO的[x, y, width, height]里的x、y是左上角坐标,转YOLO时要用cx = x + width / 2。

这三套数据你在项目里只用一套就够了。我一般默认直接用YOLO格式的txt,因为ultralytics系列的训练管线只认目录结构和txt;VOC和COCO格式留着做两件事——给老版本YOLO的VOC训练流程用,或者给跨框架实验做对照。

2.3 首次核对:先别急着训练

拿到手先做一遍抽检,这步能帮你避掉后面一半的坑。打开图片和标签文件夹,随机抽三到五张原图,同时打开对应的txt文件,把归一化坐标乘回图片宽高,用脚本画框叠在原图上,看框和物体对不对得上。我用的是LabelImg的“打开目录+打开标注”模式,点Next Image快速翻几十张,重点看两类问题:一类是明显空标签的图,另一类是框明显偏大、把背景大面积框进去的标注。

提示:这个抽检动作花不了十分钟,但它决定了你对整个数据集的信任程度。直接开训练后再发现问题,回头排查的成本是十倍以上。

另外,解压后先确认一下图片和三种标签的文件名是否一一对应,有没有纯标签没图片、或者纯图片没标签的情况。文件级的不一致在训练阶段不会直接报错,而是体现在loss曲线诡异、mAP上不去这些让你无从下手的现象上。

3. 划分脚本实战:图片与标签同步搬家,随机种子决定成败

3.1 三套脚本分别解决哪类需求

解压后能看到三套划分工具:训练集、验证集、测试集划分脚本,训练集、验证集划分脚本,以及split_train_val生成ImageSets下txt文件的脚本。前两个解决的是“把图片和标签按比例复制到新目录”的问题,差别只是一个分三份、一个分两份;第三个走的是VOC路线,生成的是ImageSets/Main下的索引txt文件。

这三套脚本解决的核心痛点是一致的:图片和标签必须同步移动。你手动拖文件夹时,很容易出现图片进了train、对应的标签还在原目录的情况。脚本按文件名前缀建立图片和标签的映射关系,在一个循环里同时处理两者,从机制上保证了同步。这也解释了为什么脚本要求图片和标签的文件名严格一致——不一致的配对在这个逻辑下会直接丢失。

根目录里那个train_list.txt就是作者跑通脚本后的示例产物。你拿它和自己脚本生成的txt对比一下格式,能快速确认是不是同一套约定。

3.2 按比例划分脚本的核心逻辑

以“训练集、验证集、测试集划分脚本(图片标签划分写入新文件夹)”为例,我按它的行为复刻了一个等价思路。这段代码写清了核心机制,路径换成你自己的就能跑:

import os import random import shutil random.seed(42) # 固定随机种子,保证每次运行划分结果一致 src_img = "/path/to/your_ds/images" src_lab = "/path/to/your_ds/labels" out_dir = "/path/to/your_ds/split_output" train_ratio = 0.8 val_ratio = 0.1 # test_ratio = 1 - 0.8 - 0.1 = 0.1 for split in ["train", "val", "test"]: os.makedirs(os.path.join(out_dir, split, "images"), exist_ok=True) os.makedirs(os.path.join(out_dir, split, "labels"), exist_ok=True) all_files = [ f for f in os.listdir(src_img) if f.lower().endswith((".jpg", ".png", ".jpeg")) ] random.shuffle(all_files) n_train = int(len(all_files) * train_ratio) n_val = int(len(all_files) * val_ratio) for i, name in enumerate(all_files): if i < n_train: folder = "train" elif i < n_train + n_val: folder = "val" else: folder = "test" stem = os.path.splitext(name)[0] lab_file = stem + ".txt" # 图片和标签必须同步复制,这是划分脚本的命门 shutil.copy(os.path.join(src_img, name), os.path.join(out_dir, folder, "images", name)) if os.path.exists(os.path.join(src_lab, lab_file)): shutil.copy(os.path.join(src_lab, lab_file), os.path.join(out_dir, folder, "labels", lab_file)) else: print(f"warning: no label for {name}")

这段脚本做的事是:扫描图片目录,随机打乱顺序,按比例切成三段,然后把图片和同名标签复制到新目录下三个分开的文件夹里。random.seed(42)是关键参数,锁死了随机序列,让每次运行结果完全一致——这对复现实验结果很重要。train_ratio和val_ratio按需求改,比如想留20%做测试,就改成0.75和0.05,或者0.7和0.1。

代码里我用了shutil.copy而不是move,因为原始数据集应该保留一份,划分产物只是它的副本。如果你硬盘吃紧,或者确认原目录不再需要,可以换成shutil.move,但要注意一旦move错乱就没有后悔药了。跑之前建议先打印一遍划分后的文件数统计,确认train、val、test图片数和标签数一致再正式执行。

3.3 生成ImageSets下txt文件的用法

如果你是走VOC训练流程,需要的是ImageSets/Main下的索引文件。这个脚本的输入是Annotations目录下的xml文件名,输出是train.txt和val.txt,里面每行存一个不带扩展名的图片名,VOC官方的训练代码靠这个名字去JPEGImages目录下找图:

import os import random random.seed(2024) voc_root = "/path/to/VOCdevkit/VOC2007" xml_dir = os.path.join(voc_root, "Annotations") out_dir = os.path.join(voc_root, "ImageSets", "Main") train_ratio = 0.8 all_xml = [f for f in os.listdir(xml_dir) if f.endswith(".xml")] random.shuffle(all_xml) n_train = int(len(all_xml) * train_ratio) train_ids = [os.path.splitext(f)[0] + "\n" for f in all_xml[:n_train]] val_ids = [os.path.splitext(f)[0] + "\n" for f in all_xml[n_train:]] with open(os.path.join(out_dir, "train.txt"), "w") as f: f.writelines(train_ids) with open(os.path.join(out_dir, "val.txt"), "w") as f: f.writelines(val_ids)

这里有个很容易混淆的点:ImageSets下的txt和YOLO训练用的txt完全是两码事。ImageSets的txt里存的是图片索引名,不带路径不带扩展名;而新版YOLO训练时用的是数据集目录结构,由data yaml文件里的train路径指向图片文件夹。如果你后面用ultralytics系列训练,这个ImageSets的txt不是必需文件,只有走官方VOC训练管线时才用得上。

4. 从环境搭建到跑通训练:数据yaml怎么写、参数怎么调

4.1 Windows和Linux两套路线的共性流程

材料里的教程文件分成了Windows版和Linux版两套,Linux又拆出了Ubuntu安装和环境搭建两篇。这个组织方式是合理的:Windows用户走Anaconda加PyTorch路线,Linux用户先把Ubuntu和显卡环境垫平再装深度学习栈。拆开看,两套流程的共性部分是一样的:创建虚拟环境、安装PyTorch、安装YOLO依赖、验证GPU可用、编辑数据yaml、启动训练。

我一般建议新手优先走Windows路线,因为Anaconda的图形界面能减少很多命令行恐惧;如果你后续要部署到服务器或工控机,Ubuntu那套才是常态环境,教程里把Ubuntu安装单独写一篇也是考虑到这一步对没接触过Linux的人是个门槛。

4.2 数据yaml的写法与路径规则

不管哪个平台,训练自己数据集的第一个动作都是写data yaml。教程里“根据案例修改训练自己的数据集”这句话,指的就是把案例yaml里的路径和类别信息换成你手头数据的实际情况。我通常这么写:

# xray.yaml —— 路径建议用绝对路径,省得在不同终端里相对路径打架 path: /home/user/dataset/xray # 数据集根目录,YOLO会基于它拼接下面的相对路径 train: images/train val: images/val test: images/test # nc和names必须和你的数据严格一致,下面只是示例类目,按实际改 nc: 5 names: 0: knife 1: phone 2: power_bank 3: lighter 4: laptop

path是根目录,train、val、test写的是相对根目录的图片文件夹路径。新版YOLO的data yaml支持这种写法,你不需要在yaml里写每个txt文件的列表。nc是类别总数,names是对应的类别名列表,顺序必须和标签txt里的class id一一对应——class id 0就对应names[0],这个是训练过程中逐张图读取标签时直接绑定的,写错的话模型学到的类别语义是乱的。

注意:只要改了数据集,第一件事永远是重新核对yaml,路径错、nc错、names顺序错占了新手训练报错的半数以上。

4.3 启动训练:参数从哪来,怎么改

写好后,训练命令本身不复杂:

yolo detect train \ data=xray.yaml \ model=yolov8n.pt \ epochs=100 \ batch=16 \ imgsz=960 \ device=0

model=yolov8n.pt是下载YOLO官方预训练权重,用它做初始化能大幅缩短收敛时间。几个参数里,imgsz=960是我针对X光数据做的调整。X光场景小目标多,640输入分辨率下很多小物体缩到十几个像素甚至更小,特征基本糊掉了。提到960会明显改善小目标召回,代价是显存占用和训练时间增加。如果你显存只有6G左右,先降到640或480跑通再说,不要一上来就撞OOM。

epochs=100是初跑建议;batch用16起步,OOM时先降batch而不是降imgsz;device=0指定第一张显卡。如果训练过程中loss突然跳到nan,常见做法是把学习率降一半,或者把batch加倍再看一轮,这种现象在yolo训练中通常伴随batch norm崩溃,根源多半是学习率过高或某个batch里包含了异常图片。

5. 避坑记录:五个把训练搞崩的细节与排查路径

5.1 空标签把loss拖成NaN

现象:训练能正常启动,前几个epoch loss正常下降,某个epoch开始loss突然跳成nan,训练直接中断。

原因:数据集中存在空标签文件——图片存在但对应的txt里没有任何标注行。模型在这个batch里拿到一张没有目标的图,输出计算出的损失出现除零或无穷值,反向传播之后数值就崩了。X光图像里确实存在一些“什么都没检出来”的样本,比如纯空包或物体极少的图,标注时它没有被写入任何目标。

解决:训练前先扫描一遍标签目录,找出空文件。

find /path/to/your_ds/labels -name "*.txt" -size 0 -exec echo {} \;

把输出的文件名对应到图片,要么删掉,要么用脚本跳过这些样本。这个动作几十秒,但能省掉你排查nan的一晚上。从那以后我每个数据集进来都先跑这条命令。

5.2 坐标格式错位导致mAP为0

现象:训练能跑完,验证和测试时mAP全是0,或者预测框位置明显不对——框不在物体上,大小也离谱。

原因:把VOC或COCO格式的像素绝对坐标直接当成YOLO归一化坐标用了。YOLO的txt里所有值必须在0到1之间,如果你把xml里的xmin=120直接写进txt,模型读到的是120倍于图片尺寸的坐标,预测自然全错。

解决:打开label目录随机看几行txt,数值都在0到1之间就是YOLO格式正常;如果出现大于1的数,说明你混用了格式,需要按第三章讲的公式做转换。这个数据集的三种标签是分开存放的,直接用对应的格式就好,最怕的是你从不同文件夹里各拿了一部分混在一起。

5.3 划分后图片和标签错位

现象:训练时loss能降,但验证loss震荡剧烈,mAP始终上不去,甚至不升反降。

原因:图片和标签没有同步划分。比如先用一个脚本按80/20切了图片,又用另一个脚本按不同随机种子切了标签,结果train目录里的图片和标签根本不是同一个样本集合。模型在训练时看到标注是错配的,学出来的特征自然混乱。

解决:图片和标签必须放在同一个循环、同一个随机种子下处理。训练前统计两个目录的文件数,数目不一致就说明同步出了问题。这个数据集自带的划分脚本名称里就写着“图片标签划分写入新文件夹”,核心思想就是这个同步逻辑。

5.4 把ImageSets的txt塞给新版YOLO

现象:训练报错,提示找不到图片,或提示标签格式非法。

原因:混淆了VOC的ImageSets/Main/train.txt和YOLO训练需要的图片目录。ImageSets里的txt存的是不带扩展名的图片名,比如002351,而新版YOLO的data yaml要求train: images/train这种目录路径。直接拿txt列表硬喂给新版训练管线,它不知道去哪里找图片。

解决:走ultralytics系列就用目录结构加yaml,这是最省事的路线;只有当你确实在跑VOC官方训练流程时,才需要用到split_train_val生成的那份txt。

5.5 小目标多导致mAP虚低

现象:整体mAP还行,但拆到具体类别看,手机、打火机这类小物体的AP比刀具、笔记本电脑低一截,推理时小目标经常漏检。

原因:X光安检图像分辨率高但目标偏小,加上物体堆叠遮挡,小目标特征在经过网络下采样后所剩无几。输入分辨率越高,小目标保留的像素越多,但如果训练时用的是640甚至更低的输入,小目标一开始就注定检不出来。

解决:把imgsz从640提到960或1280,验证集会看到明显变化。代价是显存和推理速度,部署时如果算力紧张,再用TensorRT做模型压缩把速度拉回来。数据增强里保留mosaic但对裁剪尺寸做约束,避免小目标被裁得更碎,也是这个场景常用的手段。

6. 训练完怎么验:一条命令跑出混淆矩阵,看类目打架

训练结束后先别急着看mAP那个数字,先用推理命令在测试集上跑一遍,肉眼确认效果:

yolo predict model=runs/detect/train/weights/best.pt \ source=/path/to/test_images \ conf=0.15 \ save=True save_txt=True save_conf=True

推理时我把conf降到0.15,是为了看模型在小目标上的响应边界。实际部署时阈值会调回0.4以上,但验证阶段你需要在低阈值下暴露问题——看漏检主要漏在哪里、误检集中在哪里,然后回训练配置里针对性调整。

接下来看类目层面的表现,用Python脚本直接读取验证结果的混淆矩阵:

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") metrics = model.val(data="xray.yaml", split="test") # 每个类别的mAP50-95,单独拉出来看谁拖后腿 for i, ap in enumerate(metrics.box.maps): print(i, round(ap, 4)) # 混淆矩阵对角线是正确分类,非对角线下三角是误判关系 print(metrics.confusion_matrix.matrix)

X光场景最常见的是两类“类目打架”:手机和充电宝因为轮廓相似互相误判,刀具和雨伞在灰度图下边界不清。如果混淆矩阵里某两个类目的误判比例明显偏高,先回去检查这两类的标注框有没有贴合边缘,再考虑给数据增强里加旋转——X光图没有上下方向的概念,旋转对模型是合法的变化。

这套流程在YOLO的v5、v8、v11系列上都适用,换模型只需要改model参数,数据集侧的处理结构完全一致。我现在每次拿到新数据集,第一件事不是解压就开训,而是随机抽三张图把原图、txt、xml对一遍,再跑一遍空标签扫描和文件数统计;这一遍看着慢,实际能省掉后面几天的填坑时间,希望帮到你。

本文还有配套的精品资源,点击获取

返回列表