ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于深度学习的X光安检危险品自动识别:YOLO工程实践

2026/10/1 10:33:37 拓冰建站 浏览量
基于深度学习的X光安检危险品自动识别:YOLO工程实践 简介基于深度学习的机场安检危险品自动识别系统提供完整的Python源码包采用Faster R-CNN检测框架并配套可视化界面与图像样本集。资源主要面向计算机科学、人工智能、大数据等相关专业的在校生与教师可直接用于课程设计、毕业设计及期末大作业也可作为入门深度学习和目标检测项目的练手素材。压缩包共179个文件核心包括37个.py与53个.pyc源码模块、63张jpg和6张png图像样本、2个.ui界面文件及xml配置、Cython加速模块等整体大小约9.73MB目录结构完整便于按功能模块检索。项目代码已经过功能验证能够稳定运行包含构建脚本create.bat和运行录屏process2.gif方便快速启动和效果展示。目前已有345人学习使用适合希望在安检场景下实践目标检测算法、完成课设或毕设展示的同学进行二次开发和功能拓展。1. 先把话挑明这套“机场安检危险品自动识别系统”到底解决了什么问题机场安检传送带上一只拉杆箱在任何时刻都包着两层以上东西笔记本电脑、折叠伞、保温杯、充电宝堆叠让X光机前的判图员必须在3到5秒内做出开箱决定。把这道题交给深度学习标题里的“基于深度学习的机场安检危险品自动识别系统”就是这个工程化答案读入X光图像输出危险品的有无、类别和位置。这类系统最常见也最可靠的开源实现是一条Python源码工程主干是YOLO系列目标检测模型加一套数据预处理和训练脚本。它最容易被新手误判的地方是把识别当分类做——只输出一个“有没有刀”的概率结果测试集上准确率挺高一到真实X光图上一个框都画不出来。适合拿它起步的人是做毕设的学生、安防设备小团队和第一次接触检测任务的Python工程师它值不值得你投入时间取决于你能不能接受“模型只是辅助判图不能替代人”这件事。2. X光安检图像与自然图像差在哪先搞懂输入再谈模型选型2.1 为什么安检识别不能做成图像分类X光图像的视觉规律自然图像分类任务里目标的主体轮廓、颜色、纹理基本是完整的。你在ImageNet上训练一个分类器输入一张“刀”的图片输出“刀”的类别这件事在语义上是通的。但X光安检图像有一个致命的差别内容是半透明的、堆叠的。一把刀放在背包里刀身和背带扣、水杯外壳重叠在一起视觉上刀身被“透视”成了几段边缘被干扰线淹没分类器用全局池化提取特征时这些细碎特征会被周围的背景噪声稀释掉。X光图还有第二个规律颜色不是物体的本色而是材质的物理响应。有机物呈橙色无机物呈蓝色金属呈深蓝色甚至接近黑色。一个充电宝的铝壳、一把剪刀的金属刀刃、一块电路板上的锡点在X光下颜色接近靠纯颜色分类必然翻车。第三个规律是透视压缩包内物体前后叠放在二维投影里彼此融为一体位置信息比类别信息更可靠。这就是为什么安检危险品识别必须走目标检测路线。检测模型输出的不是一个全局类别而是“某个位置、某个尺寸的框里有什么”的局部判决。框本身把目标从重叠背景里切出来缓解了堆叠对分类的干扰。所以标题里的这套系统无论源码长什么样核心一定是一个检测头而不是一个分类头。你拿到源码如果发现只有CNN分类结构别改直接换架构。2.2 检测模型选型为什么Anchor Based的YOLO系是默认答案安检危险品识别对检测模型有三条硬要求推理要快到能跟上传送带节奏、小目标要捡得起来、训练数据有限时要能靠迁移学习快速收敛。按这三条筛一遍YOLO系几乎是唯一能把CPU/低端GPU都跑起来的方案。Faster R-CNN的两阶段结构在小目标召回率上有优势但单张X光图动辄1到2秒的推理时间在安检场景里没有部署价值。SSD速度够了但浅层特征图直接出框对X光这种高噪声、强遮挡的图像误检率高得离谱背景里的拉链齿会被框成刀具。真正有讨论空间的是YOLOv5和YOLOv8。YOLOv5是Anchor Based的典型代表有成熟的autoanchor机制、清晰的代码结构、大量现成教程标注数据转成txt格式就能开训这是做课程设计和工程复现最顺的一条路。YOLOv8把Anchor改成了Anchor Free的Decoupled Head收敛稳定、mAP更高但它的标签分配策略和损失函数在X光图像上没有本质优势反而对源码阅读者更不友好。我的习惯是第一次跑通流程用YOLOv5s跑通后再横向对比v8这样你能清楚地看到两种范式在安检数据上的真实差距而不是被源码复杂度带偏。2.3 数据准备把VOC标注转成YOLO txt的脚本与文件组织拿到一个安检识别项目八成给你的数据是VOC格式的XML标注而YOLO系训练要的是每张图对应一个同名txt一行一个目标class_id x_center y_center width height坐标归一化到[0,1]。转换脚本是所有步骤里最不起眼但最容易出错的一环我给你一份我常用的脚本保留了关键注释。import os import xml.etree.ElementTree as ET from pathlib import Path # 类别顺序要和后续 data.yaml 里的 names 完全一致 CLASSES [gun, knife, wrench, pliers, scissors, hammer] def convert_voc_to_yolo(xml_path: str, out_dir: str): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) if img_w 0 or img_h 0: # 有些标注文件 size 字段缺失直接跳过并打印警告 print(fskip {xml_path}: size missing) return lines [] for obj in root.iter(object): cls_name obj.find(name).text.strip() if cls_name not in CLASSES: continue cls_id CLASSES.index(cls_name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 关键YOLO 坐标是中心点 宽高不是左上角 右下角 x_center (x1 x2) / 2.0 / img_w y_center (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 边界框坐标偶尔会超出图像范围做一次截断避免训练时 loss 异常 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if not lines: return xml_name Path(xml_path).stem out_path os.path.join(out_dir, f{xml_name}.txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) # 用法示例转换 train 和 val 两个目录 for split in [train, val]: xml_dir fdatasets/sixray/{split}/annotations out_dir fdatasets/sixray/{split}/labels os.makedirs(out_dir, exist_okTrue) for xml_file in Path(xml_dir).glob(*.xml): convert_voc_to_yolo(str(xml_file), str(out_dir))脚本里有三个点需要单独说明。第一CLASSES列表的顺序就是模型输出头里类别的索引顺序一旦定了就不要在训练中改动否则旧权重加载后张量维度全对不上损失却乱套。第二x_center y_center width height的归一化公式我一再写进注释里因为很多人把XML里的xmin xmax直接除以图像宽就当成中心点结果是所有框全部偏移半个身位训练时mAP永远上不去。第三坐标截断那段看起来多余但在X光图上经常有人把目标故意标出边界一点不截断的话模型会一直在边界上做无效梯度更新。转换完成后文件组织应该是images/train、images/val、labels/train、labels/val四个目录图片和txt名字保持一致这个结构是YOLO系的约定俗成不要改动。3. 网络结构与训练参数锚框、输入分辨率、类别权重必须按安检数据重调3.1 网络结构里和安检场景关系最大的三个部件YOLOv5s的Backbone用的是CSPDarknet53的改进版负责把输入逐步下采样提取语义特征。到这里为止它和通用目标检测没有区别真正影响安检场景的是后面三样东西。第一是Neck里的PANet结构。它做双向特征融合把深层的语义信息回传给浅层特征图让网络既能识别“这是一把刀”又能保留“刀的位置在哪个像素范围”。X光图像目标叠放严重浅层纹理被遮挡剥离靠PANet回传的语义能在一定程度上“补全”被覆盖的目标轮廓。第二是三尺度检测头分别在8倍、16倍、32倍下采样处输出预测框。8倍下采样特征图感受野小适合捡小目标32倍适合大目标。安检场景里一个打火机在640分辨率下只有二三十个像素宽全靠小尺度检测头兜底。第三是anchor预设。YOLOv5自带的是基于COCO数据集聚类出来的锚框尺寸COCO里的目标是猫、狗、汽车长宽比分布和安检物品差异极大——刀是长条形、钳子是短粗形、枪支轮廓不规则直接沿用默认锚框会让匹配阶段大量正样本被当成背景丢掉。3.2 必调参数anchors重算、输入分辨率、类别权重训练前必做的第一件事是重算anchors。YOLOv5提供了自动锚框命令在训练脚本里加一行参数就行python train.py --data data/sixray.yaml --weights yolov5s.pt \ --img 640 --batch 16 --epochs 100 --name sixray_run \ --noautoanchor False--noautoanchor False表示允许训练前自动运行K-Means聚类重新计算当前数据集的最优锚框尺寸。如果你不想每次训练都重算可以单独跑一次python utils/autoanchor.py --data data/sixray.yaml然后把生成的锚框值固化到模型配置文件里。重算之后匹配阶段的IoU阈值不变但每个尺度的候选框分布会更贴合安检物品的长宽比小目标召回率通常能涨2到5个点效果非常直接。第二个必调参数是输入分辨率--img。通用检测项目常用640但X光图像的目标密度比自然图像高得多一个包里十几个叠放目标640分辨率下小目标像素太少。资金允许时我会把训练分辨率提到1280推理分辨率保持1280或降到960。代价是显存占用翻倍、训练速度下降但mAP0.5在安检小目标上的收益经常能到8个点以上。显存不够就只对验证和推理用高分辨率训练用640这样至少保证部署时能捡到更多小目标。第三个必调参数是类别不平衡权重。公开的安检数据集里gun和knife样本往往远多于wrench和pliers模型天然偏向多数类。YOLOv5的超参数文件hyp.yaml里有一个cls_pw代表类别损失的权重系数默认1.0。按样本数比例给稀有类提高权重比如wrench的样本数是gun的一半就把cls_pw调到1.5到2.0。这个参数不用精调但必须调否则稀有类mAP会是0。3.3 训练策略迁移学习、epochs与增强开关安检X光图像和自然图像域差异大很多人因此觉得不能用COCO预训练权重这个观点是错的。COCO模型的Backbone已经学会了边缘、纹理、形状等通用视觉特征X光图像虽然域不同但底层特征仍然通用用--weights yolov5s.pt做初始化能省掉几百个epoch的从头训练。真正需要关掉的是与颜色相关的数据增强。X光伪彩色图里颜色是材质信息的编码不是外观属性。随机调Hue和Saturation会让一把金属刀变成橙色模型学到的“橙色有机物”的映射被破坏训练loss下降但验证mAP一路横盘。在hyp.yaml里把hsv_h、hsv_s、hsv_v全部设成0只保留几何增强Mosaic随机拼图、缩放、翻转、平移。Mosaic对X光图尤其有效它把四张图拼在一起等于强行做了四次空间堆叠模拟了包里多物体重叠的情况这对模型泛化能力提升比任何颜色扰动都大。epochs方面100到150个epoch足够收敛。超过150个epochX光图像数量小模型开始死记训练集的噪声纹理验证mAP反而下降。如果训练到50个epoch时验证mAP还在明显上升说明数据量比预期大可以适当加长但一般别超过200。4. 把源码从zip包变成能出检测框的模型安装、改配置、训练与验证4.1 解压源码包后的目录认识与环境搭建拿到标题里这个python源码zip包第一步不是急着跑训练而是确认目录结构。一个规范的YOLO检测工程顶层一定有train.py、val.py、detect.py、utils/、models/、data/几块。先解压到纯英文路径下比如D:\security_detection避免中文路径在OpenCV读取图片时概率性报错这个坑在Windows上遇到一次就能记住一辈子。然后安装依赖git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt python -c import torch; print(torch.__version__)如果你手里的源码不是YOLOv5工程而是自定义实现只要requirements.txt存在第一步永远是装依赖。装完后用python -c确认PyTorch版本能在CUDA上运行CPU也能跑通全流程只是慢。之后把源码zip里的data/、models/、utils/和你的自定义脚本覆盖进YOLOv5官方目录优先保留官方代码因为官方代码测试充分自定义部分容易翻车。4.2 修改data.yaml与模型配置文件训练前需要写一个data/sixray.yaml它告诉训练脚本数据在哪、有几类、类名是什么。以下是我常用的写法# 训练和验证图片的根目录建议写绝对路径避免相对路径在不同操作系统下解析不一致 path: D:/security_detection/datasets/sixray train: images/train val: images/val # 类别数量必须和前面的CLASSES列表长度一致 nc: 6 names: 0: gun 1: knife 2: wrench 3: pliers 4: scissors 5: hammer注意train和val的值是相对于path的路径不是完整路径。如果你的zip包里有额外的大类如“其他物品”建议直接删除或并入背景因为安检场景里“其他”这个概念太宽泛模型学了反而会把所有不认识的框都标成“其他”。模型配置文件models/yolov5s.yaml里有一行nc: 80改成你自己的类别数6其他结构参数保持默认即可。4.3 三条命令走完训练、验证、推理训练命令是整条流水线的核心参数含义必须清楚再动python train.py \ --data data/sixray.yaml \ --weights yolov5s.pt \ --img 1280 \ --batch 16 \ --epochs 120 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name sixray_baseline--batch 16在1280分辨率下需要约16GB显存显存不够就降到8同时把--img降回640。--hyp选hyp.scratch-low.yaml而不是hyp.scratch.yaml因为后者预设了更强的数据增强X光图像数量小增强过猛反而影响收敛。训练结束后结果在runs/train/sixray_baseline/里weights/best.pt和weights/last.pt分别对应验证集最优和最后一轮权重。验证用下面这条命令它会输出每个类别的AP和整体mAPpython val.py \ --data data/sixray.yaml \ --weights runs/train/sixray_baseline/weights/best.pt \ --img 1280 \ --conf-thres 0.25 \ --iou-thres 0.5--conf-thres是置信度阈值低于它的预测框会被丢掉--iou-thres是评价时判断框是否匹配的IoU阈值。安检场景建议同时看mAP0.5和mAP0.5:0.95两个指标前者衡量框是否大致正确后者衡量定位精度。一张图推理用detect命令python detect.py \ --weights runs/train/sixray_baseline/weights/best.pt \ --source D:/security_detection/test_images \ --conf-thres 0.3 \ --line-thickness 2推理时--conf-thres降到0.2能看到更多弱目标代价是误检变多。先0.3跑一遍数一下漏检数再0.2跑一遍看误检数取折中点作为部署阈值。5. 安检识别项目最容易翻车的四个地方现象、原因、修复顺序5.1 颜色增强一开检测就“翻车”了X光图的颜色是材质不是外观现象训练loss正常下降但验证mAP只有个位数或者检测结果里把深色握把的刀检成“gun”。 原因默认超参数里开了HSV颜色扰动把伪彩色X光图的材质编码打乱了。 解决把hyp.yaml里的hsv_h设为0.0hsv_s设为0.0hsv_v设为0.0只保留几何增强。如果你拿到的源码是YOLOv8同样在default_hyp.yaml里把hsv_h、hsv_s、hsv_v三项归零。这一步是安检X光项目和普通视觉项目最大的分水岭。5.2 稀有类别训练完mAP一直是0或者极低现象gun和knife的AP能到90以上wrench和pliers的AP是0。 原因数据集中这两个类别的样本量可能只有多数类的十分之一训练时大类的梯度完全淹没了稀有小类。 解决先看.txt标签文件里每个类别的框数量统计后用两个手段一是提高cls_pw权重到1.5或2.0二是对稀有类做在线采样增强在mosaic拼图中提高稀有类图片的出现概率。如果这两个手段做完AP还是0直接检查标注文件很多情况下是XML转txt时类别名字对不上被脚本静默跳过了。5.3 小目标刀刃漏检输入分辨率不够不是模型不够强现象大件危险品全部检出小刀片、打火机、小剪子一个都检不到。 原因640分辨率下一个40像素宽的小目标经过32倍下采样后只剩1个网格特征检测头根本没有足够的信息量。 解决把训练和推理分辨率提高到1280重算一次anchors观察mAP0.5:0.95的变化。如果显存不够在hyp.yaml里把mosaic从1.0降到0.5释放一部分显存。真实安检项目里小目标漏检往往不是网络结构的问题是输入分辨率和硬件预算的妥协问题。5.4 loss一直在降但mAP不动甚至越训越差现象训练集loss一路下降验证mAP停在50上下继续训反而掉点。 原因模型开始过拟合背景纹理尤其是Mosaic拼图边界产生的不自然边缘被模型当成特征。 解决先确认训练集和验证集的图片来源如果两者来自不同设备或不同批次X光图像的质量差异会让模型无法泛化然后调低Mosaic概率到0.3关掉MixUp这两个增强对X光图很容易引入拼接伪影。最后检查标签里有没有大量重叠的框X光图像里一把刀和一个水杯在投影上重叠过半时两个框的IoU高得离谱模型学起来自相矛盾这种情况要人工合并标注而不是调参。6. 把模型推到安检设备上ONNX导出、两级筛查与结束语6.1 从PyTorch到ONNX部署导出的最小流程训练完不能直接拿best.pt去部署PyTorch的推理环境太重。导出成ONNX是安检边缘设备部署最通用的中间格式导出命令如下python export.py \ --weights runs/train/sixray_baseline/weights/best.pt \ --include onnx \ --opset 12 \ --img 1280导出后用onnxruntime写一个简单的推理接口输入是1280x1280的X光图输出是检测框。部署设备上如果用的是Intel CPU用OpenVINO跑ONNX比纯onnxruntime再快20%到40%如果是NVIDIA Jetson直接转成TensorRT FP16小目标推理速度可以从60ms降到25ms。导出后必须做的验证是用同一张图分别跑PyTorch和ONNX推理对比输出框坐标有没有漂移超过2个像素就要检查预处理是否一致。这个步骤是部署前最容易被跳过的我吃过一次亏X光图像经过OpenCV读入时的色彩通道顺序和PyTorch训练时不一致导出模型在真机上误检率直接失控。6.2 两级筛查架构把漏报率压到可接受范围安检场景里危险品漏检比误检严重得多。一个实用做法是二级筛查架构第一级用高召回的低置信度模型跑全图把所有可能是危险品的候选框都捞出来第二级对候选框区域做裁切放大用另一个轻量分类器判断“真有危险品”还是“误检”。第一级模型用mAP0.5做主要优化目标第二级分类器只关心裁剪出来的小图任务简单准确率能到99%以上。这样整体漏报率可以压到1%以下误检率也比单模型低一半。部署时我把这套流程写成一个流水线X光图进来先跑一级检测捞出候选框再跑二级分类最后只把置信度超过0.8的框发给操作员复核。这个架构在算力有限的设备上特别划算因为第一级模型可以故意用低阈值多检一些第二级把错杀拉回来。6.3 一个收束先跑通再谈精度我做安检类项目有一个习惯第一次跑数据集只跑50个epoch结束以后先不看mAP直接看val.py输出的一批检测结果图用肉眼确认“模型理解了X光图的堆叠结构”这件事再回头调参。mAP是一个平均数它掩盖了模型在特定类型危险品上的失败方式而安检场景恰恰要求你知道“它在哪个环节会漏”。先跑通、再调精度、最后压部署这个顺序可以让整个流程少走弯路。希望帮到你。本文还有配套的精品资源点击获取