ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv5的电动自行车头盔佩戴检测实战全流程

基于YOLOv5的电动自行车头盔佩戴检测实战全流程 简介目标检测是计算机视觉领域的核心任务之一其原理是通过深度学习模型对图像中的物体进行定位与分类。在众多算法中YOLO系列凭借其高效的单阶段检测架构成为工业界应用最广泛的方案之一。YOLOv5作为该系列的重要版本在检测精度与推理速度之间取得了良好平衡尤其适合小目标场景下的工程落地。头盔佩戴检测作为智慧交通与公共安全领域的典型应用需要模型在复杂监控画面中准确识别骑行者头部状态。本文以电动自行车头盔检测项目为例系统讲解从数据集构建、标注规范、模型训练调参到服务部署的完整流程并分享实际踩坑经验与优化技巧帮助开发者快速掌握YOLOv5目标检测项目的实施方法降低二次开发成本。 最近把电动自行车头盔佩戴检测这个项目完整跑通了从数据集清洗、模型训练到最后的服务部署整个链路都走了一遍。这里把过程详细记录下来包括踩过的坑和验证有效的参数配置给正在做YOLOv5目标检测方向的朋友一些参考。这个项目实现的是对电动自行车骑行人员是否佩戴头盔进行自动识别核心是基于YOLOv5框架训练目标检测模型最终输出包含佩戴头盔、未佩戴头盔、人头等类别的检测结果。整套内容包括完整源码、标注好的数据集、训练好的权重文件以及可以二次开发的API调用代码。无论你是刚接触目标检测的初学者还是要在实际业务中落地这套方案的技术人员这篇记录都能帮你节省大量试错时间。1. 项目背景与整体设计思路1.1 业务需求与检测逻辑选型电动自行车头盔佩戴检测这个场景核心难点不在于“识别头盔”本身而在于“判断这顶头盔是不是戴在那个人头上”。听起来有点绕但在实际路口监控画面里你经常能看到骑车人把头盔挂在车把上、放在车筐里人却光着脑袋。这时候如果模型只识别“画面里有没有头盔”就会给出错误的判断结果。所以检测逻辑上有两种主流方案第一种是检测“人”和“头盔”两个类别然后通过比对人的检测框和头盔检测框的位置关系来判断是否佩戴。具体做法是计算头盔框中心点是否落在人头框的顶部区域内或者直接计算头盔框与人头框的IOU交并比超过阈值就判定为佩戴。这种方案的好处是模型相对简单两类目标都比较容易标注缺点是后处理逻辑稍微复杂而且当画面里有多个人时需要做头盔和人的匹配。第二种是直接检测“佩戴头盔的头”和“未佩戴头盔的头”两个类别。模型直接输出带帽头和光头的检测框业务逻辑零判断检测结果就是最终结果。这种方案我在实际测试中发现精度更高因为标注时就是按照头部区域画的框框里就是“头头盔”或“头光头”整体模型学习到的特征是完整的头部形态。缺点是对于密集场景小目标的召回率需要仔细调。我最终选择了第二种方案同时加了第三个类别“person”完整人体框用于辅助筛选避免把背景中的无关人员算进去也方便后续统计总车流量和人车对应关系。最终类别是三个helmet_head戴头盔的头部、no_helmet_head未戴头盔的头部、person人体。1.2 为什么选YOLOv5而不是YOLOv8或更早版本这个项目选型时对比过YOLOv5、YOLOv8和YOLOv3。YOLOv3训练收敛速度偏慢小目标检测能力相对弱一些头盔在监控画面里属于典型的小目标可能只有几十个像素在YOLOv3上需要花很多精力调anchor和输入分辨率性价比不高。YOLOv8确实是更新更好的框架但考虑到项目需要完整可控的源码、方便二次开发以及后续要部署到嵌入式设备或带GPU的服务器上YOLOv5的生态成熟度和资料丰富度是当下最优的选择。YOLOv5配合ONNX导出、TensorRT部署的路线非常成熟社区里有大量现成的踩坑记录可以借鉴这对项目的开发周期影响很大。另外要强调的是YOLOv5的代码结构非常清晰整洁对于学习目标检测原理和做二次开发来说可读性远好于YOLOv8封装后的代码结构。如果你后续要修改损失函数或者数据结构YOLOv5的代码框架改起来会更顺手。2. 数据集构建与标注规范2.1 数据集的三种获取方式这个项目的数据集主要由三部分构成。我建议你不要只用单一来源的数据因为单一来源会导致模型泛化能力差换个场景就崩。公开数据集兜底。头盔检测这个方向开源数据集不算少GitHub上搜helmet detection能搜到不少仓库Roboflow上也有现成的头盔数据集可以直接下载使用。公开数据集适合用来做预训练和冷启动但需要注意开源数据集的标注规范往往不一致有的只有helmet这一类有的只有with/without两类需要统一转换成自己的标注体系。自采数据补充。这是最重要的部分。我在实际项目里从道路监控摄像头截取了不同时段、不同天气、不同角度的画面大约2000多张。自采数据之所以关键是因为头盔戴在哪、朝哪个方向、在画面里占多大比例每个路口的实际情况都不一样公开数据集覆盖不了这些。如果你自己采集数据注意多覆盖这几个维度白天和夜间、晴天和雨天、近距离和远距离、正面和侧面角度。数据增强扩展。YOLOv5自带的增强策略hyp.scratch.yaml里的HSV变化、平移、缩放、翻转等在训练时由--augment参数控制训练时模型会每轮对图像做随机增强。建议离线再做一次针对性增强尤其是Mosaic增强YOLOv5的Ultralytics版默认在前10个epoch使用Mosaic增强把多张图拼接成一张对小目标检测的提升很明显。2.2 标注工具选择与格式转换标注工具我用的LabelImg虽然界面朴素但胜在稳定、轻量、支持PascalVOC格式导出。安装很简单网上有编译好的包。标注时的核心规范是头盔的检测框贴合头部不要框到肩膀和躯干。因为模型学的是“头部区域带帽特征”如果框得太松把肩膀也框进去背景干扰会很严重。未佩戴头盔的检测框也是框住头部不能框脖子。遮挡严重的头部比如头盔被绿化带遮挡一半可以选择不标或者用difficult标记避免引入过多噪声。有争议的头部比如头盔风镜反光导致看不清是否戴帽我建议删掉而不是硬标一个类别这种样本会让模型学出错误的边界。LabelImg标注生成的是PascalVOC XML格式YOLOv5需要的是TXT格式每行类别id x_center y_center width height归一化到0-1。自己写一个转换脚本几十行Python就能搞定。转换脚本的核心逻辑import xml.etree.ElementTree as ET def convert_label(xml_path, txt_path, classes): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) with open(txt_path, w) as f: for obj in root.iter(object): cls obj.find(name).text if cls not in classes: continue cls_id classes.index(cls) 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) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n)提示转换后一定要抽样可视化检查我遇到过坐标原点混乱左上角vs中心点导致的训练完全无法收敛的情况。建议在训练前写一个脚本把TXT标注框画回图片上目测一遍。2.3 数据集目录结构与划分YOLOv5的目录组织方式有两种一种是images和labels分开放另一种是train/val分开放。官方推荐的结构是后者这样训练和验证天然隔离。dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 └── labels/ ├── train/ # 训练标注 └── val/ # 验证标注训练集和验证集按9:1划分。这里有个容易被忽略的点相同场景的连续帧要放在同一个集合里。如果从一段视频里抽了10帧其中8帧放训练集、2帧放验证集那验证集和训练集的内容高度相似验证结果会异常好看但换到真实新场景就失灵了。我按照监控点位分组每个点位的全部图片只属于训练集或只属于验证集这样验证结果才可信。3. 环境搭建与训练配置3.1 硬件与依赖版本我训练用的机器是单卡RTX 309024GB显存这个级别的显卡训练yolov5s模型非常从容。如果你手里是8GB显存的卡建议用yolov5n模型或者降低输入分辨率。软件环境版本是Python 3.8、PyTorch 1.12.1、CUDA 11.6、cuDNN 8.3.2。YOLOv5官方仓库ultralytics/yolov5的requirements.txt会列出一系列依赖安装时候有个坑torchvision和torch的版本必须匹配直接pip install torch torchvision装出来的版本兼容性一般没问题但如果你的环境里已经有老版本的PyTorch升级的时候要小心。建议用conda隔离环境避免和别的项目互相污染conda create -n yolov5 python3.8 conda activate yolov5 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt3.2 数据配置文件的编写YOLOv5通过YAML文件描述数据集路径和类别。在yolov5/data/下新建一个helmet.yamltrain: /path/to/dataset/images/train val: /path/to/dataset/images/val nc: 3 names: [helmet_head, no_helmet_head, person]注意train和val路径可以写绝对路径也可以写相对于yolov5目录的相对路径。写绝对路径的话换机器训练需要改这个文件不太方便我习惯把数据集放在yolov5上层目录然后写../dataset/images/train这种相对路径这样项目整体迁移更方便。3.3 预训练权重与模型规模选择YOLOv5的预训练权重在官方仓库的weights/目录下可以下载有yolov5n、yolov5s、yolov5m、yolov5l、yolov5x五个档次。头盔检测这个任务建议直接用COCO预训练的yolov5s作为起点除非你有非常充分的理由需要更轻量的模型。yolov5s在COCO上预训练过已经学会了大量通用特征边缘、纹理、形状等用它在自己的小数据集上微调收敛速度远快于从零训练最终精度也普遍更高。这个项目的起步权重是yolov5s.pt。注意下载预训练权重时一定要选择和你clone的YOLOv5版本匹配的权重。YOLOv5仓库在不同release版本里模型结构有调整比如v6.0把Focus层换成了Conv层如果你用v6.0的代码加载v5.0的权重会直接报结构不匹配的错误。4. 模型训练全流程与参数详解4.1 核心训练命令实战数据准备好、环境搭好后训练命令非常简单但参数组合需要根据实际情况调整。我最终的训练命令如下python train.py \ --data helmet.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --workers 8 \ --device 0 \ --cache逐项解释每个参数的作用--img 640是输入图片分辨率。头盔在真实监控画面里是小目标理论上有用越大分辨率输入效果越好但显存的瓶颈很快就到了。测试过1280分辨率mAP确实提升明显但训练时间和显存占用几乎翻了3倍。折中后640是主流选择。如果你的监控画面覆盖面非常大需要识别很远的距离外的小头盔建议用1280或者用SAHI这样的切片推理方案。--batch 16是批大小。batch太小会导致梯度抖动大收敛不稳定batch太大受显存限制。3090跑yolov5s 640分辨率 16张batch显存占用大约12GB完全在安全范围内。如果你的卡只有8GBbatch改成8或者用yolov5n。--epochs 150是训练轮数。150轮在这个数据集规模下足够收敛关键在于训练过程中要保存每个epoch结束时的权重等训练完再回头选择最优的那一版而不是简单用最后一轮。--cache是在训练开始前把全部图片缓存到内存里可以极大加速数据读取。但要注意总数据集几万张图片时内存会很紧张我这个数据集2000多张完全没问题。如果你的数据集比较大建议去掉这个参数或者用--cache ram。--workers 8是数据加载线程数。在Linux系统下这个值可以设成CPU核心数的一半如果碰上DataLoader卡死可以降低workers数量或者设置OMP_NUM_THREADS1环境变量。4.2 超参数配置与调优记录YOLOv5的超参数在data/hyps/hyp.scratch.yaml里。默认参数是经过大量实验的平衡值在没有充分调参经验的情况下不要轻易全局乱调。我这次只动了两个地方学习率相关默认lr00.01初始学习率。当你的batch size和默认值16差距很大时建议等比缩放学习率经验公式是lr0 0.01 * batch / 16。我用batch 16所以没动。如果你batch改成4学习率改成0.0025左右会更稳。mosaic增强YOLOv5默认是mosaic1.0始终开启Mosaic增强在最后--epochs的10%轮次会自动切换为关闭。如果你觉得增强太猛可以把mosaic调成0.5但一般不建议因为Mosaic对小目标效果太好。其他的如hsv_h、hsv_s、degrees、translate、scale这些增强参数用默认的即可目标检测的数据增强已经相当成熟随心改参数反而会破坏平衡。4.3 训练过程的监控与分析训练启动后终端会实时打印每个epoch的lossbox_loss、obj_loss、cls_loss。训练结束后在runs/train/exp*/目录下会生成一组图表results.png展示loss曲线和mAP曲线confusion_matrix.png是混淆矩阵F1_curve.png是F1分数曲线labels.jpg是标注分布图。这些图表里我建议重点关注两样东西loss曲线是否收敛。正常情况下box_loss和cls_loss是在波动中逐渐下降的到了后期趋于平缓小幅度震荡。如果loss曲线在某一个时刻突然跳升很可能说明学习率设大了导致梯度爆炸如果loss一直在直线下降但验证集mAP上不去那就是过拟合信号需要增加数据增强或提前停止。混淆矩阵的误判模式。我在第一版模型里发现no_helmet_head被误判为helmet_head的比例非常高。后来去翻样本图发现原来一些烫发卷发的形态和头盔轮廓高度相似模型学到了“头发蓬松呈块状头盔”的错误特征。这个问题的解决方法是补充了大量把头发扎起来、戴帽子、留寸头的样本同时把数据增强里的hsv_h色相变化适当降低避免模型依赖颜色特征来判断是否戴头盔。关于“损失函数怎么理解”这个问题我简单类比一下模型的本质是生成一堆数作为预测结果这些数值就是参数。训练时模型预测出的结果和真实标注有差距损失函数就是衡量这个差距的函数模型通过梯度下降不断调整内部的参数数值让损失值逐渐降低预测结果越来越接近真实标注。5. 模型评估与优化实践5.1 评估指标怎么看训练结束后在runs/train/exp*/目录下执行验证或者用官方val.py对指定权重做评估python val.py --data helmet.yaml --weights runs/train/exp/weights/best.pt --img 640它会输出P精确率、R召回率和mAP0.5、mAP0.5:0.95这几个核心指标。我最终版本的指标如下指标数值说明Precision精确率0.923所有预测为目标框里真正是目标的比例Recall召回率0.905所有真实目标里被成功检测到的比例mAP0.50.937当IOU阈值为0.5时的平均精度均值mAP0.5:0.950.711当IOU阈值从0.5到0.95的平均精度均值对于头盔检测业务来说mAP0.5比mAP0.5:0.95更有参考意义因为实际业务判定时通常用IOU阈值0.5来和真实框做匹配。更高阈值的mAP主要影响的是框的精准度如果框偏大偏小但能框住头部业务上已经可以接受。5.2 实际优化手段提升小目标检测效果如果你的验证mAP尚可但实际部署时发现头盔在画面里太小检测不出来有两个非常有效的优化手段方案一是用测试时增强TTA推理。YOLOv5的detect.py带了--augment参数推理时把原图、翻转图和缩放图都跑一遍模型最后融合输出结果能显著提升小目标召回率代价是推理时间增加3到5倍。这在服务器GPU推理场景可以接受但如果要做实时视频流不太划算。方案二是提高输入分辨率并用切片推理。把原图切成1280×1280的网格每个网格分别送入模型推理再把结果合并回原图坐标。这种方法能获得接近大分辨率输入的效果显存占用却只取决于单次推理的切片大小。在代码里可以用sahi这个库直接实现切片推理替代自己手写繁琐的坐标映射逻辑。6. 模型部署与业务集成6.1 导出ONNX并进行本地推理模型训练完成后需要导出成生产环境可用的格式。YOLOv5官方提供了export.py下面命令将其导出为ONNX格式ONNX是跨平台AI模型格式标准可方便地转换成TensorRT等部署加速格式python export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 12在本地用ONNX Runtime做推理的代码框架如下import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx) input_name session.get_inputs()[0].name input_shape session.get_inputs()[0].shape # [1, 3, 640, 640] def preprocess(image, input_size640): h, w image.shape[:2] ratio min(input_size / h, input_size / w) new_w, new_h int(w * ratio), int(h * ratio) resized cv2.resize(image, (new_w, new_h)) canvas np.full((input_size, input_size, 3), 114, dtypenp.uint8) canvas[:new_h, :new_w] resized # BGR转RGB、归一化、CHW blob canvas[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 return np.expand_dims(blob, 0), ratio, (new_w, new_h) def postprocess(outputs, ratio, new_shape, conf_thres0.25, iou_thres0.45): # outputs shape: [1, 25200, 8] (cx, cy, w, h, obj_conf, cls1_conf, cls2_conf, cls3_conf) # 经典NMS逻辑这里省略细节实际可以用ultralytics的utils.general.non_max_suppression pass这里需要留意一个非常容易踩的坑推理输入输出尺寸和坐标缩放。YOLOv5输入的是letterbox后的图像预测出的框坐标是归一化到640×640画布上的坐标需要除以缩放比例ratio并减去padding偏移量才能映射回原始图像上的坐标。如果映射逻辑出错你会看到模型能检测出目标但框的位置完全错乱。6.2 构建实时检测API服务实际业务中摄像头视频流一般通过RTSP等协议接入然后由检测服务逐帧分析。如果直接推流用Python逐帧解码处理会有性能瓶颈更好的方案是用FFmpeg拉流抽帧再交给GPU推理。我用FastAPI搭建了一个简单的检测服务接收图片URL或base64编码返回检测框。下面是一个核心框架from fastapi import FastAPI, UploadFile import asyncio import numpy as np import cv2 app FastAPI() app.post(/detect) async def detect(file: UploadFile): contents await file.read() img cv2.imdecode(np.frombuffer(contents, np.uint8), cv2.IMREAD_COLOR) # 将img送入前面写的preprocess和onnx推理 # 返回每个检测框的类别、置信度、坐标 return {detections: [...]}线上部署时有两点要在代码里处理好第一小目标优化后的后处理逻辑。当多个检测框重叠时NMS阈值需要结合业务场景调整。头盔检测场景里如果一个人只露出头模型可能预测出两个几乎重合的框一个helmet_head一个person头部区域需要按类别做NMS优先级处理保留置信度最高的那类。第二并发与显存管理。GPU显存是有限的如果API服务同时来了多路视频流的检测请求需要用asyncio.Semaphore控制并发数避免显存溢出或者推理任务排队时间过长。我遇到过一个奇怪的现象单路视频流时延迟只有50ms但并发请求一多延迟直接到500ms。后来排查发现是显存碎片化导致的用TensorRT的set_memory_pool_limit显式限制显存池大小并做了显存预热后并发情况下的延迟稳定下来了。7. 常见问题与排查技巧实录7.1 训练阶段高频问题对照表问题原因分析解决方法GPU显存不足CUDA out of memorybatch过大或输入分辨率过高减小batch到4或8或使用--img 512训练loss无法下降数据标注格式错误或类别编号不对打开TXT标注文件逐个核对类别id和坐标值验证mAP非常低低于0.5训练集和验证集数据分布不一致重新划分数据集按监控点位分组训练过程中loss突然变成nan学习率过大或数据中有异常像素值调低--lr0到0.001检查图片是否有损坏文件训练很慢很慢数据加载是瓶颈GPU利用率低逐渐升高--workers增加--cache参数关于显存不足有一个非常实用的技巧使用yolov5n作为起步模型只训练少数epoch来验证数据管线是否正常确认无误后再切换到yolov5s跑正式训练。这样能避免在数据问题还没解决的情况下白白跑掉一整晚时间。7.2 推理阶段的经典坑在推理部署阶段我遇到的几个比较典型的问题问题一NMS阈值设置不当导致误检飙升。检测输出会有大量低置信度的框如果直接用原始输出一张画面可能产生几百个重叠框。需要NMS处理但NMS的IOU阈值设置不能太大。我测试过IOU阈值从0.45调整到0.7时车辆密集路段的误检率上升了约三倍。原因是两个相邻头盔框之间IOU可能大于0.45如果阈值设得太大两个“疑似头盔”都保留下来一个是对的另一个是误检出来的需要靠低置信度过滤。问题二类别阈值和置信度阈值不区分。YOLOv5的每个框输出有objectness是否含目标的置信度和class score属于哪个类别的置信度。在训练好的相对干净的数据集上我建议设置conf_thres0.25iou_thres0.45如果发现漏检多优先降低conf_thres而不是iou_thres。问题三摄像头角度和模型训练数据差异过大。最容易踩的坑是你的摄像头俯视角度很大而训练集主要是平视照片。我建议在模型上线后先拿一段新场景视频做“影子测试”即只记录检测结果但不对业务产生实际影响持续跑2到3天观察漏检和误检的规律再去定向补充样本。7.3 数据集过少时的应急方案如果你的样本非常有限比如只有两三百张我实测有效的方法是用公开的大规模头盔数据集训练一个通用模型然后在自己小数据集上只冻结主干backbone微调检测头head。具体可以通过修改train.py里的freeze参数实现python train.py --data helmet.yaml --weights yolov5s.pt --freeze 10 --epochs 100freeze 10表示冻结前10个层的参数只训练后面层的参数。这样即使样本少也不容易过拟合因为骨干部分已经在大量通用数据上学习到了足够好的基础特征。从零训练和迁移学习的区别在这里体现得比较明显。迁移学习时模型在一个更大数据集COCO上已经学会了识别视觉模式的基础能力相当于一个具备“图像理解能力”的新手学生只需要在你给他的特定考题上多练习而从头训练相当于让这个学生从零开始读书学习需要的样本量自然要大得多。8. 扩展方向如何继续提升精度与效率头盔检测这个项目在模型精度上已经做到了业务可用的水平但实际落地过程中你会发现有很多扩展的方向值得继续投入。首先是将模型剪枝量化再部署到边缘设备上。电动自行车头盔检测的实际应用场景往往不在服务器机房而是路口的边缘计算盒子或者高通、瑞芯微等嵌入式平台。YOLOv5官方的剪枝工具可以对模型进行通道剪枝减小模型体积的50%以上推理速度提升明显的。然后再进行INT8量化在几乎不影响精度的情况下让模型在嵌入式设备上的推理速度有质的飞跃。其次是结合跟踪算法实现连续检测而不是单帧检测。单帧检测容易因为遮挡、运动模糊等问题出现漏检如果结合ByteTrack或者DeepSORT目标跟踪算法锁定每个目标然后取连续几帧的检测结果做投票可以有效降低单帧误检漏检的影响。这个思路在路测时表现特别好骑车人停在路口时的多角度连续帧能提供更稳定的判断依据。最后是引入更丰富的业务维度。当前模型检测到的是戴头盔的头部和不戴头盔的头部如果你还想统计电动车的车牌号、识别骑行人的性别年龄段那可以在这个检测模型之上再接OCR模型或者分类模型。目标检测模型在整体系统中扮演的是“框出目标区域”的第一步后续处理可以精细化到特征级别。这也是工程落地的常规思路没必要试图让一个模型干所有事。如果你正在做类似的项目我建议你从自己的实际场景出发先把数据管线跑通用小模型验证效果再逐步迭代。模型训练这件事方向比努力更重要一份质量过硬的数据集比任何花哨的网络结构都能带给你更大的精度收益。本文还有配套的精品资源点击获取
返回列表