ARTICLE DETAIL

资讯详情

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

YOLOv5电动车头盔检测实战:3类别数据集构建与训练避坑指南

YOLOv5电动车头盔检测实战:3类别数据集构建与训练避坑指南 简介面向道路电动车头盔目标检测任务这份YOLOv5格式数据集涵盖佩戴头盔、未佩戴头盔及整体标注三个类别图像均为1920×1080 RGB实景照片并已按训练集3646张图与对应txt标签和验证集911张图与对应txt标签划分完成无需额外标注或格式转换可直接投入YOLOv5模型训练。包体内共2000个文件主体为1999个txt标签文件另有1个Python可视化脚本整体压缩包约502MB脚本无需修改随机传入一张图片即可绘制边界框并保存到当前目录方便快速检查标注结果同时txt类别文件也明确了三个类别信息。已有955人浏览学习适合目标检测初学者、电动车安全治理项目开发者以及需要快速部署头盔检测方案的工程师参考也可用于模型评估、调优与演示。1. 为什么电动车头盔检测要先过数据集这一关做路口电动车头盔检测的实战项目卡住大多数人的不是模型选型而是手头根本没有能直接喂给YOLOv5的现成数据。网上散落的数据集要么类别定义含糊要么标注格式混乱要么场景单一到模型一换路段就翻车。所谓道路上电动车是否佩戴头盔目标检测数据集3类别核心就是解决三件事类别怎么划分才算合理、样本怎么采集才覆盖真实道路、标注怎么组织才能直接跑通YOLOv5训练流程。这篇笔记按我实际做过的方案来拆解适合刚接触目标检测的学生、做交管边缘设备的工程师以及准备用YOLOv5训练自定义数据的开发者。我会把数据集结构、标注工具链、训练参数和踩坑记录完整过一遍。2. 3类别数据集的设计逻辑类别边界清晰比数量更重要2.1 为什么是3类别而不是2类别或5类别很多初做头盔检测的人上来就设头盔和无头盔两个类别结果模型在真实道路上表现极差。原因在于背景里的行人、自行车骑手、摩托车骑手都没有被显式建模模型被迫把非头盔这个语义塞进一个脆弱的二分类边界里。3类别的常见做法是把目标区分为佩戴头盔的电动车骑手helmet、未佩戴头盔的电动车骑手no_helmet、以及普通行人或非机动车骑手person。第三个类别起到背景消解的作用让模型不再把没戴头盔的行人误判成违规骑手。这个设计和COCO数据集的person类别思路一致但针对电动车场景做了裁剪——类别少了训练难度下降误报率也明显改善。实际标注时还有一个隐藏收益3类别让模型天然学会了骑手-车辆的空间关系。因为电动车和骑手通常挨得很近检测框重叠度高模型会隐式学到头盔框应该出现在车辆框的上方区域。这个信息对后续做违规判定逻辑比如联动车牌识别非常有用。如果你把类别扩到5类比如区分共享电动车和私人电动车不仅标注工作量成倍增长小类别样本不足还会导致YOLOv5的mAP波动剧烈得不偿失。2.2 目录结构与标注文件VOC还是YOLO格式YOLOv5官方代码库默认消费YOLO格式的txt标注每一行是class x_center y_center width height坐标值都归一化到0到1之间。虽然用VOC格式的xml也能通过脚本转换但我强烈建议标注时直接输出YOLO格式省掉一次转换的出错机会。标准的目录结构长这样helmet_dataset/ ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ └── ... │ └── val/ │ └── ... └── labels/ ├── train/ │ ├── img_001.txt │ └── ... └── val/ └── ...每个txt文件和对应jpg文件同名不存在对应标注的图片要么删除要么移入ignore目录。YOLOv5的dataset loader会严格检查同名文件漏一个都会在训练时报Image without labels警告累积多了会静默拉低召回率。标注文件内容示例0 0.4321 0.5678 0.1234 0.2345 1 0.6789 0.3456 0.0987 0.1876 2 0.1122 0.4433 0.0765 0.1534这里class id 0代表helmet1代表no_helmet2代表person。每行五个数值的含义依次是类别序号、归一化后的框中心x坐标、框中心y坐标、归一化后的框宽、归一化后的框高。全部数值用空格分隔不要用逗号。我在转换脚本里吃过亏labelImg导出时偶尔会生成多余的空行训练时会触发YOLOv5的坐标越界警告之后我在pipeline里统一加了空行过滤。2.3 类别分布怎么配比才不拖后腿一个常见的错误是每个类别平均分配样本量。真实道路场景中戴头盔的骑手往往远多于不戴头盔的如果强行11等于在原始分布上造假模型学到的是虚拟的先验概率。我的经验是让训练集呈现接近真实路口的分布helmet占50%左右no_helmet占30%person占20%。如果某一个类别死活凑不够数量优先做数据增强而不是复制粘贴同一张图。复制图片会导致验证集和训练集出现完全相同的标注框mAP虚高但部署时被打回原形。验证集和训练集应保证场景不重叠——比如训练集用A路口的白天视频抽帧验证集就用B路口的傍晚视频抽帧这是防止数据集泄漏最有效的办法。3. 从零构建头盔检测数据集视频抽帧、自动清洗与标注实操3.1 视频采样策略每隔几帧抽一张才有效直接截图或连续抽帧会产生大量高度相似的图片模型看到的是同一辆电动车在不同帧的微小位移本质上是在背题。常用做法是按时间间隔抽帧码率和场景节奏决定间隔大小。路口场景车辆通过速度不快我一般每秒取2帧即间隔15帧抽一帧。高速路段每秒取1帧就够。抽帧后的图片要做一次去重用简单的感知哈希比对删除相似度大于0.95的图这一步能减少20%到30%的冗余样本。import cv2 import os from PIL import Image import imagehash video_path road_1.mp4 output_dir raw_frames os.makedirs(output_dir, exist_okTrue) cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) frame_interval int(fps / 2) # 每秒抽2帧 frame_idx 0 save_idx 0 hash_set set() while True: ret, frame cap.read() if not ret: break if frame_idx % frame_interval 0: pil_img Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) h str(imagehash.phash(pil_img)) if h not in hash_set: hash_set.add(h) cv2.imwrite(os.path.join(output_dir, fframe_{save_idx:06d}.jpg), frame) save_idx 1 frame_idx 1 cap.release()这段脚本里最关键的是frame_interval的计算。直接用固定每隔10帧在不同帧率视频上会导致抽帧密度不一致比如25fps和60fps的视频同样的帧间隔抽出来的是完全不同的时间密度。所以先用CAP_PROP_FPS拿到真实帧率再换算成时间间隔。感知哈希去重的作用是挡住静止画面——红绿灯期间车辆静止连续多帧几乎一样这些帧留着只会增加标注成本且不贡献新信息。imagehash库的phash算法对轻微压缩和亮度变化不敏感适合视频帧场景。3.2 标注工具与3类别的标签规范标注工具首推labelImg它原生支持YOLO格式不需要额外转换脚本。在conda环境里安装运行conda activate labelimg_env pip install labelImg pyqt5 labelImg images/ labels/ --labels helmet,no_helmet,person启动后的操作逻辑很简单用W键切换到画框模式左键拖出目标框在弹出的对话框里选择类别。三个类别的画框规范要提前定死否则不同标注员或不同时间段标注出来的框风格不一致。我的规则是helmet和no_helmet的框从骑手头部上方2像素开始到下巴位置为止不包含肩膀person的框是全身框从头到脚。头盔框如果框得太小包含大量背景噪点训练后模型预测的框会偏小且置信度低框得太大则会和person框高度重叠导致后处理NMS时被抑制。标注过程中最容易出的问题是漏标。一帧图里有3个骑手你却只标了2个模型训练时会把没标的那一个当成背景学习这就是漏标即负样本陷阱。对策是每张图标注完成后做一次人工复核重点关注远处的小目标。头盔检测的漏标高发区在图片上下边缘——摄像头俯拍角度下远处的骑手被压得很小标注员容易忽略。这部分小目标恰恰是交警场景最需要的后续可以针对性做切片增强。3.3 标注质量校验三步把坏标注找出来标注完成后不能直接训练需要先跑校验脚本。最常见的问题有三种坐标越界、框面积异常、类别错标。坐标越界通常是labelImg拖动时超出图片边缘导致YOLOv5在训练时会把越界框裁剪掉但裁剪后框的中心点和原标注对不上。框面积异常指宽度或高度小于图片尺寸的0.5%或大于50%这类框要么是误操作要么是把整个画面框了进去。import os import numpy as np label_dir labels/train img_dir images/train img_w, img_h 1920, 1080 # 按实际图片尺寸修改 for txt_name in os.listdir(label_dir): if not txt_name.endswith(.txt): continue txt_path os.path.join(label_dir, txt_name) img_name txt_name.replace(.txt, .jpg) img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): print(fMissing image: {img_path}) continue with open(txt_path) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: print(fBad line in {txt_path}: {line}) continue cls int(parts[0]) x_c, y_c, w, h map(float, parts[1:]) if not (0 x_c 1 and 0 y_c 1): print(fCenter out of range in {txt_path}: {x_c}, {y_c}) if w * img_w 5 or h * img_h 5: print(fToo small box in {txt_path}: {w * img_w} x {h * img_h})校验脚本的重点在于宁可误报也不漏报。我习惯把输出直接重定向到log文件逐条人工决定是删除还是修正。坐标越界的框直接删掉往往比修正更快因为一张图里越界框通常是少数个删掉不影响训练。框面积异常的框大概率是标注时手滑打开对应图片重新拉一个框。类别错标则需要在训练第一轮后看混淆矩阵才能发现这也解释了为什么第一轮训练永远是基线和排错的用途而不是直接产生产物。4. YOLOv5训练配置数据yaml、超参数与命令行参数的精调4.1 data.yaml怎么写才能一次通过YOLOv5的数据配置走一个data.yaml文件里面定义路径、类别数和类别名。我给出一个经过验证的写法并标注哪些字段不能省# helmet.yaml train: /home/user/helmet_dataset/images/train val: /home/user/helmet_dataset/images/val nc: 3 names: [helmet, no_helmet, person]train和val的路径指向images目录即可YOLOv5会自动把images替换为labels来寻找标注文件。这一点经常被误解写成了labels路径训练直接报错找不到图片。nc必须和names列表长度一致多一个或少一个都会在后处理阶段出维度错误。names顺序必须和标注文件里的class id一一对应我在第一次多类别训练时就把helmet写在了第二个位置结果类别全程对调推理结果完全没法看。验证集的路径可以是指向一个单独的目录也可以是train目录里划出的一部分我建议独立目录便于后续做交叉验证。4.2 训练命令与三个必调超参数YOLOv5训练的命令行参数众多但真正对头盔检测效果影响最大的是--img、--batch和--hyp。数据集原始分辨率常见1080p如果显存不够先缩到640训练否则直接用960。头盔是典型的小目标输入分辨率每下降一个量级小目标召回率就肉眼可见地掉。batch的选择不只看显存还要看类别均衡程度如果你的no_helmet样本本来就少batch设太大会导致每个batch里无头盔样本梯度方向不稳定。python train.py \ --data helmet.yaml \ --weights yolov5s.pt \ --img 960 \ --batch 16 \ --epochs 150 \ --hyp data/hyps/hyp.scratch-low.yaml \ --cache ram \ --device 0hyp.scratch-low.yaml是官方针对中小数据集调过的超参数文件相比默认的hyp.scratch-med.yaml它的mosaic概率设得更低。如果你的数据集只有几千张mosaic过强会导致大量拼接后的图片里目标被切割变形反而掩盖了原始分布。--cache ram会把图片预加载到内存训练速度能快30%以上前提是内存够大——每张960分辨率的jpg大约占1.5MB内存一万张图就需要约15GB。显存低于6GB的机器建议把--img降到640batch降到8保证训练过程不因为OOM中断。4.3 mosaic、mixup与自动优化哪些增强该开哪些该关YOLOv5默认开启的mosaic增强对头盔检测有利有弊。利在于四张图拼接后模型被迫在更小的目标尺度上学习对远处骑手的检测能力提升明显弊在于拼接缝处会出现虚假的边界如果标注框恰好跨越拼接边界训练时会报边界错误并自动丢弃该框。我的做法是第一轮训练baseline关闭mosaic让模型先学会基础特征第二轮微调时再打开mosaic把mosaic概率设为0.5而不是默认的1.0。具体改法是在hyp文件里找到mosaic字段直接修改数值。mixup增强对头盔检测不建议开。头盔和no_helmet在视觉上本来就是强语义目标mixup会让两个类别的特征叠加产生中间形态模型学到的类别边界更加模糊。相比之下只做色彩抖动和随机平移旋转就够。图像增强的目的是增加模型的泛化性而不是制造训练难度。数据集足够丰富时过度增强反而拉低验证集精度因为验证集是真实场景分布增强后的训练分布和真实分布偏移太大。这个度需要用小规模的消融实验来确定我通常会固定其他参数只切换mosaic开关每个配置跑50个epoch看val精度再决定最终方案。5. 头盔检测训练避坑指南五类高频翻车现场与排查记录5.1 类别漏检模型把远处的无头盔骑手当成了背景现象是验证集上no_helmet的召回率显著低于helmet逐张看图发现远处目标的头部只有十几个像素。原因是标注时漏标了大量远距离目标模型从没在训练时见过这么小的无头盔样本。解决分两步第一步用原始的1080p视频重新对小目标密集的场景做补充标注第二步把--img从640提升到960让模型在特征图上多保留几个像素的响应。如果显存有限制就采用SAHI切片推理作为后处理手段但那是部署阶段的事训练阶段直接提分辨率最省事。5.2 训练loss下降到平台期换个初始权重比死磕超参数更快现象是训练到第60个epoch后box_loss和cls_loss都停在某个数值不再下降验证集mAP也纹丝不动。原因大概率是随机初始化的卷积核陷入局部最优而不是超参数设置问题。解决方法是把--weights从yolov5s.pt换成yolov5m.pt用更深更大的backbone重新训练50个epoch看loss是否继续下降。另一个有效手段是直接改用YOLOv5的预训练权重做微调只冻结backbone的前10层让后面的层在头盔数据集上重新适应。从我的实测看预训练权重微调比从头训练节省约40%的epoch数最终mAP还高2到3个点。5.3 夜间和逆光场景大规模失效现象是白天场景mAP有0.85换到夜间场景直接掉到0.4以下。原因是训练集里夜间样本占比太少只占全部图片的5%左右。解决思路是数据层面的不是模型层面的补充夜间红外或低照度监控画面如果实在没有用图像增强算法模拟夜间亮度分布——但我必须说清楚模拟出来的夜视图和真实监控的噪声模式差距很大只能作为应急手段。更可靠的做法是在标注阶段就把夜间样本单独建一个子目录训练时按比例混合保证每个batch都包含一定数量的夜间图防止模型只在白天特征上过拟合。5.4 验证集mAP虚高但实际视频检测效果差现象是训练曲线漂亮用验证集图片测试也正常但一跑真实路口视频就大量误报。大部分情况是数据集场景单一造成训练集和验证集都来自同一个路口的相似时段模型根本没有见过其他角度的骑手。解决是在采集阶段刻意覆盖至少3个不同路口、2个时段白天和傍晚、两种天气。如果项目周期紧至少保证训练集和验证集来自不同路口模型才会学到头盔的通用形状而不是某个路口的头盔样子。出现虚高时先审视数据来源而不是急着调NMS阈值。5.5 person类别拉低了整体mAP现象是helmet和no_helmet的AP都超过0.9但person的AP只有0.6拖累整体mAP掉到0.75。原因是person类别和no_helmet在视觉上高度相似两者的边界在标注时就没有清晰划分。我在复核时发现有的标注员把站在路边打电话的行人归为person有的归为no_helmet同一张图在不同时期的标注标准都不一样。解决方法是重新统一标注规范person只标不在电动车上的人而no_helmet必须坐在电动车上。定义清楚之后把标注文件里违反规则的样本批量替换类别id重新训练一轮mAP立刻回升。6. 训练完别急着部署先跑验证脚本、再查混淆矩阵、最后做一次类别均衡测试模型训练完的验收不能只看一个总的mAP。用官方val.py跑出每个类别的AP逐个审视哪一类是短板python val.py \ --data helmet.yaml \ --weights runs/train/exp/weights/best.pt \ --img 960 \ --conf-thres 0.25 \ --iou-thres 0.45重点关注confusion matrix输出。正常的头盔检测模型helmet和no_helmet之间会有少量混淆但如果no_helmet大量被识别成person或者背景说明类别定义或训练数据还有偏差。验证时用--conf-thres 0.25这只是基准线实际部署要根据场景调整如果违规抓拍宁可误报率高一点也不能漏报阈值降到0.15如果做统计报表要求精确率优先阈值提到0.4。这一步没有唯一正确答案只能根据现场反馈来回调。类别均衡测试是很多团队跳过但值得做的环节。拿一段标注好的视频统计其中三类目标的数量分别计算模型检测出的数量看比例是否与标注比例接近。如果no_helmet标注了100个但模型只检出50个说明模型对稀有类别仍然欠拟合需要针对性补充数据而非继续调参。这个测试耗时半小时但能避免把明显偏科的模型带到现场省下返工的精力。从做目标检测项目到现在我最大的教训是数据集比模型更值得投入时间YOLOv5的训练框架已经足够稳大部分效果差异其实来自标注质量和场景覆盖度。希望这篇笔记能帮你少走弯路把头盔检测数据集这条路走通。本文还有配套的精品资源点击获取
返回列表