
简介面向计算机、数学、电子信息等专业毕业设计及课程设计场景的YOLOv5反光衣与安全帽检测项目包含完整源码、训练好的权重及配套数据集涵盖数据预处理、模型训练、推理检测等关键环节可用于快速复现安全帽佩戴与反光衣穿着识别流程。项目经导师指导并获高分评审适合正在准备毕设的学生或需要实战练习的学习者参考借鉴也可作为期末大作业。压缩包内共1131个文件以Python脚本475个py、编译缓存、yaml配置文件与txt说明文档为主同时包含大量png/jpg图像样本、ipynb示例、pth权重文件以及Dockerfile、使用手册和演示视频等辅助内容整体大小约48.12MB目录结构清晰便于按模块检索。目前已有154人学习使用。资源自带可运行的检测代码、预训练模型权重和标注数据集能够直接运行或进一步调优满足毕业设计、课程设计中对目标检测项目的完整性与可展示性要求同时省去自行训练模型的成本适合作为高分参考项目进行二次开发。1. YOLOv5 反光衣安全帽检测不是两个目标的问题是一整套落地链路的问题把 YOLOv5 跑通容易把 YOLOv5反光衣安全帽检测跑进工地实景是另一回事。我见过太多项目卡在同一个地方demo 视频上检测效果不错一到自己拍的素材上漏检、误报全冒出来了。反光衣和安全帽看着只有两个类别实际要同时应付小目标、反光材质、密集遮挡和类别不平衡任何一个环节偷懒mAP 都会用数字诚实地告诉你。这份资源把训练好的权重、标注过的数据集、完整源码和 DeepStream 部署文件打包在一起评审分 98 分适合正在做毕业设计、课程设计或者想直接拿一套可用权重做二次开发的从业者。下载下来不是看代码而是看一条已经跑通的链路。2. 数据集与标注决定训练上限的是每一行 txt2.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), encodingutf-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 支持在images和labels目录下直接分 train/val 子目录对应关系如下目录内容dataset/images/train训练图片jpg/png 均可dataset/labels/train与训练图片同名的 txtdataset/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 python3.8 -y conda activate yolov5 pip install -r requirements.txtrequirements.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.pypython 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 \ --onnxbest.onnx \ --saveEnginebest.engine \ --fp16 \ --workspace1024--fp16开启半精度推理显存占用和延迟都能降不少但代价是精度可能会有微小损失。对这个场景来说FP16 造成的影响通常肉眼难辨但如果实测发现小目标漏检变多第一步就是去掉--fp16重新转一个 FP32 engine 做对照。--workspace指定 TensorRT 构建时能用的显存上限单位是 MB这一步不能省遇到显存不足的报错优先调大这个值。转好 engine 之后在 DeepStream 的配置里指定模型文件注意预处理参数要和训练时一致。YOLOv5 训练时归一化方式是像素值除以 255DeepStream 的net-scale-factor要设置成 0.003921568627451即1.0/255的浮点值。很多人在这一步出错训练时用的 BGRDeepStream 默认读入的也是 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.0focal loss 会让模型把注意力转向难分类的反光衣样本。我一般先扩数据再调 fl_gamma两个手段叠加效果最明显。5.2 白天准晚上崩只有「好看」的训练集现象是同一个权重白天拍的视频检测正常傍晚或阴天场景反光衣大量漏检。原因不是模型退化了而是训练集里 90% 以上都是光线充足的白天画面模型学会的是「高亮度下的荧光色块」而不是「反光衣」这个语义概念。解决方法是重建验证集和训练集的时段分布从监控视频里按整点抽帧确保早上、中午、傍晚、夜间各有一定比例。夜间反光衣本质上靠反光识别目标在画面里可能偏暗这时候数据增强里的亮度扰动区间要适当向低亮度方向扩展hsv_s和hsv_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.png、PR_curve.png和results.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 分钟真实视频把误报的帧截图拉出来看分布再决定阈值的最终取值。不管你是拿这份资源做毕设、课程设计还是要上真实项目这套验证流程都建议完整走一遍。希望这份拆解能帮你在反光衣安全帽检测上少走几步弯路。本文还有配套的精品资源点击获取