简介:这份资源面向计算机视觉方向的学习者与工程实践者,提供在 Bdd100k 自动驾驶数据集上训练 YOLOv5s 目标检测模型的完整工程包,适合已掌握 Python 与深度学习基础、希望上手真实道路场景检测的开发者。压缩包共 85 个文件、约 97.7MB,以 yaml 配置、py 源码、pt 权重、ipynb 笔记本及 jpg/png 可视化结果为主,另含 sh 脚本、Dockerfile 与说明文档,覆盖模型定义、数据配置、训练与推理全流程。资源内含预训练权重、预处理后的 Bdd100k 数据集链接、从零训练与基于预训练权重训练两套实验记录,以及模型架构图与 4K 测试视频演示,便于对照复现训练曲线与检测效果。目前已有 109 人学习下载,可作为自动驾驶感知入门与 YOLOv5 迁移训练的实操参考。
1. 从 Bdd100k 到 YOLOv5:为什么这套组合值得你花一个周末跑通
如果你手头只有 COCO 或 VOC 的预训练权重,却要面对真实道路场景——逆光、雨夜、密集车流、被遮挡的行人——你会发现模型掉点掉得莫名其妙。Bdd100k 恰好补上这块短板:它包含 10 万段行车视频抽帧、覆盖晴天/雨天/夜间/黄昏、标注了车辆、行人、交通灯、交通标志等 10 类目标,是目前少有的带「天气+时段」维度的大规模驾驶场景检测数据集。而 YOLOv5 在工程侧的优势不用多说:单阶段、推理快、训练脚本开箱即用、导出 ONNX/TensorRT 链路成熟。把两者拼起来,你得到的是一个能直接往车载或路侧设备上搬的对象检测模型。这篇笔记面向的是想自己动手跑一遍的工程师——从数据格式转换、配置文件改写、训练参数调优,到踩过的坑和验证方法,全部按可复现的步骤写清楚。
2. 数据落地:把 Bdd100k 转成 YOLOv5 能吃的格式
2.1 先搞清楚 Bdd100k 的标注长什么样
Bdd100k 官方提供的是 JSON 标注文件,图像和标注分开存放。检测任务的标注文件通常叫bdd100k_labels_images_train.json和bdd100k_labels_images_val.json,每个条目包含图像名、属性(天气、时段、场景)以及一组labels,每个 label 里有category、box2d(x1,y1,x2,y2)等字段。注意两点:一是坐标是绝对像素值,不是归一化值;二是类别名和 YOLOv5 默认的 COCO 类别完全不同,需要自己建立映射表。
常见做法是先把 JSON 解析成每张图一个 txt 的中间格式,再统一转成 YOLO 的class cx cy w h归一化格式。我一般会写一个转换脚本一次到位,避免中间文件堆积。
2.2 转换脚本:从 JSON 到 YOLO txt 的完整实现
import json import os from pathlib import Path from PIL import Image # Bdd100k 原始类别到 YOLO 索引的映射,按需增删 CATEGORY_MAP = { "car": 0, "bus": 1, "truck": 2, "person": 3, "bicycle": 4, "motorcycle": 5, "traffic light": 6, "traffic sign": 7, "train": 8, "rider": 9, } def convert(json_path, img_dir, out_dir): with open(json_path, "r") as f: data = json.load(f) out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) for item in data: img_name = item["name"] img_path = Path(img_dir) / img_name if not img_path.exists(): continue w, h = Image.open(img_path).size lines = [] for label in item.get("labels", []): cat = label.get("category") if cat not in CATEGORY_MAP: continue box = label.get("box2d") if box is None: continue x1, y1, x2, y2 = box["x1"], box["y1"], box["x2"], box["y2"] # 过滤掉宽高为 0 的脏标注 if x2 <= x1 or y2 <= y1: continue cx = (x1 + x2) / 2.0 / w cy = (y1 + y2) / 2.0 / h bw = (x2 - x1) / w bh = (y2 - y1) / h # 裁剪到 [0,1],防止越界 cx, cy = min(max(cx, 0), 1), min(max(cy, 0), 1) bw, bh = min(max(bw, 0), 1), min(max(bh, 0), 1) lines.append(f"{CATEGORY_MAP[cat]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") txt_name = Path(img_name).with_suffix(".txt").name with open(out_dir / txt_name, "w") as f: f.write("\n".join(lines)) if __name__ == "__main__": convert("bdd100k_labels_images_train.json", "images/100k/train", "labels/train") convert("bdd100k_labels_images_val.json", "images/100k/val", "labels/val")逻辑说明:脚本逐条读取 JSON,用 PIL 拿到图像真实宽高做归一化,这是必须的——Bdd100k 图像尺寸并不完全统一,硬编码 1280×720 会在部分图上产生偏移。参数方面,CATEGORY_MAP决定了你最终模型的输出类别数,删掉不用的类别(比如train)能减少类别不平衡带来的干扰。if x2 <= x1这类过滤是血泪经验:原始标注里存在少量零面积框,不清理会让训练时 loss 出现 NaN。
2.3 目录结构与 data.yaml 的对应关系
YOLOv5 对目录结构有约定,转换完成后建议整理成下面这样:
bdd100k_yolo/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── bdd100k.yamlbdd100k.yaml内容如下:
path: /data/bdd100k_yolo train: images/train val: images/val nc: 10 names: ['car', 'bus', 'truck', 'person', 'bicycle', 'motorcycle', 'traffic light', 'traffic sign', 'train', 'rider']注意nc必须和CATEGORY_MAP的类别数严格一致,names的顺序也要和索引对应。我见过有人改了映射表却忘了改 yaml,训练不报错但类别全乱,验证时 mAP 低得离谱还找不到原因。
3. 训练配置:YOLOv5 在 Bdd100k 上的参数怎么设
3.1 模型选型:s/m/l 怎么挑
YOLOv5 提供 n/s/m/l/x 五个尺度。Bdd100k 训练集约 7 万张,验证集 1 万张,类别 10 类,属于中等规模。我的建议是:如果你只是验证流程,用yolov5s跑通即可;如果要落地到边缘设备,yolov5s或yolov5m是性价比最高的选择;追求精度且算力充足再上l。x在 Bdd100k 上收益递减明显,训练时间却翻倍,不太划算。
从官方预训练权重起步能显著加快收敛。命令里用--weights yolov5s.pt即可,注意预训练权重是 COCO 80 类,加载时 YOLOv5 会自动跳过分类头不匹配的层,这是它脚本内置的逻辑,不用手动改。
3.2 关键训练命令与参数逐条解释
python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data bdd100k.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ --name bdd100k_s \ --cache逐条说明:--img 640是输入分辨率,Bdd100k 里小目标(远处交通灯、行人)不少,如果你显存够,可以提到 960 或 1280,mAP 通常有 2~4 个点的提升,但 batch 要相应调小。--batch 16在单卡 16G 显存下比较稳,显存不够就降到 8 并配合--accumulate做梯度累积。--epochs 100是经验值,Bdd100k 上 80~120 轮基本收敛,看results.csv里 mAP 曲线走平就可以停。--hyp选hyp.scratch-low.yaml是因为从预训练迁移时学习率不宜过高,这个配置的初始 lr 和 warmup 更适合微调场景。--cache会把图像缓存到内存,7 万张图大约占 20~30G 内存,内存不够就别加,否则会被 OOM kill。
3.3 数据增强:Bdd100k 场景下哪些该开、哪些该关
YOLOv5 的 hyp 文件里控制增强的参数很多,针对 Bdd100k 我一般这样调:
| 参数 | 默认值 | 建议值 | 原因 |
|---|---|---|---|
| mosaic | 1.0 | 1.0 | 驾驶场景目标密集,mosaic 提升小目标召回 |
| fliplr | 0.5 | 0.5 | 水平翻转合理,交通场景左右对称 |
| flipud | 0.0 | 0.0 | 上下翻转会让天空跑到地面,语义错误 |
| hsv_h | 0.015 | 0.015 | 色调扰动帮助适应不同时段光照 |
| hsv_v | 0.4 | 0.5 | 夜间/逆光场景多,亮度扰动可以加大 |
| mixup | 0.0 | 0.1 | 轻微 mixup 有助于抑制过拟合 |
注意flipud千万别开,这是驾驶数据集的常识。另外 Bdd100k 本身已经覆盖多种天气,不需要再额外做雨雾模拟,过度增强反而会让模型在晴天场景下降点。
4. 训练过程排查:loss 不降、mAP 不动、显存爆炸怎么办
4.1 现象:训练前几个 epoch loss 就是 nan
原因通常是标注里有非法值。Bdd100k 原始 JSON 中存在少量宽高为负或坐标越界的框,转换时没过滤干净,归一化后出现负数或大于 1 的值,YOLOv5 计算 CIoU loss 时就会产生 nan。
解决办法:在转换脚本里加严格校验,除了前面写的x2 <= x1过滤,还要检查cx, cy, bw, bh是否都在(0, 1]区间内,超出就丢弃该框。另外可以在训练命令里加--rect关闭矩形推理,减少 padding 带来的坐标异常。
4.2 现象:mAP 卡在 0.2 左右上不去
先检查类别映射是否和 yaml 一致。我遇到过把rider和person索引写反的情况,模型学出来的框位置对但类别全错,mAP 自然低。排查方法是跑val.py后看混淆矩阵,如果某两类互相混淆严重,基本就是映射问题。
如果映射没问题,再看学习率。从 COCO 预训练迁移到 Bdd100k,初始 lr 建议在 0.01 左右,太高会导致预训练特征被破坏。可以先用--epochs 5做一次 lr 范围测试,看 loss 下降最陡的区间。
4.3 现象:训练到一半显存突然爆掉
这通常是--cache加上 dataloader 的 prefetch 导致的。YOLOv5 默认workers=8,每个 worker 都会拷贝一份缓存数据,内存和显存都会涨。解决办法是把--workers降到 4,或者去掉--cache改用--cache disk。另外如果开了--multi-scale,每轮图像尺寸随机变化,某些大尺寸 batch 会瞬间吃满显存,训练不稳定时可以先关掉。
4.4 现象:验证集 mAP 比训练集低很多
Bdd100k 的验证集和训练集在场景分布上并不完全一致,比如夜间样本比例不同。如果差距超过 15 个点,说明过拟合了。可以加大--weight_decay到 0.0005,或者把dropout打开(在 hyp 里设dropout: 0.1)。另一个容易被忽略的点是:Bdd100k 验证集里有些图像标注不完整(漏标),这会导致 mAP 被低估,属于数据集本身的噪声,不用过度调参去拟合。
5. 验证与导出:怎么确认模型真的能用
5.1 用 val.py 看逐类指标,别只看总 mAP
python val.py \ --data bdd100k.yaml \ --weights runs/train/bdd100k_s/weights/best.pt \ --img 640 \ --task val \ --save-json跑完后重点看per-class那一栏。Bdd100k 里traffic light和traffic sign因为目标小、密集,mAP 通常比car低 20 个点以上,这是正常的。如果person的 mAP 异常低,检查一下是不是把rider误标成了person,这两个类别在骑行场景里容易混。
5.2 导出 ONNX 并做一次推理对齐
python export.py \ --weights runs/train/bdd100k_s/weights/best.pt \ --include onnx \ --img 640 \ --batch 1 \ --simplify导出后务必用onnxruntime跑一张测试图,和 PyTorch 的输出做数值对比。我一般会算两个输出的最大绝对误差,正常应该在 1e-4 以内。如果误差大,检查--simplify是否生效,以及导出时的 opset 版本是否和推理引擎匹配。这一步是后悔药:等到部署时才发现导出有问题,回头查成本很高。
5.3 一个快速验证技巧:用视频抽帧做时序一致性检查
单张图的 mAP 好看不代表视频里稳定。我习惯用ffmpeg从一段行车记录仪视频里抽 100 帧,批量推理后看同一目标在连续帧里的框是否跳动。如果跳动超过 10 个像素,说明模型对运动模糊或光照变化敏感,可以考虑在训练时加入--multi-scale和更强的 HSV 扰动。这个检查花不了十分钟,但能提前暴露很多部署阶段才会发现的问题。
6. 进阶技巧:把 Bdd100k 的天气标签用起来
Bdd100k 每个样本都带weather和timeofday属性,大多数人转换时直接丢掉了,其实这是做场景自适应训练的免费资源。我的做法是在转换脚本里额外输出一个meta.json,记录每张图的天气和时段,训练时按这些属性做分层采样。
具体操作:先统计训练集里night和rainy的样本占比,如果低于 15%,就在 dataloader 里对这些样本做 oversampling。YOLOv5 本身不支持按属性采样,但可以写一个简单的WeightedRandomSampler替换默认的 sampler。代码不复杂,核心是给每个样本算一个权重,稀有场景权重高,常见场景权重低。
另一个用法是分场景验证。把验证集按timeofday拆成day和night两个子集,分别跑val.py。如果夜间 mAP 比白天低 30 个点以上,说明模型对低光照泛化不足,这时候再针对性做亮度增强或引入夜间专用数据,比盲目调参有效得多。
我自己的习惯是:每次在 Bdd100k 上训完一个模型,先不看总 mAP,而是先看夜间和雨天的分项指标。这两个场景过了,模型才敢往车上搬。这套流程我踩过不少坑,从标注转换的脏数据到导出对齐的数值误差,每一步都有翻车的可能,但跑通之后你会发现 Bdd100k + YOLOv5 是目前驾驶场景检测里最省心的组合之一。希望帮到你。
本文还有配套的精品资源,点击获取