简介:面向夜间车辆检测任务的数据集与配套教程包,适合使用YOLO系列模型做目标检测的开发者与学习者。素材来自真实夜间行车场景,图像质量高、场景丰富,可满足夜间交通监控、自动驾驶辅助等场景下的算法验证与模型训练需求;标注框质量可靠。包内标签同时提供VOC的xml、COCO的json和YOLO的txt三种格式,分别存放于不同文件夹,无需手工转换即可接入常见训练流程。附带的Python划分脚本可按需生成训练集、验证集与测试集,另有Linux与Windows环境搭建及训练教程,便于从零跑通案例并迁移到自己的数据集。资源共2000个文件,以xml、txt标注文件为主,配合py脚本、yaml配置与html说明文档,整体约41.77MB,目录结构清晰。已有1639人学习,适合需要高质量夜间车辆数据并希望快速上手YOLO训练的入门及进阶用户。
1. 夜间车辆检测,数据决定上限,模型只负责逼近上限
做目标检测的人都知道一句话:白天检得爽,晚上就翻车。夜间场景里车灯眩光、暗部细节丢失、路灯色偏叠加在一起,同一个 YOLO 模型白天 mAP 能过 0.9,晚上直接掉到 0.6 甚至更低。这套资源就是冲着这个痛点来的——1000 张夜间车辆图片,附带 VOC、COCO、YOLO 三种格式的完整标签,再加一个划分脚本和一份能照着跑的训练教程。你不用再花两三天去爬图、清洗、标注、转格式,解压后直接进训练流程。适合正在做智慧交通、夜间辅助驾驶感知的从业者,也适合刚入坑 YOLO 目标检测但卡在数据准备环节的新手。下面我按“三种格式差异 → 划分脚本 → 训练参数 → 踩坑排查 → 验证评估”的顺序把这个包拆透。
2. 打开压缩包:VOC、COCO、YOLO 三种标签格式到底差在哪
一个数据集同时给三种格式标签,很多人第一反应是“冗余”,实际干过活的人才知道这有多省事。不同训练框架吃的标签格式不一样:老牌的 SSD、Faster R-CNN 常用 VOC 的 XML,Detectron2、MMDetection 吃 COCO 的 JSON,而 YOLO 系(v5/v8/v9)统一用 TXT。拿到手不用写转换脚本,直接按框架选对应的目录就行。
2.1 VOC 的 XML:框坐标是绝对像素
VOC 格式的核心是一个 XML 文件对应一张图片,文件名和图片同名,只是后缀换成 .xml。这里面最关键的是<object>节点,每个<object>代表一个目标,<name>是类别名,<bndbox>里存的是左上角和右下角的绝对像素坐标。
<annotation> <folder>night</folder> <filename>night_0001.jpg</filename> <source> <database>Night Vehicle Dataset</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>car</name> <bndbox> <xmin>432</xmin> <ymin>620</ymin> <xmax>891</xmax> <ymax>854</ymax> </bndbox> </object> <object> <name>truck</name> <bndbox> <xmin>1203</xmin> <ymin>688</ymin> <xmax>1510</xmax> <ymax>902</ymax> </bndbox> </object> </annotation><size>节点的宽高决定了坐标的绝对参考系,做数据增强或 resize 时,必须拿原始宽高做等比换算,不然框就偏移了。我拆过不少数据集,最常见的问题是 XML 里图片宽高和实际图片不一致,通常是标注完又压缩过图片但没同步改 XML,这种脏数据在训练时不会有报错,但 mAP 就是上不去。
2.2 COCO 的 JSON:categories、annotations、images 三段式
COCO 格式把所有标注集中在几个 JSON 文件里,典型结构是images、annotations、categories三个数组。images里记录图片 id 和文件名,annotations里每条是一个目标框,categories是类别 id 到类别名的映射。它和 VOC 最大的区别是 bbox 用了[x, y, width, height]的绝对像素表达,x、y 是框左上角坐标,而不是中心点。
{ "images": [ {"id": 1, "file_name": "night_0001.jpg", "width": 1920, "height": 1080} ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "bbox": [432, 620, 459, 234], "area": 107406, "segmentation": [], "iscrowd": 0 } ], "categories": [ {"id": 1, "name": "car"}, {"id": 2, "name": "truck"} ] }注意这里的area字段,如果 annotation 是从 VOC 转过来的,不少转换脚本会忘了填area,或者直接填 0。MMDetection 训练时area为 0 不影响反向传播,但在做按面积分档评估(COCO 的 mAP 分成 small/medium/large 三档)时结果会失真。这个包里的标注我逐条抽查过,area是按 bbox 宽高乘积算的,可以用。
2.3 YOLO 的 TXT:归一化坐标省去单位换算
YOLO 的标签格式最简洁,每张图对应一个 TXT 文件,每行一个目标,格式是class_id x_center y_center width height,所有值都归一化到 0~1 之间。归一化的好处是换分辨率不用改标签,坏处是一旦图片尺寸变了,必须重新检查归一化坐标是否还是围绕 0~1 分布。
0 0.3445 0.6829 0.2391 0.2167 1 0.7068 0.7361 0.1599 0.1981拿前面 VOC 示例里第一辆车来算:x_center = (432 + 891) / 2 / 1920 = 0.3445,y_center = (620 + 854) / 2 / 1080 = 0.6829,width = (891 - 432) / 1920 = 0.2391,height = (854 - 620) / 1080 = 0.2167。这个换算逻辑在第三章会用到,因为标签格式转换是划分脚本里最容易出 bug 的环节。
2.4 三种格式的横向对比与选型
| 格式 | 存储形态 | 坐标表达 | 典型框架 | 适用场景 |
|---|---|---|---|---|
| VOC | XML 文件 | 绝对像素 xmin/ymin/xmax/ymax | SSD、Faster R-CNN | 单机小规模训练、源码调试 |
| COCO | JSON 字典 | 绝对像素 x/y/w/h + 分割信息 | MMDetection、Detectron2 | 多类别、实例分割、算法评测 |
| YOLO | TXT 行记录 | 归一化中心点 + 宽高 | YOLO 全系列 | 快速迭代、边缘设备部署 |
我自己常用的原则是:跑 YOLO 就用 TXT,跑 MMDetection 就转 COCO,VOC 基本只在复现老论文时碰。这张数据集三种都给,实际工作里省掉了最烦的“格式转换后坐标漂移”问题。
3. 划分脚本实战:比例、随机种子与类别平衡怎么设置
划分脚本是所有数据集里最不起眼、但最容易被忽视的部分。很多人的做法是“前 700 张训练、后 300 张验证”,看起来比例没问题,结果训练集里全是近景车辆,验证集里全是远景车辆,mAP 曲线异常但找不出原因。
3.1 为什么不能顺序切分
夜间车辆图片通常按拍摄时间段、路段批量采集,文件名连续往往意味着拍摄环境高度相似。前 700 张可能都是同一段高速路的车灯特写,后 300 张是市区路口的全景,两个分布差异大到模型根本学不到统一的特征。正确的划分方式必须是随机打乱后切分,而且随机种子要固定,保证别人复现你的结果时训练集和验证集完全一致。
3.2 脚本核心代码与参数说明
这个压缩包里带的划分脚本逻辑不复杂,但有几个细节处理得比较到位。我拆出来核心部分看:
import os import random import shutil random.seed(42) IMG_DIR = "images" LABEL_DIR = "labels" TRAIN_RATIO = 0.8 VAL_RATIO = 0.2 imgs = [f for f in os.listdir(IMG_DIR) if f.endswith((".jpg", ".png"))] random.shuffle(imgs) train_imgs = imgs[: int(len(imgs) * TRAIN_RATIO)] val_imgs = imgs[int(len(imgs) * TRAIN_RATIO) :] with open("train.txt", "w") as f: for img in train_imgs: f.write(os.path.join(os.path.abspath(IMG_DIR), img) + "\n") with open("val.txt", "w") as f: for img in val_imgs: f.write(os.path.join(os.path.abspath(IMG_DIR), img) + "\n") # 同步划分标签文件 for img in train_imgs: label_name = img.rsplit(".", 1)[0] + ".txt" shutil.copy(os.path.join(LABEL_DIR, label_name), "train_labels/") for img in val_imgs: label_name = img.rsplit(".", 1)[0] + ".txt" shutil.copy(os.path.join(LABEL_DIR, label_name), "val_labels/") print(f"train: {len(train_imgs)} images, val: {len(val_imgs)} images")random.seed(42)是画龙点睛的那笔。没有它,每次运行脚本出来的训练集都不一样,实验结果无法横向对比,尤其在调参阶段会非常被动。TRAIN_RATIO = 0.8是目标检测比较主流的切分比例,1000 张图里 800 张训练、200 张验证,验证集够算 mAP 但不会挤占太多训练样本。如果数据量更大,0.9/0.1 也常见,但 1000 张这个体量保持 0.8/0.2 更稳。
shutil.copy把标签文件同步复制到新目录,比移动更安全,万一划分错了还能回滚。这一点看起来是小事,实际做的时候能省不少后悔药——我有一次直接shutil.move把标签从原目录移走,结果发现验证集里有两张图标签丢了,原图又没备份,只能重新标,血的教训。
3.3 划分后的检查清单
脚本跑完后,不要急着开训练,先做三件事。第一,确认 train.txt 和 val.txt 的行数和实际图片数一致,别出现空行或者路径带中文的情况。第二,统计两类别的数量分布是否接近整体分布:
for label_dir in train_labels val_labels; do echo "== $label_dir ==" grep -oE "^[0-9]+" $label_dir/*.txt | awk -F: '{print $1}' | sort | uniq -c done第三,在验证集里随机抽三张图,加载标签可视化检查坐标是否在图片范围内。尤其是从 VOC 或 COCO 转 YOLO 的时候,归一化坐标偶尔会算出小于 0 或大于 1 的值,这种脏数据会在训练时被 YOLO 直接忽略,表现为 loss 正常下降但 mAP 永远在一个低位徘徊。
如果发现某个类别在验证集里数量过少,比如 truck 只出现在 5 张图里,那 mAP 的波动会非常大,单次评估结果没有参考价值。这种情况建议把类别少的图片手动补进验证集,或者直接跑 K 折交叉验证。
4. 训练教程里的关键参数:从 data.yaml 到 loss 曲线
这个包里的训练教程篇幅不长,但把 YOLO 训练里最容易翻车的参数都点了名。入门玩家容易犯的错是把训练当黑匣子,一键跑起来就不管了,等到 loss 曲线不对了才回头查参数,那时候时间已经浪费掉了。
4.1 先把 data.yaml 写对
YOLO 训练的第一步不是调超参,而是把数据配置文件写对。data.yaml 里路径、类别数、类别名任何一个错,训练都能跑,但结果大概率是废的。
train: ./train.txt val: ./val.txt nc: 2 names: ['car', 'truck']train和val指向第三章生成的 txt 路径列表文件,YOLO 会按 txt 里的路径逐行读取图片。这里有一个隐藏的坑:路径必须是绝对路径或者相对命令行执行目录的路径,不能写~/night_dataset/train.txt,因为 YOLO 的 dataloader 不会做 shell 展开。nc必须和names的列表长度严格一致,多一个少一个都会导致训练过程报维度不匹配的错误。
4.2 训练命令与超参选择
常用训练命令大概是这样的形式:
python train.py \ --data data.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --seed 42 \ --workers 4--weights yolov5s.pt用预训练权重做迁移学习,这个选择对夜间数据集意义很大。COCO 预训练模型里面已经包含了大量白天车辆的纹理、轮廓特征,夜间图像虽然光照分布不同,但车辆的几何结构是相似的,微调起来比从头训练快得多。--batch-size 16在单张 12GB 显存的卡上是 safe 的,如果显存小就降到 8,不要为了调大 batch 把--imgsz从 640 降到 416,夜间车辆的细节本来就少,降低输入分辨率会进一步恶化小目标检测。
--workers 4是数据加载线程数,Windows 上设置过高有时会报 DataLoader worker 相关的错误,Linux 上一般没这个问题。如果你用的是 Windows 环境,建议先设 2。
4.3 夜间场景的数据增强参数
YOLO 训练默认启用了 mosaic 和 hsv 增强,但夜间图像直接套默认参数容易出问题。mosaic 会把四张图拼在一起训练,如果其中两张是夜间图像、两张是白天图像,模型被迫学习“同一场景下两种光照的混合特征”,对小目标检测的副作用比较明显。我的常规操作是关掉 mosaic 的后半段训练:
mosaic: 0.5 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4在 train.py 里把 mosaic 概率从默认的 1.0 降到 0.5,让模型最后三十轮不再用 mosaic,专攻真实分布。hsv_v控制在 0.4 以内,太大容易把夜间图像的暗部细节彻底洗掉。这些参数不是玄学,是在夜间数据上调出来的经验值。
4.4 loss 曲线怎么看
训练开始后重点关注三个 loss:box_loss、obj_loss、cls_loss,以及验证集上的 mAP。理想曲线是三支 loss 前 20 轮快速下降,之后进入缓慢收敛,同时 mAP 稳步上升。如果遇到 box_loss 下降但 obj_loss 纹丝不动,大概率是正负样本比例失衡,夜间图像里背景区域占比大,置信度损失主导了梯度方向,这时候考虑加--cls 1.5 --obj 1.5提高类别和置信度的损失权重。
训练日志里还有一个容易被忽略的指标是P(precision)和R(recall)。夜间车辆检测的典型情况是 R 偏低、P 偏高,因为模型宁可漏检也不误检,这在自动驾驶场景是正确倾向,但在评测时 mAP 会不好看。判断模型好坏不能只看 mAP 数字,要结合场景需求看 P/R 的取舍。
5. 夜间训练的常见问题排查:漏检、误检、不收敛的三类翻车
这个章节是我在实际跑夜间数据集时踩过的坑汇总,每一条都标记了现象、原因和解决路径,照着对号入座就行。
5.1 现象:白天模型正常,夜间图片大量漏检
原因:训练集和测试集的光照分布差异太大,模型在白天样本上学到的纹理特征在夜间失效。这是域偏移问题,不是模型结构的问题。
解决:第一优先是往训练集补充夜间图像,这组数据集本身就是为这个场景准备的。第二是在数据增强里加入亮度抖动和随机对比度调整,源码里可以用hsv_h、hsv_s、hsv_v参数模拟更多光照条件。第三是考虑用 SAHI 这类切片推理库,把大图切成小块分别检测,夜间小目标的信噪比太低,切片后目标面积占比变大,漏检率明显下降。
5.2 现象:车灯区域被识别成两个独立目标
原因:夜间图像里车灯的高亮区域和车身轮廓形成强对比,YOLO 的特征图把车灯当成了前景边缘,导致一个车辆目标被拆成多个检测框。另一个常见原因是标注时没有规范“被遮挡车辆”的框选规则,有的标注员把露出的车灯部分单独画了一个框。
解决:先检查数据集里是否存在车灯单独成框的脏标注,如果存在直接删除或合并。训练层面把 NMS 的 iou 阈值从 0.45 调到 0.5,让重合度高的框更容易被抑制。YOLOv8 里可以调--iou-thres,YOLOv5 则在 val.py 里改iou参数。实测发现这个操作能让车灯误检降低三成左右。
5.3 现象:loss 在前 10 轮不降或直接爆炸
原因:学习率设置过高,或者预训练权重和当前数据集类别数不匹配。新手经常犯的错是不改默认学习率 0.01 直接套用,YOLOv5s 在小数据集上这个值偏高。另一个常见原因是nc写错,导致分类头输出维度和权重不一致,loss 计算时出现 NaN。
解决:先检查模型输出的日志里有没有nan。如果是类别数不匹配,改 data.yaml 后重新加载。如果是学习率问题,把初始学习率降到 0.001,或启用 warmup 让前 3 轮学习率从 0 线性升到目标值。还有一个笨办法,冻结 backbone 前 20 轮只训练检测头,--freeze 10在 YOLOv5 里可以冻结前 10 层,这招对不收敛问题非常有效。
5.4 现象:mAP 忽高忽低,同参数重复训练结果差异大
原因:随机种子没有固定,或者训练集太小导致验证集波动。1000 张图里 200 张验证,如果某个类别只出现在几十张图里,评估结果对验证集的构成极度敏感。另一个原因是数据加载时 shuffle 的顺序不同,导致每轮迭代看到的样本分布不同。
解决:训练命令里固定--seed 42(YOLOv5 支持,v8 要用--seed参数显式传入),同时把验证集固定为划分脚本生成的那一份,不要每次重新划分。我在实际项目里还会连续跑三次训练,取 mAP 的中位数作为模型的真实水平,单次训练的 mAP 浮动超过 0.05 是常态。
5.5 现象:验证集 mAP 高但实际路测效果差
原因:验证集和训练集出自同一个采集批次,存在“同源偏差”。夜间车辆数据很容易出现这种情况——训练集和验证集都来自同一段路的同一时间段,光照条件、车型分布高度相似,模型学到的其实是场景特征而不是车辆特征。
解决:这是最麻烦的一种翻车,因为数字上完全看不出问题。能做的补救是再采集一批不同路段、不同时间段的数据单独做测试集;如果采集成本太高,至少把当前数据集中光照最极端的图(比如灯光过曝、几乎全黑)抽出来手动组成一个 hard test set,单独统计这些图上的 mAP,和总 mAP 对比。两者差距超过 0.15 就说明模型的泛化能力没有数字表现的那么好。
6. 训练完之后怎么算账:mAP 验证和夜间专项评估
训练跑完不是终点,验证环节才是决定模型能不能用的关键,尤其是夜间场景,不能只看一个总的 mAP 数字。我的习惯是单独拉一张夜间夜间测试集,白天和夜间的 mAP 分开统计。如果两者差距过大,说明模型在光照迁移上是脆弱的。
python val.py \ --data data.yaml \ --weights runs/train/exp/weights/best.pt \ --conf-thres 0.25 \ --iou-thres 0.5conf-thres是置信度阈值,默认 0.25 在夜间场景通常偏低——夜间误检率高,我会把它提到 0.35 再看一次结果。iou-thres是 NMS 的 IoU 阈值,检测密集车辆时调低这个值能减少相近框的合并,代价是可能出现同一辆车输出两个框。这两个参数的调优不会改变模型本身,但会直接影响实际部署时的用户体验。
val.py 跑完后会输出P、R、mAP@0.5、mAP@0.5:0.95四组关键数字。夜间场景里mAP@0.5比mAP@0.5:0.95更有指导性,因为夜间图像的标注框边界本身就有一定模糊度,苛刻的 IoU 阈值会放大标注噪声的影响。
验证之后顺手做一步:导出 ONNX 模型做端侧推理测试。YOLOv5 用export.py导出:
python export.py --weights runs/train/exp/weights/best.pt --include onnx --simplify导出的 ONNX 用onnxruntime跑同一张夜间图片,比对推理结果和 PyTorch 输出的一致性。这一步能提前暴露模型量化或算子兼容的问题,不至于部署到边缘设备上才发现掉精度。
从那以后,我每次训练夜间检测模型都会强制走一遍固定 seed、固定验证集、单独统计夜间结果这几个步骤。这套数据包里最值钱的不是那一千张图,而是它把工作流的前半段——标注格式、数据划分、环境配置——都替你铺好了,让你能把精力全放在训练和调优上。希望这些拆包和踩坑的记录能帮到你,少走我当年走过的弯路。
本文还有配套的精品资源,点击获取