YOLOv5反光衣安全帽检测实战:从数据标注到DeepStream部署全解析
2026/9/24 22:28:09 网站建设 项目流程

简介:面向计算机、数学、电子信息等专业毕业设计及课程设计场景的YOLOv5反光衣与安全帽检测项目,包含完整源码、训练好的权重及配套数据集,涵盖数据预处理、模型训练、推理检测等关键环节,可用于快速复现安全帽佩戴与反光衣穿着识别流程。项目经导师指导并获高分评审,适合正在准备毕设的学生或需要实战练习的学习者参考借鉴,也可作为期末大作业。压缩包内共1131个文件,以Python脚本(475个py)、编译缓存、yaml配置文件与txt说明文档为主,同时包含大量png/jpg图像样本、ipynb示例、pth权重文件以及Dockerfile、使用手册和演示视频等辅助内容,整体大小约48.12MB,目录结构清晰,便于按模块检索。目前已有154人学习使用。资源自带可运行的检测代码、预训练模型权重和标注数据集,能够直接运行或进一步调优,满足毕业设计、课程设计中对目标检测项目的完整性与可展示性要求,同时省去自行训练模型的成本,适合作为高分参考项目进行二次开发。

1. YOLOv5 反光衣安全帽检测:不是两个目标的问题,是一整套落地链路的问题

把 YOLOv5 跑通容易,把 YOLOv5反光衣安全帽检测跑进工地实景是另一回事。我见过太多项目卡在同一个地方:demo 视频上检测效果不错,一到自己拍的素材上,漏检、误报全冒出来了。反光衣和安全帽看着只有两个类别,实际要同时应付小目标、反光材质、密集遮挡和类别不平衡,任何一个环节偷懒,mAP 都会用数字诚实地告诉你。这份资源把训练好的权重、标注过的数据集、完整源码和 DeepStream 部署文件打包在一起,评审分 98 分,适合正在做毕业设计、课程设计,或者想直接拿一套可用权重做二次开发的从业者。下载下来不是看代码,而是看一条已经跑通的链路。

2. 数据集与标注:决定训练上限的是每一行 txt

2.1 数据从哪来:公开数据集打底、自建样本补短板

反光衣的公开数据远没有安全帽多,这是做这个课题第一个要认清的现实。常见做法是拿开源的安全帽数据集打底,再自己补拍反光衣视频抽帧。抽帧不是按固定间隔无脑截取,我一般会在画面里有人员走动、光线变化明显的片段里多抽几帧,静止画面抽一帧就够,不然训练集里全是重复背景,模型学到的全是背景记忆。

标注工具选 labelImg 就够用,类别只有两个:helmet 和 vest(反光衣),输出格式可以选 PascalVOC XML,后面统一转换。这里有个容易被忽略的点:安全带、手套、护目镜这类 PPE 物品,就算画面上出现了也不要顺手标进去。类别越少,类别之间越不容易混淆,这对反光衣这种颜色接近环境色的目标尤其关键。标完一轮之后务必随机抽 10% 的图检查一遍,重点看小目标有没有漏框、紧挨着的人有没有框位重叠,这两个问题会在训练时直接拉低小类别的召回率。

公开数据集里也有一类现成的反光衣样本,比如网上流传的腾讯安全帽数据集,里面部分场景带有安保反光背心。拿这种数据做预训练打底是可以的,但要注意它的拍摄角度多来自监控俯视,和工地平视画面的分布差异很大。直接用别人数据训练出来的权重去跑自己的视频,十有八九在视角一变就翻车。资源里自带的这套数据集是按工地入口闸机视角组织的,抽帧密度和标注风格比较统一,这也是它能拿到 98 分评审分的基础。

2.2 把 XML 标注转成 YOLO txt:一个转换脚本和一排易错点

YOLOv5 训练时读的标签不是 XML,而是与图片同名的 txt,每行格式是class_id x_center y_center width height,四个坐标值都除以图片宽高做了归一化。下面这段脚本是把 labelImg 导出的 XML 批量转成 YOLO 格式的标准做法:

import os import xml.etree.ElementTree as ET from pathlib import Path classes = ["helmet", "vest"] # 必须与后续 dataset.yaml 中 names 的顺序一致 def convert_xml(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) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in classes: continue # 出现未定义类别直接跳过,避免污染标签 cls_id = classes.index(name) x_min = float(obj.find("bndbox/xmin").text) y_min = float(obj.find("bndbox/ymin").text) x_max = float(obj.find("bndbox/xmax").text) y_max = float(obj.find("bndbox/ymax").text) # 归一化:中心点坐标 + 宽高,全部除以图片尺寸 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_path = Path(out_dir) / (Path(xml_path).stem + ".txt") out_path.write_text("\n".join(lines), encoding="utf-8") # 批量转换 # xml_dir = "data/xmls/" # for f in os.listdir(xml_dir): # if f.endswith(".xml"): # convert_xml(os.path.join(xml_dir, f), "data/labels/")

这段脚本的逻辑只有三件事:读 XML 里的坐标、转成归一化的中心点宽高格式、写到同名 txt。注意classes列表的顺序一旦定了就不要改,它在后续 dataset.yaml 里必须保持一致,否则模型训练时会把 helmet 和 vest 的标签彻底调换,训练出来两个类别的预测结果完全错位。

这里有一个肉眼很难发现的坑:有些标注框正好贴着图片边缘,除以宽高后数值接近 1.0,如果标注时不小心出了边界,归一化后的值会大于 1,训练时模型会在这个框上产生梯度异常。我在脚本里会额外加一行x_center = min(max(x_center, 0.0), 1.0)做钳位,建议把这个防御加上,代价几乎为零,但能省掉排查 loss 变成 nan 的时间。另外 txt 文件里不要留空行,有些版本的 dataloader 读到空行会直接把整行丢弃,导致图片和标签数量对不上,报错会非常隐晦。

2.3 训练集/验证集划分与一次到位的数据校验

数据划分是另一个看起来简单、实际很容易出问题的环节。最简单也最稳的划分方式就是按图片名后缀做 hash 取模,或者直接 random.shuffle 后按 8:1:1 切片。不要按文件夹硬切,工地场景里同一个监控点的连续帧必须进同一个集合,否则训练集和验证集高度相似,验证指标虚高,换到真实场景立刻现原形。YOLOv5 支持在imageslabels目录下直接分 train/val 子目录,对应关系如下:

目录内容
dataset/images/train训练图片,jpg/png 均可
dataset/labels/train与训练图片同名的 txt
dataset/images/val验证图片
dataset/labels/val与验证图片同名的 txt

每次新数据集跑训练之前,我都强制先跑一遍校验脚本,检查图片和标签是否一一对应、标签内容是否在合法范围内。这个习惯帮我挡掉了至少三次「训练到一半 loss 飞」的尴尬场景。校验的核心逻辑就两句话:遍历 images 下所有图片,看同名的 txt 是否存在;读每个 txt 的每一行,确认类别 ID 在预设范围内,坐标值都在 0 到 1 之间。写成一个 30 行的脚本放在项目根目录下,每次换数据跑一遍。从标注、转换到划分,每一步都做检查,后面调优时才能确定问题出在模型而不是数据上。

3. 训练与权重:超参数怎么设、检测脚本怎么跑

3.1 环境配置:先还版本债,再谈训练

拿到这份资源的第一步不是急着跑 train.py,而是先把环境对齐。YOLOv5 的训练链路对 torch 版本和 CUDA 版本非常敏感,常见的翻车现场是:pip install 的时候装上了最新版 torch,结果和本机显卡驱动支持的 CUDA 版本不兼容,训练时直接报 CUDA error。先看驱动的显存和版本,用nvidia-smi查驱动支持的最高 CUDA 版本,再决定装哪个 torch。

conda create -n yolov5 python=3.8 -y conda activate yolov5 pip install -r requirements.txt

requirements.txt 里通常已经锁定了 torch、torchvision、opencv 等核心依赖的版本范围。如果你在资源里没找到 requirements.txt,就自己建环境后再按需安装,但 conda create 这一步不要省,直接用 base 环境装容易把系统 Python 环境搞乱。python 3.8 是我在这个项目上常用的版本,3.10 以上个别依赖的编译会有麻烦。环境装完之后先跑一个python -c "import torch; print(torch.__version__, torch.cuda.is_available())",确认 GPU 可用再进入下一步。

3.2 dataset.yaml 与 train.py:每个参数在决定每张图怎么学

数据集配置是训练前的最后一道关卡。在 YOLOv5 6.0 之后的版本里,dataset.yaml 的格式长这样:

path: D:/graduation/dataset # 数据集根目录,可以用相对路径 train: images/train # 相对 path 的训练图片目录 val: images/val # 相对 path 的验证图片目录 nc: 2 # 类别数量 names: ["helmet", "vest"] # 类别名称,顺序必须与标注一致

path这个字段是 YOLOv5 6.0 新增的,训练时如果还按老写法把 train 填成绝对路径,某些版本会拼接出错误路径直接报找不到图片。names的顺序是这份 yaml 里最重要的信息,和标注时 classes 列表的顺序完全一致,一旦错位整个训练结果都是废的。这里还有一个容易踩的点:val 和 train 的目录不要指向同一个文件夹,否则训练曲线的验证段全是记忆数据,完全没有参考意义。

训练命令本身不算复杂,但参数直接影响训练效果:

python train.py \ --weights yolov5s.pt \ --data dataset.yaml \ --epochs 100 \ --batch-size 8 \ --img 640 \ --device 0 \ --name helmet_vest_run1

--weights yolov5s.pt用的是 COCO 预训练权重做迁移学习,收敛速度和精度都远好于从零训练。--batch-size是按 8 张图设置的,如果你的显卡显存少于 8G,改成 4 或 2 更稳妥,batch 太小梯度不稳,太大显存爆掉,这个参数需要按实际硬件调。--img 640是训练输入分辨率,640 是速度和精度的平衡点。--epochs 100对这个规模的数据集足够用了,我见过很多人硬跑到 300 epoch,结果验证 loss 早就开始反弹,浪费了时间还过拟合。训练日志和权重会输出到runs/train/helmet_vest_run1/目录,best.pt 是验证集上表现最好的权重,last.pt 是最后一轮的权重。

超参数的作用容易被新手忽略,但确实值得花几分钟理解。YOLOv5 的超参数在data/hyps/hyp.scratch-low.yaml里,几个关键的字段如下表:

超参数常见默认值作用
lr00.01初始学习率,太大 loss 震荡,太小收敛慢
momentum0.937带动量的梯度更新,加速收敛
weight_decay0.0005L2 正则化强度,抑制过拟合
fl_gamma0.0focal loss 参数,类别不平衡时可以调成 1.5 左右

我对超参数的态度是:一次只动一个,不要同时改 lr 又改 fl_gamma,否则模型崩了都不知道是哪一个参数背锅。反光衣样本少、安全帽样本多的情况下,把 fl_gamma 从 0.0 调到 1.5 往往能明显提升反光衣的召回率,但代价是整体 mAP 可能微降,这个取舍要看项目的核心诉求。

3.3 用训练好的权重跑推理:detect.py 的参数细节

训练完成之后,真正频繁使用的是推理脚本:

python detect.py \ --weights runs/train/helmet_vest_run1/weights/best.pt \ --source test_video.mp4 \ --conf-thres 0.35 \ --iou-thres 0.45 \ --save-txt \ --save-conf

--conf-thres是置信度阈值,低于这个值的检测框会被直接丢弃,默认 0.25。反光衣安全帽这个场景我建议从 0.35 起步调试,低阈值会把远处虚影都框进来,高阈值又会漏掉远处的小目标。--iou-thres是 NMS 的 IoU 阈值,默认 0.45,在人员密集、框相互重叠的画面里,这个值调低一些可以让重叠目标的框被更好地过滤掉,避免一个目标出现三四个框。--save-txt会输出每个目标的位置信息到 runs/detect 目录,做考勤统计或者二次分析时非常有用。

detect.py 的默认输出目录是runs/detect/exp,每次运行会自增为 exp2、exp3。这个设计很容易被忽略,但实际调试时很方便:不同参数跑出来的结果文件夹互不覆盖,可以直接对比同帧画面在不同阈值下的表现差异。如果你要处理的是图片文件夹,--source填目录路径即可,支持视频、图片、摄像头流。摄像头实时推理在这个资源里也能直接跑,--source 0就可以调用默认摄像头,部署到工地闸机场景时这个能力尤为重要。

4. 从 PyTorch 到 TensorRT 与 DeepStream:部署链路里的 cpp 和 cu 是干什么的

4.1 yololayer.cu 与 nvdsparsebbox_Yolo.cpp:从 PyTorch 到专用引擎的桥

资源包里出现 yololayer.cu 和 nvdsparsebbox_Yolo.cpp 这两个文件,说明项目作者不是只做到「训练完出个 demo」就结束,而是把模型推到了 DeepStream 部署这一层。yololayer.cu 是 YOLOv5 在 TensorRT 里的自定义解码层,负责把网络输出的原始特征图解析成检测框的坐标和类别概率。之所以要单独写一个 .cu 文件,是因为 YOLOv5 的 detect 头里有解码逻辑,TensorRT 原生的层不直接支持,必须用 CUDA 把这个解码过程实现为自定义 plugin。

nvdsparsebbox_Yolo.cpp 则是 DeepStream 里的 bbox 解析函数,DeepStream 在拿到 TensorRT engine 输出的原始检测结果后,需要调这个接口把数据转换成 NvDsObjectMeta 结构。这两个文件的共同点是:它们都是「格式转换器」,一个把网络输出变成坐标,一个把坐标变成 DeepStream 能理解的元数据。没有它们,光有训练好的 .pt 权重,DeepStream 根本不知道该怎么解读模型吐出来的矩阵。

所以当你看到这两个文件时,应该意识到这份资源的价值不止于训练,而是覆盖了从 PyTorch 训练到 GPU 加速推理的完整链路。评审能给 98 分,有一部分原因就是这种工程完整性。接下来的关键问题是:你自己的部署环境是否具备编译这些文件的条件。

4.2 从 .pt 到 .engine 的转换链路

如果你的目标是在 DeepStream 里跑这个模型,标准流程是先把 PyTorch 权重导出成 ONNX,再转成 TensorRT engine。导出这一步用 YOLOv5 自带的 export.py:

python export.py \ --weights best.pt \ --include onnx \ --opset 11 \ --simplify

--opset 11是 ONNX 算子集版本,TensorRT 8.x 对 opset 11 支持最稳定,版本太高有些算子会不兼容,导出报错就从这里开始排查。--simplify会调用 onnx-simplifier 对计算图做精简,去掉冗余节点,这一步建议保留,能减少后续转 engine 的报错概率。导出完成后用 onnxruntime 先跑一遍验证输出,确认精度没有明显变化再继续。

转换 engine 用 trtexec 工具,这是 TensorRT 自带的命令行工具,不需要写代码:

trtexec \ --onnx=best.onnx \ --saveEngine=best.engine \ --fp16 \ --workspace=1024

--fp16开启半精度推理,显存占用和延迟都能降不少,但代价是精度可能会有微小损失。对这个场景来说,FP16 造成的影响通常肉眼难辨,但如果实测发现小目标漏检变多,第一步就是去掉--fp16重新转一个 FP32 engine 做对照。--workspace指定 TensorRT 构建时能用的显存上限,单位是 MB,这一步不能省,遇到显存不足的报错优先调大这个值。

转好 engine 之后,在 DeepStream 的配置里指定模型文件,注意预处理参数要和训练时一致。YOLOv5 训练时归一化方式是像素值除以 255,DeepStream 的net-scale-factor要设置成 0.003921568627451(即1.0/255的浮点值)。很多人在这一步出错:训练时用的 BGR,DeepStream 默认读入的也是 BGR,这里不需要转换;但如果你的模型导出前做过其他预处理,就必须在配置里同步调整,否则推理结果会出现大面积误检。

4.3 Dockerfile 部署的取舍

资源里带 Dockerfile 和 .dockerignore,说明作者尝试了容器化部署。用 Docker 跑 DeepStream 是业界常见做法,好处是环境隔离、迁移方便,坏处是 GPU 直通配置不熟练的人会在起容器阶段就卡很久。一个最小可用的 DeepStream Dockerfile 思路是把 DeepStream 基础镜像作为底,把编译好的 engine 文件、配置文件和解析插件的源码拷进去,而不是在容器里重新编译 TensorRT engine。

.dockerignore 的作用同样关键,常见做法是把数据集、runs 目录、标注 XML 这些训练阶段的大文件排除在镜像之外。我之前见过有人把整个 20G 的数据集塞进镜像,构建出来镜像体积大得离谱,传到部署机器上费半天时间。构建完成后启动容器时,必须加--gpus all参数并安装 nvidia-container-toolkit,否则容器内部看不到显卡,推理程序会直接报 CUDA 初始化失败。

容器化部署的核心经验是:镜像里只放推理需要的东西,模型权重、配置文件、推理脚本,其他的一律不要。训练阶段的数据和代码放在宿主机上,通过 volume 挂载进去,这样即使模型要重新训练,容器也不用重新构建。

5. 避坑:让反光衣安全帽检测精度崩掉的五个操作

5.1 mAP 不见涨,loss 一直在降:类别不平衡在捣鬼

现象是训练日志里 box_loss 稳定下降,但验证集的 mAP50 一直停留在 0.7 左右不动,反光衣这个类别的 P/R 曲线尤其难看。原因在于反光衣样本数量和安全帽差距太大,模型把大部分参数容量都用去拟合安全帽,反光衣只学到了少量特征的表面模式,验证集上稍微变形就认不出来。解决方向有两个:一个是数据层面,把反光衣样本通过水平翻转、亮度扰动、HSV 增强扩到接近安全帽的数量级;另一个是损失函数层面,在 hyp 配置文件里把fl_gamma从 0.0 调到 1.5 到 2.0,focal loss 会让模型把注意力转向难分类的反光衣样本。我一般先扩数据,再调 fl_gamma,两个手段叠加效果最明显。

5.2 白天准,晚上崩:只有「好看」的训练集

现象是同一个权重,白天拍的视频检测正常,傍晚或阴天场景反光衣大量漏检。原因不是模型退化了,而是训练集里 90% 以上都是光线充足的白天画面,模型学会的是「高亮度下的荧光色块」,而不是「反光衣」这个语义概念。解决方法是重建验证集和训练集的时段分布:从监控视频里按整点抽帧,确保早上、中午、傍晚、夜间各有一定比例。夜间反光衣本质上靠反光识别,目标在画面里可能偏暗,这时候数据增强里的亮度扰动区间要适当向低亮度方向扩展,hsv_shsv_v的增强范围加大一些能显著提升鲁棒性。

5.3 验证 loss 反弹:超参数一次改太多

现象是训练前 60 个 epoch 一切正常,之后验证 loss 开始持续上升,训练 loss 还在降。这是典型的过拟合信号,很多人第一反应是调 weight_decay,结果越调越乱。根本原因是超参数调整没有遵循单变量原则,可能同时改了 lr、weight_decay 和 fl_gamma,导致模型容量和正则化强度的匹配关系被打破。解决方法是回退到默认超参数,只把 epoch 减到 80,加早停策略。如果确实需要提升泛化能力,用--weights从一个更大的预训练模型(比如 yolov5m)迁移,比盲目调超参数更可靠。

5.4 engine 部署后效果肉眼可见变差:FP16 的精度代价

现象是 PyTorch 里跑 best.pt 效果正常,转成 engine 部署后小目标漏检明显增多,偶尔还出现类别错乱。先检查是不是 FP16 精度损失导致的,把 trtexec 命令里的--fp16去掉转一个 FP32 engine 对比,如果 FP32 恢复正常,说明确实是精度问题。此时可以保留 FP16 但把--img从 640 提高到 960,输入分辨率提升对恢复小目标召回率效果立竿见影。如果还想用 INT8,必须在验证集上做校准,校准图片集要有足够的多样性,建议至少 500 张覆盖不同光线条件的图片,否则量化误差会在某些场景集中爆发。

5.5 小目标全漏检:img 640 的上限

现象是站在远处的人完全检测不到,近处的正常。原因是输入分辨率 640 时,远处的目标可能只占十几个像素,YOLOv5 的下采样倍率决定了太小的目标在特征图上已经不可分。解决方法是把推理和训练分辨率都提到 1280,这在反光衣安全帽场景里是值得的,因为这个场景的核心是「不漏人」,而不是「跑得快」。代价是训练时间约翻倍,显存占用上升。如果硬件支持,我建议训练阶段就用 960 起底,部署时用同一分辨率,训练和推理分辨率不一致会在真实场景里引入额外的尺度偏差,这个偏差很难用阈值调参弥补。

6. 验证与调优:mAP、混淆矩阵和两个阈值的取舍

训练完成后,runs/train目录下除了权重文件,还有一组容易被忽略的产物:confusion_matrix.pngPR_curve.pngresults.csv。val.py 验证脚本跑完之后,先看 confusion_matrix,它能直接告诉你误报的源头。反光衣安全帽这个场景里,最常见的是 vest 被识别成 helmet,或者背景被识别成 vest。前者说明两个类别在画面中的颜色和位置特征有重叠,后者说明置信度阈值偏低,虚警偏多。PR_curve 里每条曲线的拐点就是对应类别的置信度最优区间,拿来作为 detect.py 里--conf-thres的初始值,比凭感觉填数值靠谱得多。

两个阈值的调节顺序也有讲究:先固定 IoU 阈值 0.45,只看置信度阈值的变化。把 conf 从 0.5 往 0.2 调,召回率会上升但误报也增加;反过来,误报减少但漏检变多。工地的实际诉求是「宁可多框一个背景,不可漏掉一个人」,所以我会把阈值往低处定,然后用逻辑层的帧间确认去过滤虚警,比如同一位置连续 3 帧都被检出才判定为有效目标。这个策略的实现成本极低,但效果比单纯调阈值显著得多。下表是我的调参基准:

参数说明
conf-thres0.30偏低,保证远端小目标不丢
iou-thres0.50偏高,减少密集人群下的重复框
img-size1280针对小目标场景的折中

验证调优这件事,测试集上的 mAP 只是参考,真正要盯着的是部署现场的连续视频流。我以前总是只看 mAP 数字,直到有一次在真实闸机视频上跑,才发现默认阈值产生了一堆背景虚警,mAP 再高也抵不过现场观感崩坏。从那以后,我每换一个场景都强制先跑 10 分钟真实视频,把误报的帧截图拉出来看分布,再决定阈值的最终取值。不管你是拿这份资源做毕设、课程设计,还是要上真实项目,这套验证流程都建议完整走一遍。希望这份拆解能帮你在反光衣安全帽检测上少走几步弯路。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询