做智慧交通项目的人应该都有这种体会:跑模型之前,算法调参都是小事,真正让人头疼的是手里没有对口的标注数据。去年我接到一个城市路口头盔识别需求时,第一反应也是去找现成的头盔检测数据集,结果发现市面上开源的数据集要么是国外场景(骑手和国内的电动车形态完全不同),要么是单场景拍摄(同一摄像头、同一角度),要么就是各种合成图和爬虫图混杂在一起。训练出来的模型换个路口就掉点,根本没法落地。
后来我把重心完全倒过来:先不下猛药调模型,而是老老实实把数据这件事做扎实。磨了快两个月,整理出一套 8300 张基于真实交通监控场景的头盔检测数据集,配套 YOLO 格式标注,专门给 YOLO 系模型使用。这套数据集的定位很明确,不是"看起来很多"的通用目标检测图库,而是尽量贴近国内典型城市混合交通实际路况:电瓶车、摩托车、三轮车、骑行和步行的行人混在一起,戴盔和不戴盔的目标往往只有几十个像素,天气、光照、角度都跟实验室里的公开数据集不一样。
这篇博文会把整个数据集从采集、清洗、标注到训练坑点完整拆开,聊聊一个真正能用于智慧交通项目落地的头盔检测数据集应该长什么样。适合谁看?正在做头盔检测、骑车行为识别、道路安全监控项目的同学,哪怕是刚入门 YOLO 想找一个靠谱的数据集练手,这篇文章里的数据准备思路和踩坑记录也可以直接复用。
这里先把数据集的关键信息摆出来,后续所有细节都围绕这些展开。
| 项目 | 说明 |
|---|---|
| 图片总数 | 8300 张 |
| 标注格式 | YOLO txt(归一化 x_center, y_center, w, h) |
| 标注类别 | 7 类(详见第 3 节) |
| 图像尺寸 | 1280×720 至 1920×1080 |
| 场景来源 | 城市路口、非机动车道、小区出入口、学校周边 |
| 时间分布 | 白天(约 70%)、黄昏/阴霾(约 18%)、夜间(约 12%) |
| 目标尺度 | 多数电瓶车驾驶人目标高度在 40~120 px 之间 |
这个构成不是我拍脑袋定的,而是根据业务落地的实际需求反推出来的。下面我把每部分的思考过程讲清楚。
1. 8300 张图背后的场景取舍:为什么公开数据集训练完就掉点
先说一下采集思路。市面上很多头盔数据集喜欢把车拍得又大又清楚,看起来精度很高,但真实路口的监控画面里,一辆电瓶车从出现在画面到驶离,可能只有 100 px 左右,头盔更是只有 20~50 px。所以我在整理这批数据时,第一个原则就是:目标必须小、场景必须杂、单张图的内容必须多。
1.1 采集分布的构成
这套数据的采集来源包括:
- 城市主干道十字路口:占 42%。重点是红灯等待时车辆密集停靠、绿灯放行时多车同时启动这两个高价值片段,能大幅提高模型对"目标密集+相互遮挡"情况下的鲁棒性。
- 非机动车道和人行横道:占 28%。这类场景容易出现电动车和行人并行、逆行、横穿等不规则运动,是目标关联和漏检的高发区。
- 小区出入口、学校周边:占 18%。这两个地方的特点是车速慢、目标多、头盔佩戴率差异大,能人为制造出"难例"。
- 商业街、外卖集散点:占 12%。外卖骑手群体的头盔佩戴情况、车辆停放时头盔挂在车把上等特殊形态,都集中在这里出现。
每张图都是直接从监控视频里按帧抽取的,不是连拍一个场景下几十张高度相似的画面。抽帧间隔我会尽量控制在 30 帧以上,保证同一辆车不会以几乎相同的姿态重复出现在数据集里,这能有效避免模型在训练时对某个特定目标过度记忆。
1.2 关注场景本身,而不是只关注目标本身
有一个容易被忽略的点:很多开源数据集在裁剪时只保留了目标附近的区域,训练时模型看到的永远是被放大的头盔特写。但真实的监控画面里,远处目标的信噪比很低,模型必须先学会"从复杂背景中找到那个可疑的小目标",然后才能做分类。为了尽可能模拟这种难度,我保留了原始画面的完整上下文信息,而不是把每个目标裁剪成大图。你要训练的是一个能部署在路口监控上的检测器,不是一个人脸识别式的特写分类器。
2. 标注类别体系和标注规范:7 个类怎么设计出来的
标注是数据集质量的核心。很多数据集看起来数量不小,但标注规范含糊,类别定义打架,训练时混淆矩阵惨不忍睹。这套数据集的标注类别我最终定为 7 类:
| 类别 id | 名称 | 对应标注框 |
|---|---|---|
| 0 | helmet | 骑行人员头部佩戴的头盔(含正在佩戴但尚未系扣) |
| 1 | no_helmet | 骑行人员裸露头部 |
| 2 | head | 行人头部(便于辅助区分骑行人员) |
| 3 | rider | 骑行人员全身(含骑车姿态) |
| 4 | person | 行人全身 |
| 5 | motorcycle | 两轮机动车(含电动摩托车) |
| 6 | electric_bicycle | 电动自行车 / 电动轻便摩托车 |
2.1 为什么同时标了"人"和"车"
你可能觉得奇怪,做头盔检测为什么不只标 helmet 和 no_helmet 两类。我的实际经验是:只标两个类别,模型在推理时基本没法判断"哪个头是骑车的、哪个头是走路的"。虽然业务上最终只关心头盔,但加入 rider/head/person 之后,模型会在特征层面学到人和车之间的关联,后期做业务逻辑(比如"骑手未戴头盔"而不是"行人没戴头盔")时也就不需要再额外做目标关联算法了。
所以这套数据集在 YOLO 任务中建议直接跑 7 类,而不是只跑 2 类。如果你确实只需要二分类输出,可以在训练后用 rider 或 motorcycle 框做空间过滤,效果通常比一开始就训练二分类要好。
2.2 难例标注规则:特别容易标错的那几类
数据标注最怕的就是规则不统一,不同标注员对同一个模糊场景有不同理解。我整理了一批难例标注规则,这是整个过程里沉淀下来最有价值的部分:
- 正在戴头盔(头盔在手上举向头部):标 no_helmet,同时另标一个 head。如果头的最终朝向被头盔遮挡,依然以头部位置为准标 no_helmet。
- 头盔戴在头上但下巴扣没有系:标 helmet,不标 no_helmet。因为从监控上看,佩戴特征已经出现,业务上一般也认这个为"戴了"。
- 头盔挂在后视镜或车把上:只标对应的 motorcycle / electric_bicycle,不标 helmet。
- 头盔夹在腋下或抱在手上:只标 rider / person,不标 helmet。
- 后座乘员、蹲在踏板上的人:只要露出头部,一律参与标注,不因"看不全"而跳过。
- 骑车人戴工地的安全帽:归入 helmet 类。虽然形态不同,但从安全监测业务角度应该视为戴盔。
- 头盔反光、过曝、镜头虚焦:以能看到头盔轮廓为最低标准,能辨认轮廓就标 helmet,完全不可辨认则不标,宁缺毋滥。
这类规则我建议所有做数据集的团队都明确写进标注文档。没有规则的数据集,算法组根本不敢拿去算指标。
3. 数据清洗和标注重检:8300 张里被淘汰掉的 1400 张
标完一批之后我做了两轮清洗。第一轮清的是原始图:
- 剔除画面完全模糊、遮挡率过高(目标被大面积遮挡)的帧;
- 剔除同一场景连续帧中目标姿态完全重复的冗余帧;
- 剔除压缩痕迹严重、出现大量马赛克块的视频帧;
- 剔除红外补光下目标形态严重扭曲的画面(少量保留作为夜间样本的增益)。
第二轮清的是标注质量。我做了双层抽检:先由标注人员进行互检,再由我自己抽检 12% 的图逐框核对。最后大概有 1400 张图因为标注分歧过大、画面质量不达标被整体移除。如果你图省事把这 1400 张也硬塞进去,最终结果就是测试集指标虚高、换场景就崩,与其这样不如老老实实砍掉。
清洗完以后,我又做了一步关键操作:重新梳理图像尺寸和曝光分布。因为监控视频截图的宽高比不统一,直接丢给 YOLO 训练会导致大量图像被自动缩放填充,产生黑边和形变。统一到 1280×720 或 1920×1080 之后,可以避免很多 letterbox 相关的误检问题。
4. 用 YOLO 训练前的数据准备:划分、增强和目录组织
数据到位之后,不要急着开训,先把训练集、验证集、测试集的划分方案定下来。这里我踩过一个很大的坑:如果按原始采集地点直接划分,同一个路口的画面会因为背景高度相似,导致验证集和测试集的分数虚高。正确做法是先按照"采集点位"做分组,再按组划分,保证同一个监控点位的画面不会同时出现在训练集和测试集里。
4.1 数据集目录和划分比例
最终划分如下:
| 划分 | 图片数 | 占比 |
|---|---|---|
| train | 5850 | 70% |
| val | 1250 | 15% |
| test | 1200 | 15% |
图片目录建议这样组织:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── README.mddata.yaml 的核心内容:
path: ./dataset train: images/train val: images/val test: images/test nc: 7 names: 0: helmet 1: no_helmet 2: head 3: rider 4: person 5: motorcycle 6: electric_bicycle这里强调一个细节:images 和 labels 目录必须保持完全同名同结构的文件对应关系,否则训练时会疯狂报 missing label 错误。检查是否对齐最直接的方式是写一个脚本统计两边文件名集合差异,而不是眼睛看。
4.2 数据增强策略:哪些该开,哪些该关
YOLO 的默认增强策略里,mosaic 和 mixup 一般建议开着,对挖掘小目标有帮助。但我要特别提醒一点:垂直翻转(flipud)在头盔检测场景里一定不要开。因为监控画面是固定的地平线视角,头盔永远在画面靠上的位置,倒置图像会产生大量反物理样本,把模型搞糊涂。水平翻转(fliplr)可以开,因为车辆左右方向不分语义。色彩增强(hsv)建议保持温和,监控摄像头的白平衡相对稳定,不需要大幅改变色相。
夜间样本不建议依赖图像增强硬造,而是靠真实采集的夜视图补足,否则红外噪点和暗部细节的光学特征很难被增强器模拟出来。
5. 训练阶段的实际坑:小目标、混淆矩阵和 BN 崩溃
进入训练阶段之后有 4 类问题几乎每个人都会遇到,我分别讲一下我在这套数据集上的实测经验。
5.1 小目标样本在类别之间的不均衡
头盔和 no_helmet 是典型的类别不均衡问题:在交通监控画面里,不戴盔的人往往更少,但业务上恰恰最看重不戴盔的识别召回率。我处理方式不是直接改损失函数,而是先做统计——按图像统计每个类别出现的框数量,发现 helmet 框数大概是不戴盔的 3 倍左右。于是先在采样层面做了类别加权:训练时以一定概率复制含 no_helmet 的样本参与本轮迭代,而不是简单粗暴地重复生成整张图。这样既提高了难例的出现频率,又不会显著增加过拟合风险。
5.2 混淆矩阵里 helmet 和 no_helmet 的混淆来源
跑完一轮之后去翻 confusion matrix,经常能发现 helmet 和 no_helmet 之间存在不少误判。我仔细检查了误判样本,原因基本集中在这几种:远处目标分辨率太低,头盔轮廓信息在小尺寸下断裂;雨夜反光导致背景和头盔纹理相似;佩戴的半盔露出大面积头发,跟不戴盔在外观上接近。针对这些问题,我在数据集中额外补充了一批"半盔+露出头发""雨天天桥底阴影"等难例图,行之有效地拉低了混淆比例。只调模型不补数据,是很难解决这类光学混淆的。
5.3 BN 层崩溃和 batch size 的关系
训练的时候第一次用单卡 3090 跑 YOLOv5 的 base 模型,batch size 设到 16,跑了几十个 epoch 后发现 loss 开始小幅震荡,随后某些类别出现大面积漏检。查了半天,问题出在 BN 统计量在小 batch 下方差偏大,导致网络在前向时输出不稳定。这在监控类数据集上尤其明显,因为小目标密度的空间分布本身就很不均匀。我改用 batch size 32(必要时用梯度累积模拟),并关闭了某些层对 BN 的自动冻结策略,情况明显好转。遇到 loss 曲线异常振荡时,先检查 batch size 再动网络结构。
5.4 预训练权重的选择
建议从 COCO 预训练权重开始迁移学习,而不要从随机初始化开始。头盔虽然属于比较特殊的目标,但 COCO 里包含 person、motorcycle 等相近类别,底层特征可以直接沿用。实测从 COCO 权重开始,相比随机初始化大概能节省 40% 的训练时间,且最终 mAP 高 3~5 个百分点。如果显存紧张,可以考虑冻结前 10 层,只微调后面几层,对监控场景的小目标检测也有正向作用。
6. 训练参数、评估指标和结果复现
给出我在这套数据集上验证过的推荐配置,直接复现即可:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| model | YOLOv5s / YOLOv8s | 推理速度与精度均衡 |
| imgsz | 1280 | 小目标场景尽量不小于 1280 |
| batch | 32(或 16 + 梯度累积) | 确保 BN 稳定 |
| epochs | 150 | 配合早停策略 |
| lr0 | 0.01 | 权重衰减默认即可 |
| mosaic | 1.0 | 开启 |
| mixup | 0.2 | 控制在较低水平 |
| fliplr | 0.5 | 开启 |
| flipud | 0.0 | 必须关闭 |
| hsv_h/hsv_s | 0.015 / 0.5 | 轻量色彩增强 |
训练完成后的评估不要只看 mAP@0.5。智慧交通业务更关注的是在不同置信度阈值下,误报和漏报的关系。我一般会看 mAP@0.5:0.95、precision-recall 曲线和 F1 曲线,同时重点检查 no_helmet 这个类别的 recall,而不是整体 mAP。因为整体 mAP 很容易被 helmet 这种多数类抬上去,掩盖少数类的失败。
我这边一个典型的验证结果:在 val 集上 mAP@0.5 约 0.86,mAP@0.5:0.95 约 0.55,no_helmet 类别的 recall 约 0.78。这个水平放到真实路口去看,单路口的漏检率大约在 8%~12%(取决于该路口非机动车流量密度)。如果你看到"公开数据集测试 99%"之类的数字,先确认它的测试集来源和复杂度,大概率是场景太简单了。
7. 数据集的适用边界和下一次迭代方向
这套数据集不是万能的,它有明确边界:主要覆盖城市监控视角,目标是电瓶车、摩托车骑行者;如果想做工厂安全帽检测,或者商场室内场景,需要另行补充室内角度数据。此外,数据集中夜间占比只有 12%,如果你做 24 小时全时段运营,建议在部署前额外采集夜间数据微调。
最后再分享一点个人体会。做数据集的这几个月,我最大的收获不是那 8300 张图本身,而是把"数据质量怎么定义"这件事想明白了。对一个落地的智慧交通项目来说,模型的泛化能力从来不是靠调参调出来的,而是靠数据的边界撑出来的。你现在花在数据清洗和标注规范上的时间,后面都会在换场景部署时成倍省回来。下一步我打算给这批数据补上一组 video sequence 级别的 tracking id,让它能直接喂给 ByteTrack 之类的跟踪模型做骑行轨迹分析;如果你在做相关项目,拿这套数据去做迁移学习测试就行。真实路口的挑战永远比想象中多,但把数据地基打牢之后,那些难题至少不再是无从下手。