简介:X光安检打火机识别数据集聚焦机场安检场景,面向计算机视觉算法工程师与安检设备研发者,主要解决危险品中打火机的自动检测问题。数据集共2119个文件,包含真实场景jpg图像706张,以及配套的xml与txt标注文件各706个,分别对应YOLO和VOC格式;所有图片均由LabelImg人工标注,类别统一为lighter,并分目录存放,压缩包整体约102.38MB,可导入常见检测框架直接训练。已有979人学习浏览,获取后可省去手动标注环节,既能直接开展YOLO系列模型训练,也能通过对比xml与txt文件理解两种标注格式的转换关系,为后续自建数据集提供参考模板,特别适合入门者快速熟悉目标检测数据的组织与使用方式。真实安检图像中的遮挡与角度差异,有助于验证打火机识别模型在复杂场景下的泛化性能。
1. YOLO机场X光安检打火机识别数据集:为什么这不是一个普通的物体检测项目
做安检智能判图的人应该都有体会:拿 COCO 上跑得飞起的 YOLO 模型,直接丢到机场 X 光机上识别打火机,第一轮测试几乎必翻车。原因很简单——X 光成像和可见光成像完全是两套视觉逻辑,打火机在透视图像里可能被手机、充电宝、金属水杯叠在一起,轮廓被切得七零八落。专门整理一份针对打火机的 X 光安检识别数据集,再把 YOLO 模型按这个数据域的规律去调,才是把项目落地的正道。这篇笔记写给两类人:一是要做安检智能判图算法的工程师,二是准备采购或自建训练数据的集成商。数据集的采集规范、格式转换脚本、训练参数和避坑点,我按实际做过的方案一次讲透。
2. X光安检成像的特殊性:打火机为什么在可见光数据集上训练不起来
2.1 X光成像的核心逻辑:灰度、穿透与材料密度
普通相机拍到的打火机,靠的是表面反射光,塑料外壳的红色、金属砂轮的银色都是关键特征。X 光安检机完全不同——它记录的是射线穿过物体后的衰减值,材质密度越高、原子序数越大,图像上就越亮(或者越暗,取决于安检机的色彩映射策略)。常见的机场安检机把图像按有机物、无机物、金属做伪彩色映射:有机物偏橙色,无机物偏蓝色,金属显示为深蓝色到黑色。但很多出口的数据集为了通用性,直接保存为灰度 PNG 或原始 16 位穿透率数据。
打火机在 X 光下是典型的复合材料:金属外壳或金属中架是高密度区域,图像上非常亮;内部的丁烷液体是低密度有机物,显示为相对透明的区域;砂轮里的压电陶瓷和火石是小而硬的高亮颗粒。这三部分组合在一起,和可见光图像里的“打火机”长相完全不同。你的 YOLO 模型如果是在 COCO 上预训练的,它学到的纹理特征是表面颜色和光影,换到 X 光灰度图后特征分布彻底漂移,这就是为什么直接搬预训练权重效果差。
2.2 打火机在X光图像里的视觉特征与标注难点
我在标注打火机 X 光图时总结出三个稳定特征:一是整体轮廓多为矩形或圆角矩形,宽高比接近 2:1 到 3:1;二是内部有气体腔形成的低密度暗区,边缘过渡柔和;三是金属打火机常看到高亮的壳体边线。如果安检机图像是伪彩色的,打火机外壳的蓝色金属部分和内部橙色气体部分会有明显分界。
但真正的难点不在这里,而在遮挡和重叠。行李箱里打火机很少单独躺着,它可能塞在背包侧袋、压在保温杯底下、和钥匙硬币混在一起。X 光透视把这些东西在二维平面上叠加,目标边界被其他物品的高亮区域切断。所以数据集的标注规范里,我一般要求标注工遵循“可辨识即标注”原则——只要人眼能看出那里有打火机的关键部位(金属壳、砂轮、气体腔至少两个特征),就标一个完整外接框,哪怕部分被遮挡。因为训练 YOLO 时,遮挡目标的学习信号本来就弱,再不标全,模型会倾向于把局部特征当成完整目标,导致部署时大量漏检。
2.3 标注格式选择:VOC 还是 COCO
X 光安检数据集的标注格式没有统一标准,但落地时关键看你的训练管线。YOLO 系列(v5/v8/v11)原生支持两种格式:一是每张图对应一个 txt,每行是 class x_center y_center width height(归一化坐标);二是 COCO JSON。我实际更推荐标注时先用 VOC XML 格式(LabelImg 默认输出,便于人工校对),再统一脚本转成 YOLO txt。原因很简单:XML 里能看到 xmin/ymin/xmax/ymax 的整数像素坐标,人工检查边界框是否偏离时直观;COCO JSON 的嵌套结构在多人协作时经常出现 id 错位,排查起来费时间。
3. 构建打火机识别数据集:从原始图像到 YOLO 训练集的完整流程
3.1 图像采集:多设备、多角度、多遮挡等级
数据集的源头是安检机的原始输出。我一般会向机场或安检设备厂商要三类数据:一是不同品牌安检机(海康、同方威视、史密斯 Heimann 等)的导出图像,因为各家的能量分辨率和色彩映射差异很大;二是不同通道的图像,比如 1 号通道和 5 号通道即便型号相同,射线源老化程度也不同,灰度分布会有偏移;三是同一物品在不同摆放角度下的扫描图。
这里有个关键参数——图像分辨率。安检机导出图的尺寸从 800x600 到 2000x1500 都有,行李图像通常整包呈现在画面中央。如果后续打算用 YOLOv8 在 640x640 下训练,大图上的打火机可能只占 30x30 像素,直接整图缩放会丢失大量细节。常见的处理是先在原图上做目标裁剪或切片,把打火机区域切出来再缩放;或者干脆训练时用 imgsz=1024 甚至 1280。我建议数据采集时就记录每个目标的原始像素尺寸,后续统计目标大小分布,决定训练分辨率。
采集时还要注意正负样本比例。安检场景里正常行李占绝大多数,打火机只是在部分包里有。一份实用的数据集,除了包含打火机的图像,还要有 20% 左右的纯负样本(完全没有违禁品的行李图)。负样本虽然不提供正样本梯度,但能让模型学会“什么都不输出”到“输出背景类”的判断,显著降低误报率。负样本图不要和正样本图来自同一批行李扫描——同一只包取出打火机再扫一遍,这两张图背景几乎一样,放进数据集会造成评估虚高。
3.2 标注格式统一:VOC XML 转 YOLO txt 的脚本
拿到标注好的 XML 后,写一个转换脚本把 VOC 格式转成 YOLO txt。这个脚本我每次都会复用,先把路径、类别名和坐标处理写清楚:
import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, class_names, out_txt_path): """ 将 LabelImg 输出的 VOC XML 转为 YOLO txt 格式。 class_names: ['lighter'] 这样的类别列表,索引即 YOLO 类别 ID。 """ tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_names: # 类别不在预设列表里就跳过,避免脏数据混入 continue cls_id = class_names.index(name) xmlbox = obj.find('bndbox') xmin = float(xmlbox.find('xmin').text) ymin = float(xmlbox.find('ymin').text) xmax = float(xmlbox.find('xmax').text) ymax = float(xmlbox.find('ymax').text) # YOLO 格式要求归一化的中心点坐标和宽高 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h # 对越界框做裁剪,防止标注超出图像边界导致训练报错 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) box_w = min(max(box_w, 0.0), 1.0) box_h = min(max(box_h, 0.0), 1.0) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") with open(out_txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) # 批量转换入口 if __name__ == '__main__': xml_dir = Path('./annotations_xml') txt_dir = Path('./labels') txt_dir.mkdir(exist_ok=True) CLASSES = ['lighter'] # 类别顺序固定,训练配置里的 nc 和 names 要和这里一致 for xml_file in xml_dir.glob('*.xml'): out_txt = txt_dir / (xml_file.stem + '.txt') voc_to_yolo(str(xml_file), CLASSES, str(out_txt))这段脚本里最值得注意的参数是框坐标的裁剪。实际标注中时常出现把框拖到图像边缘外的情况,如果不 clip 到 [0,1] 区间,YOLO 训练时会在 loss 计算里拿到负数宽高,轻则警告重则直接崩掉。另外类别名的顺序一定要和后续训练的 data.yaml 完全一致——class_names 列表的下标就是 YOLO 的类别 ID,排错一个位置,整个模型的混淆矩阵就会乱掉。
3.3 数据集划分与数据增强:别让数据泄露毁掉验证集
格式转换完成后,按 7:2:1 划分 train/val/test。划分时有个容易忽视的原则:同一件行李的多次扫描图(比如正位、侧位、倒置)必须放进同一个集合。如果同一件行李的图像既在训练集又在验证集,模型相当于见过“答案”,验证 mAP 会虚高 10 个百分点以上,部署后立刻现原形。最稳妥的方式是按“行李 ID”划分——如果原始文件命名里有设备编号和包裹编号,直接用编号前缀分组;没有的话,至少确保训练集和验证集的文件名没有重复的图像内容。
数据增强方面,YOLOv8 自带的 Mosaic 增强对 X 光图有效,但有几个参数建议按安检场景调整。Mosaic 会把四张图拼在一起,相当于人为制造了“多个物体叠加”的场景,恰好模拟了行李箱里的遮挡关系,所以我一般让 mosaic 的概率保持在 1.0(默认即开启)。旋转增强的角度要调小,默认的 rotate=0.0 就行,因为打火机在 X 光下的特征方向性较强,大角度旋转会改变金属件和气体腔的相对位置关系,可能学出不合理的特征。水平翻转可以开,安检机图像不存在左右语义差异。另外 X 光图的灰度直方图和可见光差异很大,数据增强里不要用色彩抖动(hsv_h/hsv_s/hsv_v 都设为 0),否则会把原始材料的密度信息破坏掉。
4. 用 YOLOv8 训练打火机识别模型:配置、命令与调参要点
4.1 数据配置文件:data.yaml 的写法与检查点
YOLO 系列训练的第一步是准备 data.yaml,路径必须是绝对路径或用相对 YOLO 项目根目录的写法。我踩过最深的一个坑是:在服务器上用相对路径启动训练没问题,换一台机器路径变了就直接报 Dataset not found。所以现在统一用绝对路径。data.yaml 内容如下:
# data.yaml path: /data/xray_lighter # 数据集根目录 train: images/train # 相对 path 的训练图目录 val: images/val # 验证图目录 test: images/test # 测试图目录,训练时不使用 nc: 1 # 类别数,只需要打火机一类 names: 0: lighter # 类别名和 txt 标注里的类 ID 一一对应训练命令一般长这样:
yolo detect train \ data=/data/xray_lighter/data.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=1024 \ batch=16 \ device=0 \ patience=30 \ project=/output/xray_lighter \ name=run1这里的 imgsz=1024 是关键参数。刚才提到,安检原图上打火机经常只有几十像素,用默认的 640 会丢失小目标特征。我对比过同一份数据集在 imgsz=640 和 1024 下的结果,小目标召回率差距在 8~12 个点。代价是训练显存和耗时翻倍,所以如果你的显卡是 V100 或 A100,直接用 1024;如果是 2080Ti 这类 11G 显存的卡,batch 降到 8 或者用 imgsz=960 折中。
另外很多人纠结要不要用预训练模型,我的建议是:用 yolov8s.pt 或 yolov8m.pt 的 COCO 预训练权重做初始化,但不要对它抱有太大期待。X 光域和自然图像域差别太大,预训练权重提供的更多是低层边缘、纹理特征的初始化,能加速收敛但不会改变最终的精度上限。如果数据集量足够大(比如打火机目标超过 1 万个框),直接从空白权重训练也能收到接近的结果,只是 epoch 需要的更多。
4.2 训练过程中的指标读取:从 loss 曲线到混淆矩阵
训练时我习惯盯三个东西:box_loss、cls_loss 和验证集的 mAP50。YOLOv8 的训练日志里会实时打印,也可以用 wandb 记录。box_loss 是边界框回归损失,它下降得慢不要慌——因为 X 光图像里遮挡导致的边界模糊本来就多,模型需要比较多的 epoch 才能收敛到稳定的框。cls_loss 更关键,如果 cls_loss 在训练后期出现反弹,往往是数据增强过强(比如 Mosaic 的随机缩放把打火机缩得太小,超过了学习能力)或者学习率没配合好。
训练结束后一定要做验证集评估,不要只盯着训练集 loss。运行:
yolo detect val \ data=/data/xray_lighter/data.yaml \ model=/output/xray_lighter/run1/weights/best.pt \ imgsz=1024 \ conf=0.25 \ iou=0.6conf 是置信度阈值,iou 是 NMS 的 IoU 阈值。对安检场景,我建议 conf 单独看两条结果线:一条用默认 0.25 看理论召回,一条用 0.5 以上看实际可用状态。因为安检员不可能接受机器频繁报警——每 100 个包里报警 80 次,他们会直接把系统关掉。你需要在高置信度下保持召回,这个指标在验证阶段就要量化出来。
4.3 损失函数与类别不均衡:打火机类别要不要单独调权重
这里要回应一下“yolo损失函数”这个搜索词。YOLOv8 的损失函数分成三部分:边界框回归损失(CIoU 或 DFL)、分类损失(BCE)、以及 DFL 的分布损失。分类损失默认对每个类别平等对待,但如果你的数据集里打火机目标只占所有框的很小比例(安检图像里单张图可能只有 1 个打火机,背景区域巨大),模型会偏向“什么都不检”。这时候可以在 data.yaml 里设置 cls 损失的权重,或者在训练命令里加cls=1.5这类参数。
不过我实测下来,对单类数据集(只有 lighter 一类),cls 权重的作用有限——因为模型不需要区分打火机和别的类别,只需要区分目标和背景。真正有效的是调节前景-背景样本比例:通过 mosaic、copy_paste 增强增加每张图的目标数量。YOLOv8 默认开启 mosaic,但 copy_paste 在修改版里没有默认开启,可以手动在增强参数里打开。
5. 训练与部署避坑:X光安检场景下的 5 个常见问题
5.1 现象一:训练到一半 loss 变成 NaN,权重文件全废
这个问题我自己在刚转 X 光项目时就遇到过两次,后来发现和“yolo训练中bn崩溃”的描述完全吻合。现象是训练到 40 到 60 轮时,box_loss 和 cls_loss 突然变成 nan,训练进程不退出但指标全坏。
原因是 BatchNorm 统计量崩了。安检图分辨率大,为了塞进显存,batch size 往往被压到 4 或 8。小 batch 下的 BN 层均值方差估计不稳定,一旦某次前向传播出现极端激活值,统计量被污染,loss 立刻发散。
解决办法分三步:第一步,batch size 提到 16 以上,如果显存不够就降低 imgsz 到 960 或 896,而不是牺牲 batch。第二步,如果必须用小 batch,可以在训练命令里关闭部分 BN 的更新,或者换用batch=-1让 YOLO 自动用最大 batch。第三步,检查数据里有没有异常图像——比如全黑的全白的、或者 16 位深度没转成 8 位的图,这些异常像素值会直接让 BN 崩掉。
5.2 现象二:验证集混淆矩阵“总和”不等于样本数
有人在验证后打印混淆矩阵,发现把每一格加起来,和 GT 框总数对不上。搜“yolo混淆矩阵总合不唯一”看到的现象基本一样:不是矩阵算错了,而是 YOLO 的混淆矩阵包含“漏检”这一格,同时 NMS 和置信度过滤会吞掉一部分低分预测框。另外如果背景类被设置为忽略(默认 bg 不参与 loss),那背景预测和背景真实各自独立一格,总和自然不等于前景目标数。
这类问题通常说明你的标注或评估配置有歧义。我的处理习惯是:跑验证时固定conf=0.001, iou=0.5看原始召回能力,得到一个“理论上限”;再正常置信度下跑一次,看“实际可用值”。两个数之间的差就是置信度阈值带来的损失,这个差值如果过大(超过 20%),说明模型输出置信度分布不合理,需要在训练时加一些难例挖掘或重采样。
5.3 现象三:打火机被金属水杯完全“吞掉”,漏检严重
X 光透视下,不锈钢水杯密度极高,图像上是一大片深色高亮区域,背后如果藏着一个打火机,灰度值范围和水杯重叠,模型几乎不可能从单张 2D 图像里分辨出来。这不是模型能力问题,是物理成像的极限。
解决思路有两个层面。算法上,把输入从单能灰度图换成双能伪彩色图——很多安检机输出有机物/无机物分离的彩色图,金属、有机物、无机物各有不同颜色通道,打火机的气体腔(有机物)和水杯(金属)在颜色上会被分开,模型学到的特征区分度大幅提升。如果只有灰度图,则要裁剪出可疑区域做局部直方图均衡化增强,人为拉开密度差。我试过在预处理时对每个切片做 CLAHE,对中等遮挡场景的召回提升约 5 个点。
5.4 现象四:高分辨率原图直接缩放训练,小目标全部丢失
这个坑几乎所有人都会踩一遍。安检原图 1500x1200,设 imgsz=640 后,原图被压缩到 1/2 以下,一个原本 40x40 像素的打火机在输入图上只剩 20x20,特征细节基本没了。YOLOv8 在 640 下对小目标的检测本身就弱,暴力缩放等于雪上加霜。
我现在的做法是先用原图统计目标像素尺寸分布。如果 80% 的目标长边在 32 像素以下,imgsz 直接上 1280(或者更高,只要显存够且标注质量可信)。如果只能用 640,那就切片推理——推理阶段把大图裁成 640 的滑窗块,逐块检测后再映射回原图坐标。切片重叠率设 20%,可以有效避免目标正好卡在切片边线上被切断。
5.5 现象五:部署到安检机现场后误报率奇高
训练时 mAP50 有 0.95,一到现场每分钟报 8 次警。这个问题我排查下来通常出在两个地方:现场安检机的图像色彩映射和训练数据不一致,导致整体灰度分布偏移;或者现场图像分辨率/位深和数据集不一致,模型看到的是域外数据。
购买或收集数据时,我建议明确记录每一张训练图像的来源设备型号、图像位深(8bit/16bit)、颜色空间(灰度/伪彩色)。训练时做灰度扰动增强,模拟同一台设备在不同老化阶段的表现。部署前留一份“现场样本袋”——拿一小部分现场采集图做 blind test,mAP 不掉点超过 5 个点就不能上线。
6. 验证模型是否真的可用:测试集设计、置信度阈值和部署前的最后一道检查
训练完模型,不要急着接进安检机。先做一个贴近实战的盲测:找同事拿不同品牌、不同新旧程度的打火机(塑料壳、金属壳、防风打火机)分别塞进双肩包、行李箱、手提袋,在安检机上过一遍,采集 200 到 300 张现场图。这批图不参与任何训练和验证,只在最后跑推理,计算两个指标:召回率(有打火机的包里检出来多少)和误报率(没打火机的包里报警多少次)。这两个指标才是安检员真正关心的。
置信度阈值别拍脑袋定,用验证集画一条 F1-置信度曲线。YOLO 的 val 输出会告诉你不同 conf 下的 F1 变化,一般选 F1 最高的点作为基准值,再往高调 0.05 左右留出安全冗余。如果曲线显示最高 F1 也只有 0.8,说明模型本身的判别力不够,这时去调阈值没有意义,得回到数据层面补充难例。
最后一件事是检查模型的输入输出约定。如果是接机场原有安检机,检测结果要触发告警和图像标记,务必确认模型输出的坐标是在原图分辨率下的像素坐标,而不是训练时的 1024x1024 坐标系。我习惯在部署代码里加一个坐标映射函数,把模型输出框乘以原图与输入图的缩放比。这个小步骤能避免“框画在物品旁边”的尴尬情况。
我现在的习惯是每个安检数据集项目都保留三组数据:用于训练的常规集、用于选阈值的验证集、用于最终验收的现场盲测集。三组互相隔离,任何一组被污染都能从指标异常上看出来。这个习惯帮我避开了很多“训练时好看、现场打脸”的局面。希望帮到你。
本文还有配套的精品资源,点击获取