
干过智慧交通安防这一行的人应该都有过被甲方甩来一套标注好的头盔检测数据的经历几千张图格式五花八门类别名一会儿叫hat一会儿叫helmet跑出来的模型不是漏检严重就是把工地的安全帽和骑手的头盔混在一起。最近我自己整理并训练了一套8300张的YOLO头盔检测数据集专门用在路口和工地的骑手监管场景。整个过程从数据清洗、标注规范到YOLO训练调参踩了不少坑这篇就一次性讲清楚。这套数据集的定位是“给YOLO系列模型做训练用的智慧交通场景数据”而不是单纯把图片堆在一起。数据覆盖白天、夜晚、逆光、雨天、遮挡等路口常见情况标注格式统一为YOLO txt类别只保留两个头盔helmet和未佩戴头盔的人头head。8300张图一共标注了接近18000个有效目标框按8:1:1切成训练集、验证集和测试集。适合刚接触YOLO目标检测的工程师拿来练手也适合做智慧交通监控项目的团队作为预训练底料。1. 项目背景与方案选型1.1 为什么一定要做一个专门的头盔检测数据集路口抓拍摩托车、电动车骑手是否佩戴头盔是智慧交通里高频需求。很多甲方一开始觉得“直接拿开源通用检测模型跑就行”实际落地后会发现通用模型在头盔这个细分类上非常不稳定。普通安全帽、渔夫帽、大衣帽衫都可能误判成头盔而真正的头盔在侧光、夜晚、低分辨率下又容易被漏掉。原因很简单通用数据集里的头盔样本太少标注也没按交管需求去设计。所以这个项目从一开始就确定两件事。第一类别定义必须贴近业务只有“helmet”和“head”两个类别不做多余的车、人、路牌干扰项。第二样本分布必须覆盖真实摄像头会拍到的角度和光线。单纯去网上爬几千张图不做时段和场景分层模型在白天测试集上可能表现不错一到傍晚就崩。这数据集的整体设计不是追求“大”而是追求在固定业务场景下的“准”。1.2 为什么选YOLO而不是Faster R-CNN或Transformer类模型这个项目的底层需求是实时监控路口摄像头通常是一路视频流需要模型在边缘设备上跑。Faster R-CNN精度高但推理速度达不到路数并发要求Transformer类检测器像DETR、RT-DETR精度和泛化能力确实不错但工程链路成熟度不如YOLO生态。YOLO从v5到v8训练、导出、量化、部署都有非常成熟的工具链遇到问题网上能查到的方案也多对做实际项目的团队来说这比“理论上更好”重要得多。实际操作中我选用YOLOv8作为主模型后面也用YOLOv5做了对比。YOLOv8在同样算力下比v5推理速度略慢一点点但检测头解耦后收敛更稳定尤其是小目标漏检问题比v5改善明显。项目时间紧的话YOLOv8ssmall在RTX 3090上一张640x640图片大概1-2毫秒完全够用追求更高精度再换成YOLOv8m或x。关键是YOLO系列预训练模型权重下载方便用COCO预训练权重做迁移学习比自己从零训练省太多时间。1.3 数据集规模与组成结构8300张图听上去不多但检测任务里数据质量比数量重要。我最终保留的训练图像是8026张验证集和测试集分别800张和474张。这8300张的来源有三个部分一部分是开源的摩托车骑行场景图片一部分是自己在城市路口用运动相机和手机拍摄的短视频抽帧还有一部分是从工地施工监控公开片段里截取的负样本。特别注意负样本非常关键因为“没有头盔的路人”和“戴帽子的人”同样需要让模型认识。标注框总数约17920个其中头盔类别约占48%未佩戴头盔的人头类别约占40%剩余12%是遮挡严重但可判别的目标。类别比例不是按真实场景做的均匀分布而是有意把“head”类稍微压低一点让模型不过度偏向输出head。数据增强会在训练阶段把两类比例再做平衡这个后面细讲。2. 数据清洗、标注规范与训练集构建2.1 数据清洗的第一原则不要盲目去重很多人拿到图片第一反应是“图越多越好”。其实头盔检测数据集最怕的是同一场景连续帧重复标注比如一段视频每秒抽一帧抽了60张里面其实是同一个人同一辆车。这种数据会让模型在测试集上显得精度很高但一到真实路口就被打回原形因为真实镜头里人一直在动。我的做法是用感知哈希pHash做去重相似度超过0.9的只留一张。对视频抽帧还要做帧间隔控制每2秒最多保留1帧。当然完全去重也不对同一个骑手在进入画面、驶近、驶离三个位置可以保留这能模拟不同尺度下的检测难度。去重之后手动扫一遍把带严重运动模糊、镜头脏污、目标占比小于1%的图片剔除最后的干净集就是那8026张训练图。2.2 YOLO标注格式与框选规则YOLO标注格式是归一化的txt文件一行一个目标class_id center_x center_y width heightclass_id从0开始center_x、center_y、width、height都是0到1的归一化值。很多标注工具能自动导出这个格式但手工框选的规则会直接影响模型效果。我踩过一个坑标注头盔时喜欢把框拉得比头盔大一点把头发和肩膀头都包进去。这会导致模型学到一堆背景纹理推理时把脖子区域也当成头盔的一部分误检直接增加。正确的框选规则是框必须紧贴目标可见外轮廓头盔和头的边界最多留出1到2个像素的余量。头盔如果受到树枝、雨棚遮挡只要可见面积超过60%仍然按完整头盔标注低于60%的丢弃。对于后面戴着头盔、前面没戴的骑手两个类别都要单独标出来不能为了省事只标前面的。这一点直接决定模型能不能学到“后座乘客是否佩戴”这种交管经常要查的场景。标注工具我推荐LabelImg轻量或X-anyLabeling支持半自动预标注。几千张图如果全靠人工框至少要两到三周用YOLOv8s先跑一遍伪标注再人工修框效率能提升3倍以上。但伪标注的框一定要重新检查模型漏掉的硬样本正是你需要重点补标的内容。2.3 训练集划分怎么避免数据泄露数据划分不能简单随机切否则同一个路口、同一时段、同一批行人的图片同时出现在训练集和测试集里评估结果虚高。我对数据集按“场景来源分组”来自同一段视频或同一次拍摄的图片全部分到同一个集合不跨组切分。这样测试集的样本和训练集存在场景差异更接近真实路口换摄像头的挑战。最终划分结果训练集390组、验证集47组、测试集25组。验证集和测试集特意保留了大量夜晚和逆光样本因为白天样本好识别模型能不能扛住恶劣光线才是项目成败的关键。划分完顺手做了类别分布检查确保三个集合里helmet和head的比例都接近1:1.2避免出现训练集全是头盔、测试集全是人头的极端情况。3. YOLO训练配置与调参细节3.1 环境准备与目录结构训练环境直接决定你调参的效率。我这边是Ubuntu 20.04系统一张RTX 3090 24GB显卡CUDA 11.8PyTorch 2.0。如果显存不够用YOLOv8n或者YOLOv5sbatchsize降到16也能跑只是收敛会慢。数据集目录结构按照YOLO官方约定放helmet_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── helmet.yaml图片和标注文件必须同名不同后缀这是YOLO系列默认设计。我见过很多新手把labels放在images子目录下训练时会直接报“label not found”排查半天发现是目录层级问题。如果你用的是Ultralytics YOLOv8标注文件后缀一律是.txt类别顺序与yaml里的names严格对应错一个数字模型训练时完全没有报错但mAP会莫名其妙掉二十个点。3.2 数据配置文件YAML怎么填写YAML配置看似简单却最容易埋坑。以下是我实际使用的配置path: /data/helmet_dataset train: images/train val: images/val test: images/test nc: 2 names: 0: helmet 1: head需要注意path要填绝对路径train/val是指向images子目录的相对路径。如果填相对路径训练时换一台机器很容易找不到文件。另外names的索引从0开始我在标注时把头盔记为0未佩戴的人头记为1两个类别调换后必须同步修改标注txt里的class_id。最坑的是很多人从网上下载数据集图片和标签文件混在一起yaml里train写的是data/helmet/结果训练后所有类别都识别成背景。所以配好yaml后先用一行代码验证标签格式python -c from ultralytics.data.utils import check_det_dataset; check_det_dataset(helmet.yaml)它会检查图片数量、标签数量、类别名是否合法比盲跑训练靠谱多了。3.3 训练命令、预训练权重与超参数选择YOLOv8的默认训练命令非常简单yolo detect train datahelmet.yaml modelyolov8s.pt epochs120 imgsz640 batch16 patience15 device0modelyolov8s.pt会下载COCO预训练权重这一步非常关键。千万不要用随机初始化权重从头训练头盔检测任务在图形特征上和你COCO里的“人”、“帽子”等类别高度相关迁移学习能让你在30个epoch内就达到接近70个epoch从零训练的效果。超参数方面训练初期我采用较多数据增强尽量让模型见更多的表面纹理变化。当前Ultralytics默认已经打开了mosaic、mixup、hsv变换等增强但如果你的数据集里小目标很多mosaic拼接出来的小头盔会被裁掉一半导致模型学不到完整形状。所以我训练到第70个epoch时手动关闭了mosaic增强只保留hsv和轻微的平移旋转让模型在小目标上做最后一阶段的精修。注意batch不要只盯着显卡能不能放得下。我的经验是batch达到16以上时BN层统计才比较稳定如果显存不够可以降imgsz而不是降batch因为imgsz从640降到480能省近一半显存小目标精度下降也能通过训练后期回切到640缓解。3.4 损失函数与训练曲线的观察方法YOLOv8的损失由三部分组成box回归损失CIoU、分类损失BCE、DFL分布损失。训练时不要只盯着total loss要把box_loss和cls_loss分开看。我这次训练中一个典型的曲线变化是前20个epoch box_loss快速下降cls_loss缓慢下降到第60个epoch后cls_loss出现小幅震荡通常是mosaic增强生成的拼接图里类别边界混乱导致的。这时候关闭mosaic让模型在一个epoch里看到更多真实完整的样本loss会继续稳定下降。还有一个经验如果训练集的loss已经降得很低但验证集的box_loss开始反弹说明模型开始在训练数据上背场景了尤其是背景里的道路标记、护栏等纹理。这时优先做的事情不是盲目加数据而是把当前训练状态保存减少后续epoch或加大随机遮挡增强让模型只能依靠头盔本身的轮廓做判断.3.5 混淆矩阵为什么总和不为1很多新手看验证集混淆矩阵发现每一列加起来不是100%以为模型预测出了问题。其实混淆矩阵的每一格显示的是“所有包含该类别目标的正样本中被预测为各类别包括背景的比例”。背景那一列没有框住任何标签所以它不是百分比而是绝对数值或者不显示。我的头盔混淆矩阵里helmet行中约92%预测正确6%落到了head2%落到了背景head行中约89%预测正确8%落到了helmet3%落到了背景。真正需要动手处理的是helmet和head互相混淆的那部分因为这两种状态的交管含义完全相反一个判断错就是误报。4. 常见问题与排查技巧实录4.1 漏检远处小目标和夜间画面怎么办漏检是头盔检测项目最头疼的问题。远距离骑手在640x640输入下可能只有20x20像素而头盔又没有很强的纹理特征模型稍不注意就把它跳过了。我的第一版模型在真实路口测试时mAP0.5有0.93但15米外的骑手漏检率接近20%甲方直接说这是“瞎了”。解决思路有三步。第一训练时用多尺度训练将imgsz设为640同时在rectTrue模式下混合384、512、640尺寸的图像让模型见过不同尺度下的头盔第二推理时做测试时增强TTA把图片放大1.2倍再检测精度能提升2到3个点但会牺牲一半帧率第三在真实项目里如果摄像头焦距固定可以直接对远地区域做一个ROI裁剪把子图放大后传入模型这样比全图TTA更高效而且不损失近景检测速度。夜晚漏检的原因主要是亮度低和偏色头盔的轮廓与背景混在一起。我的数据集中夜晚图片只占约18%后来专门补充了一批带路灯和车灯眩光的路口视频帧并增加了RGB通道的随机扰动把夜间漏检率降低了将近一半。如果你手头也有类似数据集记住黑暗场景不要强行调亮再训练正常喂原图让模型自己适应真实的低照度分布效果更稳定。4.2 误检安全帽、渔夫帽与头盔怎么区分工地上戴的黄色安全帽和摩托车头盔在颜色和拱形外观上确实很像模型很容易把它们都识别成helmet。真实智慧交通项目里如果在非机动车道拍到工地工人误报也很多。这个问题没有捷径核心就是增加“安全帽但没戴摩托车头盔”的负样本并且特意在data里面保留一个“other hat”背景段来训练模型区分纹理细节。我在标注时没有给安全帽单独标签因为业务上不需要但它需要作为困难负样本出现在图片里。如果模型把安全帽识别成头盔这并不是标注问题而是决策边界问题。调整方法有三个一是把“helmet”和“head”两个类的损失权重稍微提高变相让模型更谨慎二是推理时把置信度阈值从0.25提高到0.45误检会下降但漏检也会上升需要根据场景权衡三是给分类头加一个针对头盔细纹理的注意力模块这个属于模型结构改动适合后续进阶优化不适合第一版上线。4.3 训练过程中BN崩溃和loss变成nan怎么办BN崩溃在YOLO训练中很常见尤其当batch小于8的时候BN的均值方差统计不稳定loss会在某个epoch突然跳到nan。第一次遇到这个问题的反应不要是改模型结构先检查三件事学习率是不是太高默认0.01对YOLOv8来说在上百类数据上没问题但二分类任务可以把lr0降到0.005训练数据是不是存在全黑或全白图片这种异常图会让BN统计值剧烈波动第三个是batchsize是否过小导致单个batch里只有单一类别的目标。我这次的训练过程中出现过一次loss变成nan排查下来是验证集里混入了一张从视频里抽出的全黑帧图片均值接近0。删掉这张图后重新训练问题消失。遇到loss发散的坑先看数据再看学习率最后才考虑是不是模型结构问题这个顺序能帮你省下大量时间。4.4 类别不平衡正样本过多但关键样本太少我的数据集里head类样本数量稍多于helmet类直接训练后模型会对head更敏感表现为“把戴着头盔的人也误判为未佩戴”。这个问题在混淆矩阵上能看出来helmet的召回率低head的精确率也低。处理方式不是在数据层面硬凑1:1因为真实场景中未佩戴人数本来就多模型把两类区分开靠的是特征而非数量。我采用的方法是训练时按类别重采样每个batch里helmet和head的样本尽量接近1:0.9。具体实现可以用Ultralytics提供的fraction参数对训练集抽样子集也可以直接用albumentations在数据读取时对helmet类的小图做更多的水平翻转、亮度抖动让模型对helmet类的观感更多样。5. 部署落地从验证集到真实路口的性能5.1 模型导出与推理加速训练完成后把PyTorch权重导出成ONNX再转TensorRT是在真实路口跑实时视频的主流做法。YOLOv8导出只需一行命令yolo export modelbest.pt formatonnx dynamicTrue opset17在Jetson Orin或Xavier设备上还可以进一步转成TensorRT engineFP16精度下推理速度比PyTorch快3倍以上。导出时有个细节dynamicTrue会让输入尺寸可变部署灵活性高但TensorRT引擎会因此多占显存。如果摄像头分辨率固定到1080P建议直接把输入尺寸写死成640x640或1280x1280这样显存占用和推理延迟都更稳定。5.2 视频流里的检测优化与跟踪联动真实路口不能一帧一帧单独检测因为车辆和骑手在运动连续帧间目标位置变化不大直接用单帧检测结果会给后台产生大量重复告警。我的做法是在检测模块前加一个“间隔采样”每5帧检测一次中间4帧用ByteTrack跟踪目标ID只有跟踪框持续3次都确认是未佩戴头盔才触发告警。这样既保留实时性又过滤掉闪烁误报。实测下来这一套流程在1080P视频流上Jetson Orin NX设备大概能跑到35到40fps如果只检测ROI区域还能更高。换句话说一个边缘盒子同时处理8路视频不卡顿完全满足智慧交通监控的常见需求。5.3 后续扩展由头盔延伸到安全带、超载与车牌识别头盔检测只是路口非机动车违章识别的一个维度。项目稳定上线后同样的数据集和训练流程可以复用到其他检测任务比如驾驶室安全带检测、三轮车违规载人检测、逆行和闯红灯识别。这些任务之间最大的公共点是“目标小、角度多、实时性要求高”所以YOLO加数据质量优化的方案可以继续沿用。我也在尝试把YOLO和Transformer结构结合比如在检测头前插入一个轻量注意力模块让小目标在特征图上的响应更强。这个改动会牺牲一点推理速度但对远距离骑手的头盔检测精度提升很可观。如果你的项目也有车位级或路口级大范围监控需求这个方向值得投入。最后分享一个实际测试中发现的规律训练好的模型在同一个路口测试了三天第一天下雨、第二天阴天、第三天晴天mAP波动不超过两个点。真正影响精度的不是天气而是摄像头角度变化和焦距缩放。所以做智慧交通项目时不要只盯着模型调参先去现场确认相机安装高度、俯仰角和视野范围把这些信息反馈到数据采集阶段模型实际表现才能稳定。数据集的8300张照片里我最后悔的其实是只采集了固定机位的高度导致部署到另一个更矮的路口立杆时模型对小目标精度掉得比预期多。有条件的话数据采集阶段一定要多换机位、多换焦距这是花几天时间就能避免几个月返工的事情。