简介:基于深度学习的头盔佩戴检测识别系统毕业设计完整项目包,面向计算机视觉方向学生及企业安防场景开发者,解决电动自行车头盔佩戴自动检测与识别问题。资源内共77个文件,压缩包仅23.73MB,包含PyTorch训练与推理脚本、YOLO配置文件、VOC格式数据集及转换工具、已训练权重下载脚本、环境配置文档和演示视频等,涵盖从数据标注到模型部署的完整链路。项目基于YOLOv5框架,提供train.py/detect.py等核心代码,附带yaml配置文件与Dockerfile,便于快速搭建运行环境。已有195人学习,适合用于课程设计、毕业设计或实际场景的算法验证。通过教程文档和ipynb示例,可掌握数据集制作、模型训练、测试评估与权重复用方法,节省从头调试的时间。
1. 头盔佩戴检测为什么值得用 YOLOV5 重做一遍
在工地门口、厂区走廊或者塔吊下方,戴没戴安全帽看起来只是一个二分类问题,实际落到视频流里就变成了目标检测问题:人站在几米外,头部只有几十个像素;正午逆光时帽檐和脸完全分不开;工人低头作业时,头部的姿态又让标注框随时变化。基于 YOLOV5 的头盔佩戴检测识别系统,通常以源码、数据集、训练好的权重和教程打包成 zip 分发给使用者,它的核心价值不是“模型能跑通”,而是让拿到包的人能快速复制出完整的训练、推理和调优流程。对刚接触目标检测的工程师来说,这套结构正好演示了数据集格式、训练命令、权重文件三者怎么串起来;对要做现场交付的人来说,更重要的是理解训练好的模型是用什么数据拟合出来的,出问题时从哪个环节开始排查。接下来我从数据准备、环境配置、训练调参、推理评估到最后的部署技巧,把这套系统自己动手落地时会踩的坑逐一说清楚。
2. 数据集准备与 YOLOV5 环境配置:把可复现闭环先跑起来
大多数头盔检测项目包解压后,数据部分都是 images 和 labels 两个目录,权重部分则是一个 best.pt。我拿到这类包的第一件事不是跑 detect.py,而是先看三样东西:目录结构、标签内容和环境依赖。数据和代码如果不在同一个约定下,后面所有训练都是白做。
2.1 看数据集目录结构:训练集、验证集与标签文件
YOLOv5 对数据集的组织方式有硬性要求,images 和 labels 必须同级目录存在,train、val 子目录的名字也最好固定。头盔检测常见两个类别:helmet 和 head,分别表示戴了安全帽的头部和未戴安全帽的头部。目录结构大致长这样:
helmet_dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── 0002.jpg │ ├── val/ │ │ └── 0301.jpg │ └── test/ │ └── 0501.jpg └── labels/ ├── train/ │ ├── 0001.txt │ └── 0002.txt ├── val/ │ └── 0301.txt └── test/ └── 0501.txt每个 txt 文件对应一张图片,每一行是一个目标框,格式固定为class_id x_center y_center width height,四格坐标全部归一化到 0~1。比如1 0.5 0.3 0.2 0.4,表示这个框的中心在图像横向一半、纵向三成的位置,框宽占整图 20%,框高占 40%。这里最容易出错的是把坐标当成像素值直接用,YOLOv5 训练器读取时会直接拿这个值去乘图片宽高,所以一旦混入绝对像素坐标,框就会偏到画面外。
如果项目给的是 COCO JSON 或 VOC XML,需要先把标注转成 YOLO 格式。项目包里如果提供的是 JSON 标注,我一般直接用下面这段脚本转:
import os import json def coco_to_yolo(coco_path, label_dir): os.makedirs(label_dir, exist_ok=True) with open(coco_path, 'r', encoding='utf-8') as f: data = json.load(f) images = {img['id']: img for img in data['images']} for ann in data['annotations']: img = images[ann['image_id']] w, h = img['width'], img['height'] x, y, bw, bh = ann['bbox'] x_center = (x + bw / 2) / w y_center = (y + bh / 2) / h width = bw / w height = bh / h label_path = os.path.join(label_dir, img['file_name'].replace('.jpg', '.txt')) with open(label_path, 'a', encoding='utf-8') as out: out.write(f"{ann['category_id']} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")转换逻辑看起来简单,但有两个地方最容易翻车:一是 COCO 的 bbox 给的是左上角坐标加宽高,YOLO 需要的是中心点加归一化宽高,所以x和y要先加上bw / 2、bh / 2再除图片宽高;二是类别 id 不能直接用 COCO 原始编号,必须再建一张映射表,把person映射成 0、helmet映射成 1,不然模型会把类别顺序学反。
标签检查完以后,可以用一段简单代码统计每个标注文件里有没有越界坐标,以及类别 id 是否都小于nc。YOLOv5 训练时如果发现标签和图片对不上,日志里会报image 1/xxx (label file x.txt not found),不需要自己写特别复杂的校验,但最好提前跑一次。
2.2 安装 YOLOV5 环境:Python 版本、PyTorch 与依赖
环境配置是使用 YOLOv5 时最常卡住的一步。通常做法是先把项目源码解压到一个固定路径,然后用 conda 建独立环境,避免污染系统自带 Python。我习惯这样装:
conda create -n yolov5 python=3.8 -y conda activate yolov5 conda install pytorch torchvision torchaudio cudatoolkit=11.3 -c pytorch -y cd yolov5-project pip install -r requirements.txtrequirements.txt 里包含 numpy、opencv-python、matplotlib、tensorboard 等 YOLOv5 的运行依赖。cudatoolkit=11.3这个版本号要跟你机器上的 NVIDIA 驱动匹配,驱动太旧就换低一档的 CUDA 版本,不用死守同一个值。如果没有 N 卡,可以装 CPU 版 PyTorch,但训练速度会慢到让你怀疑人生,所以训练阶段还是建议找一台带 GPU 的机器。
装完之后先跑一个最小验证,确认 torch 能正常使用 GPU,再下载一张测试图执行自带检测命令:
python detect.py --weights yolov5s.pt --source data/images --img 640如果这条命令能输出带框的图片,说明 OpenCV、PyTorch、YOLOv5 基础链路都没问题。下面这几条检查命令足够定位大部分环境故障:
| 验证命令 | 通过条件 |
|---|---|
nvidia-smi | 能看到显卡型号和显存 |
python -c "import torch; print(torch.cuda.is_available())" | 输出True |
python detect.py --help | 正常打印参数帮助 |
python detect.py --weights yolov5s.pt --source data/images | 在runs/detect里生成标注图 |
环境问题最典型的表现是AttributeError: 'Upsample' object has no attribute 'recompute_scale_factor',这基本是 PyTorch 版本和 YOLOv5 源码不匹配,优先回退 PyTorch 版本,不要自己改 upsampling 代码。
2.3 创建 helmet.yaml:类别、路径和数据集描述
YOLOv5 的train.py不会直接在命令行里接收类别名,而是读取一个 yaml 文件。头盔检测项目里,我把这个文件命名为helmet.yaml:
# helmet.yaml train: ../helmet_dataset/images/train val: ../helmet_dataset/images/val nc: 2 names: 0: helmet 1: headyaml 里的train和val必须指向 images 目录,YOLOv5 会自动把路径中的images替换成labels,所以上面写的是images/train,标签目录必须真实存在于helmet_dataset/labels/train。nc表示类别总数,names的顺序必须与标注 txt 里的class_id一一对应。顺序写反是最冤的错误:模型会把戴帽子的人学到类别 0,没戴帽子的人学到类别 1,训练指标看着正常,实际推理结果完全相反。
如果项目数据集里已经有训练集、验证集和测试集,我会在 yaml 里多留一个test项,因为最后评估模型时测试集不应该参与训练,否则 mAP 会有虚高。写完 yaml 后跑一次一个 epoch 的训练来验证数据链路:
python train.py --data helmet.yaml --weights yolov5s.pt --epochs 1 --batch-size 8这一步不是为了训练出模型,而是触发 YOLOv5 的数据集解析逻辑,任何一个标签文件路径错误或图片坐标系异常,都会在这里直接暴露。
3. 用 YOLOV5 源码训练头盔模型:超参数、日志与权重挑选
环境就绪、数据集结构正确之后,接下来进入训练阶段。YOLOv5 源码里的train.py已经封装好了训练循环、数据加载和评估逻辑,不需要改网络结构,只需要把数据集、超参数和显存资源匹配好。下面是头盔检测的典型训练流程。
3.1 最小可用的训练命令,以及每个参数为什么这么设
在单张 8GB~16GB 显存的 GPU 上,我常用的训练命令是这样的:
python train.py \ --data helmet.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache \ --name helmet_exp--weights yolov5s.pt表示从 YOLOv5s 的 COCO 预训练权重开始继续训练。安全管理帽的形态和 COCO 数据里 person 类别有一定相似性,迁移学习能明显加速收敛,比从头训练少用一半 epoch。--img 640是输入分辨率,头盔属于中小目标,如果图片里人的头部只占很小的区域,640 可能不够,可以考虑 1280,但显存占用会大幅上升。--batch 16在 16GB 显存下配合 640 分辨率是可以跑起来的,如果显存只有 8GB,就降到 8。--cache会把图片一次性缓存进内存,训练速度提升非常明显,代价是占用大约等于数据集大小的内存。
参数表如下,方便你根据自己的机器调整:
| 参数 | 常见取值 | 说明 |
|---|---|---|
weights | yolov5s.pt/last.pt | 预训练权重或断点续训权重 |
img | 512 / 640 / 1280 | 输入分辨率,小目标多则调大 |
batch | 8 / 16 / 32 | 受显存限制,batch 过小时 BN 层不稳定 |
epochs | 100 / 200 | 看 val mAP 是否变平,不是越大越好 |
cache | 无 /ram | 开启后训练更快,占用内存 |
name | 自定义字符串 | 决定结果输出到runs/name |
训练中途断掉不需要从头开始,直接用 last.pt 续训:
python train.py --data helmet.yaml --weights runs/helmet_exp/weights/last.pt --resume--resume会自动读取上次的 epoch、优化器状态和超参数,断点续训后之前的曲线也会保留在 tensorboard 里。
3.2 头盔场景真正值得调的 YOLOV5 超参数
YOLOv5 把超参数放在独立的 yaml 文件里,项目默认用的是hyp.scratch-low.yaml。数据集不同,最优超参数也不一样,头盔检测最值得关注的是下面这几个:
| 配置项 | 默认值 | 头盔场景的影响 |
|---|---|---|
lr0 | 0.01 | 初始学习率。数据量小或标注噪声大时设为 0.005 |
warmup_epochs | 3.0 | 前 3 轮小学习率热身,小数据集建议保留 |
mosaic | 1.0 | 马赛克增强。头盔目标小且密集,mosaic 过强会把目标截断 |
mixup | 0.0 | 混合增强。类别间相似度高时调大会增加误检 |
fliplr | 0.5 | 水平翻转。摄像头部署位置固定时,可以降到 0.0 |
修改超参数时单独复制一个文件,不要改原始配置:
python train.py --data helmet.yaml --hyp hyp.scratch-low.yaml --epochs 200我一般不会一上来就动超参数。头盔项目最常见的失败不是训练不收敛,而是“没戴安全帽的人”被检测成“戴了安全帽”。这种情况先补数据,再调超参数。把大量“不戴帽的人头”、“手扶帽”的负样本加入训练集,比调整数据增强参数有效得多。
网格搜索超参数可以使用项目自带的utils/torch_utils.py里的遗传算法脚本,但头盔检测场景数据集通常在几千张以内,网格搜索性价比不高,手动训练两三轮一般就够。
3.3 训练日志里哪些信息决定模型质量
训练过程中,每一轮结束会输出一组指标,越到后面越值得盯着看:
Epoch gpu_mem box obj cls labels img_size 99/199 8.67G 0.0184 0.0152 0.0102 9 640 Class Images Instances P R mAP50 mAP50-95 helmet 201 389 0.881 0.844 0.911 0.623 head 201 175 0.745 0.712 0.768 0.492box、obj、cls 分别是边框回归损失、目标置信度损失和分类损失。如果某个 loss 一直不降,比如 cls loss 波动很大,往往说明两类样本数量极度不均衡,或者标注框把上半身都标了进去,导致模型学到“有人就有头”的错误特征。
更关键的是 P、R、mAP50 三个指标。P 是精确率,检测出来的框里真正正确的比例;R 是召回率,所有真实目标里被检出的比例。在头盔安全检测场景下,漏检的代价远大于误检,所以我更看重召回率。如果 helmet 类 mAP50 一直在 0.9 以上,而 head 类只有 0.7 左右,说明模型对“没戴头盔”的头部识别不稳定,优先检查 head 类标注框是不是太小、边界是不是太贴近头发边缘。
训练结束后,模型会自动保存两个权重:best.pt和last.pt。best.pt 是根据验证集 mAP 最优时保存的,last.pt 是最后一个 epoch 的权重。不要默认 last.pt 就是更好的,对大部分场景直接用 best.pt 即可。
4. 加载训练好的权重:从 detect.py 到 Python API 推理
训练完成后,项目包里的 best.pt 就是“训练好的数据”里最核心的部分。接下来要做的不是把它当一个黑盒点两下,而是清楚它支持哪些输入源、推理参数怎么调、以及如何在自研系统里调用。
4.1 detect.py 推理参数:source、conf、iou 和 classes
YOLOv5 自带的 detect.py 支持图片、视频、摄像头和 RTSP 流。头盔检测项目落地时,最常见的是接本地摄像头:
python detect.py \ --weights runs/helmet_exp/weights/best.pt \ --source 0 \ --conf 0.35 \ --iou 0.45 \ --img 640 \ --save-txt \ --project output--source 0表示读取系统默认摄像头,数字换成 RTSP 地址就是视频流转检测。--conf 0.35是置信度阈值,低于这个值的预测框会被丢掉。头盔检测场景不建议把阈值拉到 0.5 以上,因为远距离目标置信度天然偏低,太高会出现大量漏检。--iou 0.45是 NMS 的 IoU 阈值,两个框重叠超过这个比例时只保留置信度更高的那一个。--save-txt会额外把检测结果写成 txt 文件,格式为frame class_id x1 y1 x2 y2 conf,方便和门禁系统对接。
detect.py 还有两个参数在日常调试里很有用:
| 参数 | 作用 |
|---|---|
--classes 0 1 | 只检测特定类别,比如只检测 head 类,用来排查误检 |
--hide-conf | 不在画面上显示置信度数字,适合做演示视频 |
--line-thickness | 调整框线宽度,1080p 画面上建议设 2 或 3 |
摄像头推理时不要把--img直接改成现场分辨率。1920×1080 的画面如果直接 1080 输入,显存爆炸而且速度很慢。标准做法是保持 640 或 1280 推理,画面缩放交给 YOLOv5 预处理完成。
4.2 在 Python 中加载 best.pt 做自定义检测逻辑
如果要把头盔检测嵌入到自己的后台服务里,不能每次调用都整条命令启动 detect.py。更实际的方式是在 Python 环境里加载模型权重,自己控制帧读取和结果处理。项目包里如果有 YOLOv5 源码目录,我使用下面的方式:
import torch model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/helmet_exp/weights/best.pt', force_reload=False) model.conf = 0.35 model.iou = 0.45这样加载的对象输入一个 BGR 图像数组,调用一次就返回所有检测结果。写成带摄像头的循环就是:
import cv2 cap = cv2.VideoCapture(0) while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model(frame, size=640) df = results.pandas().xyxy[0] for _, row in df.iterrows(): x1, y1, x2, y2 = int(row['xmin']), int(row['ymin']), int(row['xmax']), int(row['ymax']) label = f"{row['name']} {row['confidence']:.2f}" color = (0, 255, 0) if row['name'] == 'helmet' else (0, 0, 255) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) cv2.putText(frame, label, (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow('helmet', frame) if cv2.waitKey(1) & 0xFF == ord('q'): breakpandas() 返回的 DataFrame 里每一行分别是 xmin、ymin、xmax、ymax、confidence、class、name。判断class字段比判断 name 更可靠,因为 name 是根据 yaml 里的 names 生成的,如果别人把 names 顺序调乱了,字符串判断就会出问题。如果是离线环境且 torch.hub 下载失败,可以从本地导入models.experimental.attempt_load代替,detect.py 内部也走的是这条路。
4.3 用 val.py 验证模型效果,而不是只看一帧检测图
只看一张图片的检测效果很容易被高估。模型可能在这张图上表现很好,但换了角度、换了光线就崩。YOLOv5 提供 val.py,会在整个验证集上算平均精度:
python val.py \ --data helmet.yaml \ --weights runs/helmet_exp/weights/best.pt \ --img 640 \ --conf 0.35 \ --iou 0.45输出结果中 mAP50 是最直观的参考,对应 IoU 阈值为 0.5 时所有类别的平均精度。不同场景可以按下表做初步判断:
| mAP50 | 是否可用 |
|---|---|
| 0.90 以上 | 基本可用,可进入实地小规模测试 |
| 0.75~0.90 | 可用但漏检较多,建议补充难例 |
| 0.75 以下 | 不建议上线,优先检查标注和数据分布 |
val.py 结束后会在runs/val/exp里生成混淆矩阵图和 PR 曲线图。混淆矩阵横轴是真实类别,纵轴是预测类别,对角线越亮越好。重点看 head 那一列有没有大量跑到 helmet 行,这是“无帽误报成有帽”的典型信号。
5. 从“能出框”到“敢上线”:头盔检测的小目标切图与部署导出
模型在验证集上 mAP 达标,不代表现场视频流能用。施工现场的摄像头分辨率高、视野大,一个站在画面角落的人可能只占几十个像素。这一章讲两个最有效但常被忽略的做法。
5.1 小目标场景用滑窗切图,而不是把整帧缩到 640
许多工地摄像头输出 1920×1080 甚至 2560×1440 的画面,直接整帧缩放成 640 后,一个人头可能只有 5×5 像素,再强的检测器也认不出来。常见的做法是先在原图上做滑窗裁剪,对每个子图分别推理,再把框坐标映射回原图后做全局 NMS。
def tiled_detect(model, frame, crop=640, overlap=0.2): h, w = frame.shape[:2] stride = int(crop * (1 - overlap)) detections = [] for y in range(0, max(h - crop + 1, 1), stride): for x in range(0, max(w - crop + 1, 1), stride): patch = frame[y:y + crop, x:x + crop] result = model(patch, size=640).xyxy[0].cpu().numpy() for box in result: detections.append([ box[0] + x, box[1] + y, box[2] + x, box[3] + y, box[4], box[5] ]) return detectionsoverlap 取 0.2 能避免目标正好被切在子图边缘。这里的返回值只是候选框,还需要再做一次全局非极大值抑制,否则同一个目标可能被多个滑窗的重复框输出多次。
切图推理的耗时等于子图数量乘以单次推理耗时,所以生产环境里不要对每一帧全画面切图,我一般会先用一个 640 全景模型判断画面里是否存在人,只有判断出有人时才触发滑窗细检。
5.2 导出 ONNX,部署到没有 YOLO 源码的机器
工控机或者边缘盒子往往不会手动安装 YOLOv5 全套依赖,最省事的部署方式是把 best.pt 导出成 ONNX:
python export.py \ --weights runs/helmet_exp/weights/best.pt \ --img 640 \ --include onnx导出完成后会生成best.onnx,文件比 .pt 小很多,推理时只需要 onnxruntime:
import cv2 import onnxruntime sess = onnxruntime.InferenceSession('best.onnx') input_name = sess.get_inputs()[0].name # 读取图像后执行 letterbox + 归一化,再喂给 sessionONNX 导出需要注意两点:一是输入尺寸必须固定,尽量和训练时一致;二是检测模型的 NMS 节点在导出时可能不包含,不同版本的 YOLOv5 对这个问题的处理方式不同。导出后先用同一张原图分别跑一次 detect.py 和 onnxruntime,对比两个结果里的坐标,如果差距超过 2~3 个像素,就要检查预处理里的 letterbox 参数是否一致。确认这一步没有偏差后,再把头盔检测接到门禁闸机或者现场抓拍的平台上,模型才能算真正落到项目里。
本文还有配套的精品资源,点击获取