简介:本资源面向目标检测初学者与需要真实道路场景数据的开发者,提供一套可直接用于YOLO系列训练的车辆目标检测数据集。数据均为真实道路场景采集,覆盖多种路况与光照条件,使用labelImg标注,标注框质量较高,并同时提供voc(xml)、coco(json)与yolo(txt)三种格式标签,分别存放于不同文件夹,便于直接接入不同训练框架。压缩包共约2000个文件,以1000个xml标注、991个txt标签为主,另含少量html教程、py划分脚本与yaml配置文件,整体约115MB。资源还附赠YOLO环境搭建、Windows与Linux训练教程,以及训练集、验证集、测试集划分脚本,可按需自行划分并生成ImageSets下的txt列表。目前已有491人学习下载,适合希望快速跑通车辆检测训练、验证模型效果或开展课程实践的用户参考使用。
1. 真实道路车辆检测数据集:1000 张图、三套标签、一条能跑通的训练链路
手上有个真实道路车辆目标检测数据集,1000 张图,带 VOC、COCO、YOLO 三种格式标签,外加划分脚本和训练教程——这个组合放在实际项目里,价值不在“数据量”上,而在“格式闭环”上。做过检测的人都知道,公开数据集下载下来最耗时的从来不是训练,而是标签格式对不上:你拿到的可能是 XML,但训练脚本要 TXT;你想用 COCO 预训练权重,但手头只有 VOC 标注。这套东西把三种格式和划分脚本一起给了,等于把“数据准备”这段最枯燥的路先铺平了。它适合谁?适合刚接触 YOLO 训练、想拿真实道路场景跑通全流程的人,也适合手头有自采数据、想参照一套标准目录结构来组织自己数据集的人。下面我按“先看懂标签、再跑通训练、最后避开翻车点”的顺序,把这条链路拆开讲。
2. 三种标签格式到底差在哪:VOC、COCO、YOLO 的坐标逻辑与选型
2.1 VOC 的 XML 结构:绝对坐标与 1-based 索引
VOC 格式每张图对应一个 XML 文件,核心信息在<object>节点里。它的坐标是绝对像素值,且以图片左上角为原点,xmin、ymin、xmax、ymax四个值直接对应框的边界。这里有个容易忽略的点:VOC 的坐标是 1-based,也就是说最小合法值是 1 而不是 0。很多转换脚本在处理边界框时如果按 0-based 写,会在图片最左或最上边缘产生 1 像素偏移,单张图看不出来,但训练时 mAP 会莫名低一两个点。
<annotation> <folder>images</folder> <filename>road_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>car</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>312</xmin> <ymin>450</ymin> <xmax>689</xmax> <ymax>702</ymax> </bndbox> </object> </annotation>上面这段 XML 里,<size>记录原图宽高,<name>是类别名,<bndbox>是框。difficult字段在训练时通常用来标记难以识别的目标,YOLO 训练中一般直接忽略或当作普通目标处理。truncated表示目标是否被截断,如果做数据增强时可以据此决定是否保留该样本。
2.2 COCO 的 JSON 结构:归一化与类别 ID 映射
COCO 格式把所有标注塞进一个 JSON 文件,结构分images、annotations、categories三块。坐标是[x, y, width, height],且是归一化到 0-1 之间的浮点数。这里最大的坑是类别 ID:COCO 的category_id不一定是连续的,官方 80 类里 ID 从 1 到 90 跳着来。如果你直接拿 COCO 的category_id当 YOLO 的类索引,训练时类别会错位,模型学出来的东西完全对不上。
{ "images": [{"id": 1, "file_name": "road_0001.jpg", "width": 1920, "height": 1080}], "annotations": [ { "id": 1, "image_id": 1, "category_id": 3, "bbox": [0.1625, 0.4167, 0.1964, 0.2333], "area": 49284.0, "iscrowd": 0 } ], "categories": [{"id": 3, "name": "car", "supercategory": "vehicle"}] }bbox里的四个值是归一化后的[x_center, y_center, width, height]吗?不是,COCO 的bbox是[x_min, y_min, width, height],且都是归一化值。这一点和 YOLO 的[x_center, y_center, width, height]不同,转换时必须先还原成绝对坐标再重新计算中心点。iscrowd为 1 表示该标注是密集人群或忽略区域,训练时通常跳过。
2.3 YOLO 的 TXT 结构:归一化中心点与类别索引
YOLO 格式每张图一个 TXT,每行一个目标,格式是class_id x_center y_center width height,全部归一化到 0-1。class_id从 0 开始连续编号,这是和 COCO 最大的区别。YOLO 不存图片尺寸,所以转换时你必须自己从原图读取宽高来做归一化,否则框会全部错位。
0 0.2604 0.5333 0.1964 0.2333 1 0.7125 0.4889 0.1250 0.1778上面第一行表示类别 0,框中心在图片宽度的 26.04%、高度的 53.33% 处,框宽占图片宽度的 19.64%,框高占高度的 23.33%。第二行类别 1,以此类推。YOLO 的 TXT 文件必须和图片同名同目录,或者按images/和labels/分开存放,具体取决于训练框架的配置。
2.4 三种格式的选型建议与转换优先级
如果你用的是 YOLOv5 或 YOLOv8,直接选 YOLO 格式,省去转换步骤。如果你要用 MMDetection 或 Detectron2,COCO 格式更顺手。VOC 格式现在主要用于兼容旧代码或某些特定评估脚本。这套数据集同时给了三种格式,我的建议是:训练用 YOLO 格式,评估用 COCO 格式(因为 pycocotools 的评估指标更细),归档用 VOC 格式(XML 可读性好,方便人工核对)。转换优先级上,先从 VOC 转 YOLO 最稳,因为 VOC 的绝对坐标最直观,转换逻辑简单,出错容易排查。
3. 用划分脚本切分训练集与验证集:比例、随机种子与类别均衡
3.1 划分脚本的核心逻辑与参数说明
划分脚本通常做三件事:读取所有图片文件名、按比例随机分配到 train 和 val、把对应的标签文件也复制或移动到对应目录。下面是一个典型的 Python 划分脚本,我加了详细注释。
import os import random import shutil from pathlib import Path # 参数区:按实际路径修改 IMAGE_DIR = Path("datasets/images") # 所有图片存放目录 LABEL_DIR = Path("datasets/labels") # 所有 YOLO 标签存放目录 OUTPUT_DIR = Path("datasets/split") # 划分后输出根目录 TRAIN_RATIO = 0.8 # 训练集比例 VAL_RATIO = 0.2 # 验证集比例 SEED = 42 # 随机种子,保证可复现 random.seed(SEED) # 收集所有图片文件名(不含扩展名) image_files = [f.stem for f in IMAGE_DIR.glob("*.jpg")] image_files.sort() # 排序后再打乱,避免文件系统顺序影响 random.shuffle(image_files) # 计算切分点 total = len(image_files) train_count = int(total * TRAIN_RATIO) train_list = image_files[:train_count] val_list = image_files[train_count:] # 创建输出目录 for split in ["train", "val"]: (OUTPUT_DIR / split / "images").mkdir(parents=True, exist_ok=True) (OUTPUT_DIR / split / "labels").mkdir(parents=True, exist_ok=True) # 复制文件 def copy_files(file_list, split): for name in file_list: src_img = IMAGE_DIR / f"{name}.jpg" src_lbl = LABEL_DIR / f"{name}.txt" dst_img = OUTPUT_DIR / split / "images" / f"{name}.jpg" dst_lbl = OUTPUT_DIR / split / "labels" / f"{name}.txt" shutil.copy2(src_img, dst_img) if src_lbl.exists(): shutil.copy2(src_lbl, dst_lbl) else: print(f"警告:{name}.txt 标签缺失") copy_files(train_list, "train") copy_files(val_list, "val") print(f"总图片数:{total},训练集:{len(train_list)},验证集:{len(val_list)}")这段脚本的关键参数是TRAIN_RATIO和SEED。TRAIN_RATIO设 0.8 是常见做法,1000 张图的话训练集 800 张、验证集 200 张。SEED必须固定,否则每次运行划分结果不同,实验无法复现。脚本里先sort()再shuffle()是为了消除文件系统读取顺序的影响,这个细节很多人忽略,导致换台机器跑出来的划分结果不一样。
3.2 类别均衡检查:别让验证集里没有某类目标
随机划分有个隐患:某些稀有类别可能全部被分到训练集,验证集里一个都没有,导致评估时该类别的 AP 无法计算。1000 张图里如果某个类别只有几十个实例,这个问题出现的概率不低。检查方法很简单:统计训练集和验证集里每个类别的实例数。
from collections import Counter def count_classes(label_dir): counter = Counter() for txt_file in Path(label_dir).glob("*.txt"): with open(txt_file, "r") as f: for line in f: class_id = int(line.strip().split()[0]) counter[class_id] += 1 return counter train_counter = count_classes(OUTPUT_DIR / "train" / "labels") val_counter = count_classes(OUTPUT_DIR / "val" / "labels") print("训练集类别分布:", dict(train_counter)) print("验证集类别分布:", dict(val_counter)) # 检查验证集是否有类别缺失 all_classes = set(train_counter.keys()) | set(val_counter.keys()) for cls in all_classes: if val_counter.get(cls, 0) == 0: print(f"警告:类别 {cls} 在验证集中没有实例")如果发现验证集缺失某类,解决办法有两个:一是换随机种子重新划分,二是手动把训练集里包含该类别的图片挪几张到验证集。我一般会先跑三次不同种子,选一个类别分布最均匀的。
3.3 划分后的目录结构验证
划分完成后,目录结构应该是这样的:
datasets/split/ ├── train/ │ ├── images/ (800 张 jpg) │ └── labels/ (800 个 txt) └── val/ ├── images/ (200 张 jpg) └── labels/ (200 个 txt)验证方法:分别统计images和labels目录下的文件数量,两者必须一致。如果 labels 比 images 少,说明有图片没有对应标签,训练时框架会报错或跳过。用一行命令就能查:
# Linux/macOS echo "train images: $(ls datasets/split/train/images | wc -l)" echo "train labels: $(ls datasets/split/train/labels | wc -l)" echo "val images: $(ls datasets/split/val/images | wc -l)" echo "val labels: $(ls datasets/split/val/labels | wc -l)"四个数字应该两两相等。如果不相等,回到 3.1 的脚本里看警告信息,找到缺失的标签文件。
4. 从零跑通 YOLO 训练:配置文件、超参与训练命令
4.1 数据集 YAML 配置文件的写法
YOLOv5 和 YOLOv8 都用一个 YAML 文件来描述数据集路径和类别。这个文件必须放在项目根目录或指定路径下,内容如下:
# road_vehicle.yaml path: /home/user/datasets/split # 数据集根目录,绝对路径 train: train/images # 训练集图片路径,相对于 path val: val/images # 验证集图片路径,相对于 path nc: 2 # 类别数,根据实际修改 names: # 类别名称列表,顺序必须和 class_id 对应 0: car 1: buspath建议写绝对路径,避免训练时工作目录变化导致找不到数据。nc是类别数,必须和 YOLO 标签里的最大class_id加一相等。names的顺序就是class_id的顺序,写反了模型会把 car 认成 bus。如果类别多,可以用列表形式:
names: - car - bus - truck - motorcycle4.2 训练命令与关键超参数
YOLOv8 的训练命令最简洁:
yolo detect train \ data=road_vehicle.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/train \ name=road_vehicle_exp1逐参数说明:data指向 4.1 的 YAML 文件;model是预训练权重,yolov8n.pt是最小的 nano 版本,1000 张图用 nano 或 small 就够,用 large 容易过拟合;epochs=100是训练轮数,1000 张图通常 50-100 轮收敛;imgsz=640是输入分辨率,如果原图是 1920x1080,缩放到 640 后小目标会变模糊,可以考虑 960 或 1280,但显存占用会翻倍;batch=16根据显存调整,8G 显存跑 640 分辨率大概能到 16;lr0=0.01是初始学习率,YOLOv8 默认用 SGD 时 0.01 是常用值;patience=20表示 20 轮验证指标不提升就早停,省时间。
YOLOv5 的命令类似,但参数名略有不同:
python train.py \ --data road_vehicle.yaml \ --weights yolov5s.pt \ --epochs 100 \ --img 640 \ --batch 16 \ --project runs/train \ --name road_vehicle_exp14.3 训练过程监控与日志解读
训练开始后,控制台会输出每轮的指标。重点看三个:box_loss、cls_loss、mAP@0.5。box_loss是边界框回归损失,正常应该持续下降;cls_loss是分类损失,同样下降;mAP@0.5是 IoU 阈值 0.5 时的平均精度,应该上升。如果box_loss下降但mAP不升,可能是学习率太大或数据标注有问题。如果cls_loss震荡不降,检查类别是否均衡。
训练日志会保存在runs/train/road_vehicle_exp1/下,包括results.csv、weights/best.pt、weights/last.pt。best.pt是验证集 mAP 最高的权重,推理时用这个。results.csv可以用 pandas 读取画曲线:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/train/road_vehicle_exp1/results.csv") df.columns = df.columns.str.strip() # 列名可能有空格 plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(df["epoch"], df["train/box_loss"], label="train box loss") plt.plot(df["epoch"], df["val/box_loss"], label="val box loss") plt.legend() plt.subplot(1, 2, 2) plt.plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP@0.5") plt.legend() plt.savefig("training_curve.png")如果train/box_loss持续下降但val/box_loss先降后升,说明过拟合了,解决办法是增加数据增强、减少模型参数量或提前停止。
5. 避坑与排查:标签转换、显存、类别错位的血泪经验
5.1 现象:训练 loss 正常但 mAP 始终为 0
原因:YOLO 标签的class_id从 1 开始写了,而 YOLO 要求从 0 开始。或者类别名称和 ID 映射反了。解决:打开任意一个 TXT 标签,确认第一列数字是否从 0 开始且最大值小于nc。如果是 VOC 转过来的,检查转换脚本里有没有做class_id - 1。
5.2 现象:训练到一半报 CUDA out of memory
原因:batch或imgsz设太大,或者验证时没有释放显存。解决:先把batch减半,如果还报错就把imgsz从 640 降到 416。另外 YOLOv8 默认在训练结束后跑验证,如果显存紧张可以加val=False跳过训练中验证,但这样就没有best.pt了,不推荐。
5.3 现象:验证集 mAP 很高但实际推理时框全错
原因:推理时的图片预处理和训练时不一致。比如训练用了 letterbox 填充,推理时直接 resize,导致坐标映射错位。解决:用框架自带的推理接口,不要自己写预处理。YOLOv8 用yolo detect predict model=best.pt source=test.jpg,YOLOv5 用detect.py,它们内部会做和训练一致的预处理。
5.4 现象:某些类别完全检测不到
原因:该类别实例数太少,或者验证集里没有该类导致早停时选了不包含该类的权重。解决:先按 3.2 检查类别分布,如果某类实例少于 50 个,考虑用数据增强( mosaic、mixup )或过采样。另外patience设太小可能导致模型还没学好稀有类就停了,可以适当增大到 30-50。
5.5 现象:VOC 转 YOLO 后框整体偏移
原因:VOC 坐标是 1-based,转换时没有减 1,或者归一化时用了错误的宽高(比如用了缩放后的尺寸而不是原图尺寸)。解决:转换脚本里先读原图width和height,用(xmin - 1) / width计算归一化中心点 x,(xmax - 1) / width计算归一化宽度。减 1 这一步不能省。
6. 用 1000 张图验证训练是否真的收敛:三个可复现的检查技巧
训练跑完不是终点,你得确认模型是真的学到了东西,而不是靠数据泄露或过拟合刷出来的指标。第一个技巧是单图推理对比:从验证集里挑 10 张图,用best.pt推理,把预测框和真实框画在同一张图上,肉眼看有没有系统性偏移。如果所有框都往一个方向偏,说明坐标转换有问题;如果框大小对但类别错,说明类别映射有问题。
from ultralytics import YOLO import cv2 model = YOLO("runs/train/road_vehicle_exp1/weights/best.pt") results = model("datasets/split/val/images/road_0001.jpg") for r in results: im_array = r.plot() # 绘制预测框 cv2.imwrite("pred_road_0001.jpg", im_array)第二个技巧是混淆矩阵检查:YOLOv8 训练结束后会自动生成confusion_matrix.png,在runs/train/road_vehicle_exp1/下。看对角线是否明显深于其他格子。如果 car 和 bus 之间有很多误判,说明这两个类在特征上太接近,需要增加区分性强的样本或调整类别定义。注意热词里提到的“yolo混淆矩阵总合不唯一”问题——如果矩阵行和列的总和不一致,通常是验证时conf阈值和iou阈值设置不同导致的,统一用默认值即可。
第三个技巧是小目标专项验证:从验证集里筛出所有包含小目标(框面积小于 32x32 像素)的图片,单独跑一遍推理,统计召回率。如果小目标召回率低于 0.3,说明imgsz=640不够,需要提高到 960 或 1280 重新训练。1000 张图里如果小目标占比高,这一步不能省。
我自己的习惯是:每次训练完先跑单图推理,再跑混淆矩阵,最后跑小目标统计。三个都过了才认为模型可用。这套流程帮我省了很多次“指标好看但实际不能用”的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取