拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

头盔检测数据集构建与YOLOv8训练全流程:从标注规范到调参避坑

头盔检测数据集构建与YOLOv8训练全流程:从标注规范到调参避坑

前阵子整理了一版头盔检测数据集,8300张图片,全部转成YOLO格式,用于智慧交通场景下的骑乘人员头盔佩戴识别。断断续续搞了快半个月,从原始图像筛选、标注规范统一、标签格式转换,到拿来训 YOLOv8 和 YOLOv9,中间踩了不少坑。这篇就把整个过程完整记录下来,尤其把数据怎么清洗、标注怎么复核、训练参数怎么调这些最容易让人头大的地方讲透,给准备做头盔检测、智慧交通项目,或者刚开始接触 YOLO 数据集的朋友一个可以直接照着走的参考。

这个项目适合三类人:一是入门目标检测,想找一个不算复杂但又贴近真实业务的数据集练手;二是做智慧交通、两轮车治理相关项目,需要做可行性验证;三是已经训练过 YOLO,但总被数据质量坑到的人。我会尽量把每一步的思路讲清楚,不只是给命令,更多是告诉你为什么要这么做。

1. 头盔检测到底在解决什么问题

1.1 交通场景里的检测目标,和普通目标检测不一样

头盔检测表面上是个二分类目标检测任务,但放到真实交通场景里,它比想象中别扭得多。普通的目标检测,比如行人检测、车辆检测,目标通常比较大,姿态相对完整。但头盔检测要处理的画面,很多来自路口高位摄像头、电警杆件或者移动巡逻设备,视野里的人物往往只有几十个像素,而且经常被车辆遮挡、被阳光直射、在夜间低照度下只剩一个模糊轮廓。

骑乘人员的头部姿态也不固定,低头看手机、转头观察路况、戴帽子再戴头盔、半盔和全盔、冬天头戴厚围巾,这些情况都会让模型的特征学习受到干扰。如果只是拿一个普通行人检测模型硬套,很容易把头盔和普通帽子搞混,或者漏检后排乘客。

所以头盔检测本质上面临的是小目标、遮挡、姿态多样性和环境干扰叠加的难题。数据集在设计的时候,必须把这些真实情况都覆盖进去,而不是简单从网上抓一批戴头盔的人像图就完事。

1.2 数据质量直接决定模型天花板

我见过不少人拿到开源数据集,先不管三七二十一直接开训,结果 mAP 一直上不去,就怀疑模型结构不行。实际上目标检测任务里,模型结构对效果的影响,很多时候没有数据质量的影响大。数据集的标注是否一致、类别是否平衡、场景是否多样,这三点比换任何 Backbone 都重要。

拿头盔检测来说,最容易出现的问题是标注框不一致。同样一个戴头盔的骑手,有人把头肩部都框进去,有人只框头盔区域,还有人把整个上半身都圈上。这种标注风格的不统一,会让模型在训练时收到互相矛盾的信号,同一类目标的特征被拉散,最终表现就是置信度忽高忽低、边界框忽大忽小。

我这次整理数据集,第一原则就是:标注规范必须严格统一。宁可数量少一点,也不让脏数据混进去。因为脏数据的危害是复利式的,训练过程中每一个错误标注都会让损失函数给出错误梯度,几十张坏图就足够把一个还不错的模型拖垮。

2. 8300张数据集怎么搭出来的

2.1 图像来源与场景分层

这个数据集一共8300张图,来源包括两部分:一部分来自公开的街景采集素材,一部分来自实际路口的模拟拍摄。考虑到合规和隐私问题,所有涉及人脸的图像都做了必要的脱敏处理,或者采用人工标注后只在训练时使用脱敏版本。

场景分层是这次数据集质量的关键。我按时间段拆成了白天晴天、白天阴天、夜间开启补光、黄昏逆光四类,按镜头位置又拆成了高位俯拍、水平近景、倾斜侧拍三类。这样组合下来大概是 4×3=12 个主要场景分支,每个分支下再分配不同数量的图片。这样做的原因很简单:头盔检测的实际部署环境非常杂,只靠单一场景训练出来的模型,换个摄像头角度性能就会明显掉点。

另外我还单独筛了一批“困难样本”,包括后排乘客、儿童、外卖骑手、戴帽子的行人、建筑工地戴安全帽但不戴骑行盔的人。这些样本不是为了凑数,而是专门用来检验模型能不能区分“戴了该戴的盔”和“戴了别的帽子”。如果数据集里全是那种端端正正戴好头盔的样本,模型很容易把“头上有个东西”当成“戴了头盔”,到了真实场景立刻翻车。

2.2 标注类别与格式选择

这个数据集有两个版本,一个版本是“head_with_helmet”和“head_without_helmet”两个类别,另一个版本是“person_with_helmet”和“person_without_helmet”。实际训练下来,我更推荐按头部框来做,也就是框住头部区域而不是整个人。

原因很直接:高位摄像头拍摄的画面里,人身体的下半部分经常被车头、绿化带或者前车遮挡,如果标注人框,模型要被迫学习大量不完整的身体特征,反而干扰了对头盔特征的提取。头部框的区域更小、特征更集中,模型更容易学到“头顶有没有盔状物”的判别信息。

标注格式我最终统一转换成了 YOLO 的 txt 格式。每个 txt 文件里每一行对应一个目标,格式是:

class_id x_center y_center width height

其中 x_center、y_center、width、height 分别是边界框中心点坐标和宽高,全部归一化到 0~1 之间。举例来说,一张 1920×1080 的图里,某个头部框左上角是 (400, 300),右下角是 (520, 420),那么归一化后的值就是:

x_center = ((400 + 520) / 2) / 1920 = 0.2396 y_center = ((300 + 420) / 2) / 1080 = 0.3333 width = (520 - 400) / 1920 = 0.0625 height = (420 - 300) / 1080 = 0.1111

这个计算看着简单,但批量转换时很容易因为整数除法或者坐标越界出错。我的建议是转换后一定要写一个校验脚本,检查归一化坐标是否都在 [0,1] 区间内,宽高是否为正值,以及类别 ID 是否在配置范围内。这类低级错误在数据集规模小的时候看不出来,一旦批量自动化处理,就会变成训练集里的隐形炸弹。

2.3 数据清洗和质检流程

数据清洗我分成了三关。第一关是“图像质量关”,把模糊、过曝、严重压缩失真的图全部踢掉。第二关是“标注完整关”,检查有没有漏标的目标。这一关我用的是一个笨办法:把标注框叠加回原图,生成预览图,然后人工按场景抽检,每批抽 20% 左右。这样做虽然慢,但能发现很多自动检查发现不了的问题,比如两个高度重叠的框到底该合并还是该保留。

第三关是“标注一致性关”,重点检查两个类别之间的边界。实践中最常见的问题是:一个骑手戴着头盔但头盔带没系,有人标成 positive,有人标成 negative。这种不一致如果不处理,模型就会很困惑,它没法决定到底该学“有头盔”还是“头盔戴得规范”。

我最后的处理方式是:只要头上戴了头盔,就属于戴盔类,不考核绑带、佩戴方式是否规范。原因是从交通治理的目标来看,第一优先级是“有没有戴”,佩戴是否标准那是后续合规流程里做的事,不应该让检测模型去扛这个复杂度。数据集的目的应该是把“戴”和“没戴”区分清楚,而不是越权去做姿态评判。

3. YOLO 系列模型选型

3.1 为什么选择 YOLO 而不是其他检测框架

头盔检测对算法选型的要求很典型:推理速度要快,因为路口摄像头路数多,后端算力有限;精度要够,因为漏检和误检都直接影响业务指标;部署要省心,最好一套模型直接导出成 ONNX 或者 TensorRT,不用折腾复杂的依赖。YOLO 系列在这三点上平衡得最好。

早期我们用过 Faster R-CNN 做验证,准确率确实不算差,但单帧推理速度上不来,而且整体链路太重。后来换成了 YOLOv8,整个训练和导出流程清爽很多,一条命令行就能跑通。YOLOv9 在部分场景下因为采用了更复杂的网络结构,精度提升了一些,但对数据量和显存的要求也更高,所以主力训练我还是用 YOLOv8。

另外要强调一个点:如果不确定用哪个模型,先不要一上来就上最大的版本。YOLO 官方提供了 n、s、m、l、x 五种规格,我用的是yolov8n和yolov8s。对于头盔检测这种目标数量不多、类别只有两类、目标区域又相对集中的任务,n 和 s 已经足够,速度还快。很多项目其实根本用不到 l 和 x,模型大了反而在边缘设备上没法跑。

3.2 环境依赖与基础训练流程

我的训练环境是两卡,大概 24GB 显存,系统是 Ubuntu,驱动和 CUDA 提前装好。Python 环境用 conda 管理,避免多个项目依赖互相污染。核心依赖版本大致如下:

  • Python 3.10
  • PyTorch 2.1.0
  • torchvision 0.16.0
  • CUDA 11.8
  • ultralytics 8.x

安装 ultralytics 就一行:

pip install ultralytics

装完之后可以先跑一个官方预训练模型验证环境是否正常:

yolo predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg

环境正常之后,数据集的目录结构我对接的是 YOLO 的默认格式:

datasets/ └── helmet/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

训练配置用 data.yaml 指过去:

path: /datasets/helmet train: images/train val: images/val names: 0: head_with_helmet 1: head_without_helmet

接下来跑训练:

yolo detect train data=helmet.yaml model=yolov8n.pt epochs=150 imgsz=640 batch=32 lr0=0.01

这个流程对刚入门的人来说非常友好,基本不需要写代码,就能看到训练曲线和验证集结果。

3.3 训练超参里必须理解的几个点

很多人在 YOLO 训练里习惯直接抄默认参数,但针对头盔检测这个小目标任务,有四个参数值得认真调。

第一个是imgsz,输入分辨率。我只调了 640,但如果你想进一步提升小目标检测能力,可以考虑 960 甚至 1280。代价是显存占用和推理耗时明显上升。对于高位摄像头里的远距离小目标,分辨率提升带来的收益通常比换大模型更直接。

第二个是batch size。batch 大小影响 BN 层的统计量。数据集有 8300 张,样本不算特别少,batch=32 是安全的。如果显存不够可以降到 16,但不要低于 8,否则 BN 层的均值和方差估计会很不稳定,训练后期容易掉点。

第三个是epochs。头盔检测任务不算复杂,150 个 epoch 基本够。我观察过训练曲线,通常在 60 到 80 epoch 时 mAP50 就开始趋平,后面继续训练主要收益在小目标召回率上。如果你是第一次跑,可以设 300,但开启早停,让模型在验证集不再改善时自动停下。

第四个是mosaic增强。YOLO 默认开启 mosaic,也就是把四张图拼成一张训练。这个增强对小目标检测很有帮助,因为它相当于变相增加了小目标在画面中的密度。但如果你的数据集里小目标特别多,mosaic 也可能让目标变得更小,导致模型学不到有效特征。我建议分别在 mosaic=0.0 和 mosaic=1.0 下各跑一次对照实验,选效果好的。

4. 训练前的数据准备工作

4.1 数据集划分与目录组织

数据集划分看似简单,但划分方式决定了验证结果的可信度。最常见的错误是直接用随机划分,这会导致同一个路口、同一个时段拍摄的连续帧图像被同时分到训练集和验证集。因为相邻帧几乎一模一样,验证集上指标虚高,等模型部署到没见过的场景立刻原形毕露。

我这次是按场景先分组,再按组划分。先把属于同一个场景文件夹的图片视为一组,然后按 8:1:1 的比例拆成训练集、验证集和测试集。这样能确保同一场景的近似图像不会横跨训练和验证,验证结果才更有说服力。

最终划分下来,训练集约 6600 张,验证集约 850 张,测试集约 850 张。YOLO 默认只要求 train 和 val,但我额外留了 test 子集,专门用来做最终性能评估。原因是验证集通常在训练过程中被用来做早停和超参调整,模型间接接触过它,所以测试集才能反映真实泛化能力。

目录结构我保持着简洁:

helmet/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── helmet.yaml

data.yaml 里的 val 路径可以同时指向 val 和 test,但需要自己在代码里分别计算指标时,还是建议只把 val 填进去。

4.2 数据增强:既要丰富,又要可控

数据增强是深度学习中性价比最高的“免费午餐”,但也最容易做过头。YOLO 自带了一整套增强策略,包括随机翻转、旋转、缩放、色彩抖动、马赛克等。我最后采用的参数是:

  • 水平翻转:开启,概率 0.5
  • 随机旋转:关闭(头盔角度太敏感,旋转会让“戴头盔”变成“戴歪头盔”)
  • 上下翻转:关闭(真实场景不存在倒立的摄像头画面)
  • 亮度对比度扰动:开启,幅度 0.3
  • HSV 饱和度扰动:开启,幅度 0.3
  • Mosaic:开启,概率 0.8

这里最值得强调的是,旋转增强我直接关了。头盔检测任务有一个特殊点:交通摄像头拍摄的画面通常是水平的,头盔在图像中始终保持在头部上方。如果训练时允许图像随机旋转 30 度甚至 90 度,模型就会花大量参数去学那些现实中根本不存在的姿态,反而影响正常姿态下的检测精度。

我不建议盲目堆增强手段,尤其是针对智慧交通这种场景相对固定的任务。最好的方式是先关闭所有增强训练一个基线,然后逐步打开某些增强项,对比验证集指标,而不是一步到位全开。

4.3 类别不平衡怎么处理

头盔检测数据集里最常见的比例失衡是“戴盔样本远多于未戴盔样本”,因为我们潜意识里会收集更多正样本。这种失衡会导致模型偏向预测“戴盔”,漏检率飙升。我处理的方式有三个。

第一,采样层面控制两类数量比例。我有意识地让“戴盔”和“未戴盔”的实例数量比例接近 1.5:1,而不是 5:1。不完全等比例是因为戴盔的样本本身姿态更丰富,适当多一点正样本有助于模型学得更细。

第二,损失函数层面使用类别权重。YOLO 的训练配置里支持给不同类别加权重,我把未戴盔类的 loss 权重调高了一点,让模型在漏检未戴盔时受到更大的惩罚。

第三,数据难例挖掘。训练到一半,我会把验证集里误检和漏检的图片捞出来,补充进训练集的对应场景里。这个操作比调任何参数都有效,相当于把模型不会做的题重新放回教材里让它反复做。

5. 训练全流程实录与指标解读

5.1 训练命令与日志监控

训练命令我用的是最直接的一条:

yolo detect train data=helmet.yaml model=yolov8n.pt epochs=150 imgsz=640 batch=32 project=./runs name=helmet_v8n

训练过程中,ultralytics 会输出每个 epoch 的结果,包括 box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95。我第一次训练时盯着终端看得没什么头绪,后来学会了三个关注点。

首先关注的是 loss 是否在持续下降。如果 loss 曲线在前 20 个 epoch 内没有明显下降趋势,那大概率是数据路径配置错了,或者学习率设置不合适,而不是网络结构问题。

其次关注的是召回率 recall,而不是只看精确率。头盔检测的漏检代价通常高于误检,因为漏掉一个未戴盔的人,意味着这个违规行为完全没有被记录。所以我对 recall 的关注优先级高于 precision。

最后关注 mAP50 和 mAP50-95。mAP50 衡量的是框与真值重叠超过 50% 就算命中,适合快速判断模型整体是否正常;mAP50-95 更严格,它把不同 IoU 阈值下的表现平均起来,模型边界框质量差的话,这个指标会很惨。

5.2 PR 曲线和混淆矩阵到底怎么看

训练结束后,runs 目录下会生成一系列图表。我最常看的是这两个:PR 曲线和混淆矩阵。

PR 曲线横轴是召回率,纵轴是精确率。曲线越接近右上角越好。如果曲线整体偏低,说明模型能找到目标,但误检很多;如果曲线尾巴突然下坠,说明模型在低置信度阈值下会放过大量目标,也就是召回率不足。对于头盔检测,我更希望曲线在召回率 0.8 左右时,精确率依然保持高位,这样实际部署时可以把置信度阈值调到一个平衡点。

混淆矩阵则能直观看到类别之间的混淆。我遇到过一种很典型的情况:head_with_helmet 和 head_without_helmet 之间互相混淆不严重,但两者同时和 background 出现大额混淆,说明模型会把很多背景误判为头顶目标。这种问题通常靠增加背景负样本或者调高置信度阈值就能缓解。

数据里还有一类样本非常容易混淆:戴了遮阳帽或者安全帽的人。这类样本如果数量不够,模型很可能会把所有的“帽子”都当成“头盔”。我最后把这类样本单独抽出来,在验证集里做了专项统计,确认混淆低于 5% 才放行。

5.3 不同模型配置的对比结果

我用同一份数据,做了几组对照实验,表格如下:

模型配置imgszmAP50mAP50-95单帧推理耗时(ms, 实测)
yolov8n64092.3%68.1%约6
yolov8s64094.1%72.6%约9
yolov8s + resize 96096095.2%75.8%约16
yolov9c64094.5%73.2%约14

说明一下,这个表格的数字是我在自己数据划分和测评脚本下的结果,不同环境的绝对数值可能有差异,但趋势是有参考价值的。可以看到 yolov8s 加高输入分辨率后,mAP50-95 提升明显,这个收益主要来自小目标召回率上升。

不过部署时要权衡速度。如果是一路摄像头的实时分析,6ms 和 9ms 的差距不大;但如果是一个盒子接八路视频流,单帧多路并行处理,推理耗时就会成倍放大,这时候用 yolov8n 更稳妥。

6. 常见问题与排查技巧实录

6.1 漏检和误检对照速查表

我把训练和测试阶段遇到的主要问题整理成了下面这张表,方便后面遇到类似情况的人快速定位:

现象可能原因处理建议
白天晴天漏检远处小目标输入分辨率太低、小目标样本不足提高 imgsz 到 960;补充远距离样本
夜间补光区域误检率高夜间样本过少,模型没见过灯下高光增加夜间增强,对亮度抖动调大
戴遮阳帽被识别成戴头盔类别边界样本不足补充遮阳帽、安全帽、棉帽样本
同一画面反复框同一个头NMS 阈值设置不当调高 NMS IoU 阈值到 0.6~0.7
后排乘客漏检训练样本中多人实例少补充多乘客车辆样本,开启 mosaic
模型对某一场景过拟合场景划分不彻底按场景分组划分数据集

这张表里的每一项我都在实际项目中撞到过。最坑的一次是夜间误检率奇高,我一开始以为是模型问题,后来检查数据集发现夜间图片只占了总数据量的 8%,补到 20% 以后误检直接降了将近一半。

6.2 训练不收敛、BN崩溃这一类问题

头盔检测训练还有个常见现象:训练到某个 epoch,loss 突然暴涨,甚至变成 NaN。这个在很多场景里被称为“BN 崩溃”,本质上是 batch 内样本的均值和方差出现极端值,导致批量归一化层数值不稳定。我遇到不只一次,给几个排查思路。

第一,检查数据里有没有全黑的图片或者全白的图片。这类极端亮度图会让 BN 层的统计量颠簸,我在数据清洗阶段会把这些图单独拿出来做亮度归一化,或者直接删除。

第二,降低初始学习率。默认 lr0=0.01 不是在所有数据集上都合适,如果小目标多、目标区域小,梯度信号本来就偏弱,过大的学习率会让 loss 振荡。我通常把 lr0 降到 0.005 再观察。

第三,开启梯度裁剪或者换用更稳定的优化器。YOLO 默认用 SGD,如果发现 loss 曲线不稳定,可以试试 AdamW,很多时候收敛更平稳。

还有一个容易忽略的点:训练中断恢复后,不要直接从断点续训,有时候 checkpoint 里的优化器状态已经乱了。我习惯从最后一个 epoch 重新评估验证集指标,如果明显低于正常水平,干脆重新跑一轮。

6.3 部署阶段的几个小经验

模型训练完,导出 ONNX 或者 TensorRT 的时候也容易踩坑。YOLO 官方导出命令:

yolo export model=best.pt format=onnx imgsz=640

导出后建议用 onnxruntime 或者 TensorRT 验证一遍输入输出尺寸,确认和训练时一致。有个常见问题是训练时开启了 mosaic,模型学会了拼接图像的特征,但推理时不会使用这种增强,效果一般不会受影响,可如果你在导出前修改了输入尺寸,必须重新评估指标,不能沿用原来的置信度阈值。

另外一个部署细节是输入图像的预处理。训练时 YOLO 默认会做 letterbox,也就是在保持宽高比的前提下缩放并填充黑边。推理时如果直接 resize,没有做 letterbox,画面里的目标比例和训练时不一样,检测精度会肉眼可见地掉。这个一定要在推理代码里保持一致。

最后,头盔检测模型上线之后,不能一劳永逸。不同城市、不同路口、不同摄像头安装角度,都会让数据分布发生变化。最稳妥的做法是部署后持续收集误检漏检样本,定期回灌到数据集里做增量训练。我手上这版 8300 张的数据集,其实也只是第一版基线,后续还会根据实际反馈继续扩充。

我个人在实际项目里最大的体会是:头盔检测这个任务,真正难的不是把 YOLO 跑通,而是把数据搞干净、把场景覆盖全。模型结构在成熟框架下已经高度标准化,反而是数据层面的细节,比如标注框的一致性、场景分组划分、困难样本的收集,这些很少有人会系统性地讲。你不妨也拿自己的数据集试一遍,从数据质检开始,一步步记录问题和调整思路,最后再回头看,会发现提升模型性能最快的路,往往不是换更大的模型,而是更认真地处理那些不起眼的数据细节。

返回列表