简介:这份交通道路物体图像目标检测数据集面向计算机视觉初学者与YOLO实战开发者,用于训练和验证道路场景下的多类别目标检测模型。数据已完成标注,采用YOLO格式,涵盖汽车、警告标志、红色交通灯等11个类别,并预先划分训练集与验证集,可直接投入模型训练流程。压缩包共约2000个文件,以1420个txt标注文件、579张jpg图像及1个Python脚本为主,整体约231.32MB,其中脚本可用于数据可视化,方便快速查看标注效果。目前已有40人学习下载。借助该数据集,读者可省去繁琐的标注与划分工作,直接开展YOLOv5等检测网络的训练与调参实验,并结合作者主页的改进实战内容,进一步理解数据增强、类别分布与模型评估等环节,适合作为课程设计、毕业设计或算法入门的实操素材。
1. 拿到 1400 张交通道路 YOLO 标注数据,先别急着 train
手上有一份约 1,400 张的交通道路物体图像目标检测数据,标注已经是 YOLO 格式,这件事本身就省掉了最磨人的一环。做过检测项目的人都清楚,模型结构、损失函数、学习率这些还能靠调参和预训练权重兜底,真正拖垮进度的是标注:画框、对齐类别、处理漏标误标,一套流程走下来几天就没了。所以当你拿到一份「已标注、YOLO 格式、约 1,400 张」的道路场景数据时,第一反应不该是马上model.train(),而是先搞清楚它到底能不能直接喂给 YOLO 系列模型、类别分布是否均衡、坐标有没有越界、划分比例怎么定。
这份数据面向的是交通道路场景下的目标检测任务,典型目标包括车辆、行人、骑行者、交通标志、锥桶等路面常见物体。它适合三类人:一是刚入门目标检测、想找一个真实场景数据集跑通全流程的新手;二是做智慧交通、道路巡检、车载感知方向、需要快速验证算法可行性的工程师;三是想拿现成标注数据做 YOLO 改进实验(比如换 head、加注意力、试 NWD 损失)但不想自己标数据的研究者。1,400 张不算大,但足够跑通一次完整的训练-验证-推理闭环,也足够暴露数据层面的坑。下面按「先验证数据 → 再转格式/划分 → 配环境训练 → 排错 → 进阶」的顺序讲透。
2. 先验证再训练:YOLO 标注格式与 1400 张数据的体检方法
2.1 YOLO 标注格式到底长什么样
YOLO 检测格式的核心是「一图一 txt」。每张xxx.jpg对应一个同名xxx.txt,txt 里每一行代表一个目标框,格式固定为:
<class_id> <x_center> <y_center> <width> <height>五个字段全部用空格分隔,后四个是归一化到 0~1 的浮点数,x_center/y_center是框中心点相对整图宽高的比例,width/height是框宽高相对整图宽高的比例。class_id是从 0 开始的整数,对应一个classes.txt或data.yaml里的names列表。这里有两个新手最容易翻车的点:一是坐标必须是归一化值而不是像素值,二是类别 id 必须从 0 连续编号,中间断号会让模型把某个类别当成背景。
和 VOC 的 XML、COCO 的 JSON 相比,YOLO 格式最轻量,读取快、解析简单,代价是它不存图像尺寸、不存类别名,类别语义完全靠外部配置文件维护。所以拿到一份 YOLO 标注数据,第一件事是确认它配套的类别定义文件在不在、类别顺序对不对。
2.2 用一段脚本给数据做全面体检
在划分和训练之前,我一般会先跑一个体检脚本,把图像数量、标注数量、类别分布、坐标合法性、空标注、孤立文件一次性查清楚。下面这段可以直接抄:
import os from collections import Counter img_dir = "images" # 图片目录 lbl_dir = "labels" # 标注目录 img_exts = (".jpg", ".jpeg", ".png", ".bmp") imgs = [f for f in os.listdir(img_dir) if f.lower().endswith(img_exts)] img_stems = {os.path.splitext(f)[0] for f in imgs} lbl_stems = {os.path.splitext(f)[0] for f in os.listdir(lbl_dir) if f.endswith(".txt")} print("图片数:", len(img_stems)) print("标注数:", len(lbl_stems)) print("有图无标注:", len(img_stems - lbl_stems)) print("有标注无图:", len(lbl_stems - img_stems)) cls_counter = Counter() bad_lines = 0 empty_files = 0 box_total = 0 for stem in img_stems & lbl_stems: path = os.path.join(lbl_dir, stem + ".txt") with open(path, "r", encoding="utf-8") as f: lines = [ln.strip() for ln in f if ln.strip()] if not lines: empty_files += 1 continue for ln in lines: parts = ln.split() if len(parts) != 5: bad_lines += 1 continue cid = int(float(parts[0])) vals = [float(v) for v in parts[1:]] # 坐标必须在 0~1 之间,宽高必须为正 if any(v < 0 or v > 1 for v in vals) or vals[2] <= 0 or vals[3] <= 0: bad_lines += 1 continue cls_counter[cid] += 1 box_total += 1 print("总框数:", box_total) print("空标注文件:", empty_files) print("非法行:", bad_lines) print("类别分布:", dict(sorted(cls_counter.items())))逻辑说明:脚本先比对图片和标注的文件名主干,找出「有图无标注」和「有标注无图」两类不一致,这是数据整合时最常见的脏数据。然后逐行解析标注,检查字段数是否为 5、坐标是否落在 0~1、宽高是否为正,任何一条不满足就计入bad_lines。最后用Counter统计每个类别的框数量,直接暴露类别不均衡。
参数说明:img_dir和lbl_dir按你实际目录改;img_exts覆盖常见图片后缀,如果你的数据是.webp之类要补进去。跑完重点看三个数:有图无标注如果很多,说明标注不完整;非法行大于 0 必须逐条修,否则训练时 dataloader 会报错或静默丢弃;类别分布里如果某个类只有几十个框,后面要么过采样要么调损失权重。
2.3 类别分布和长尾问题怎么判断
1,400 张道路数据里,车辆、行人这类高频目标可能占八成以上,而交通标志、锥桶、骑行者往往是长尾。判断标准很简单:如果某个类别的框数低于总框数的 1%,或者绝对数量低于 100,就属于长尾类。长尾类在训练时会被高频类压制,表现为验证集上高频类 mAP 很高、长尾类几乎不召回。常见做法是:先按原始分布训一版作为 baseline,记录每个类的 AP;再针对长尾类做图像级过采样(把含长尾类的图复制多份)或调cls损失权重。不要一上来就大改,先拿到 baseline 才有对比。
3. 从原始数据到可训练集:划分、data.yaml 与目录结构
3.1 训练/验证/测试怎么切才不泄漏
数据划分看着简单,坑最多的是「同一段视频抽帧」造成的泄漏。如果这 1,400 张是从连续视频里抽的帧,相邻帧几乎一样,随机划分会让验证集里出现和训练集高度相似的图,指标虚高。判断方法:看文件名是否有连续编号或时间戳。如果是,按时间或编号顺序切,前 70% 训练、中间 15% 验证、后 15% 测试,保证同一段场景不跨集。
如果是独立拍摄的图片,随机划分即可,比例常用 7:2:1 或 8:1:1。1,400 张按 8:1:1 就是 1120/140/140,验证集 140 张对 mAP 评估偏少,波动大,可以改成 7:1.5:1.5。下面是一个按顺序切分的脚本:
import os, shutil, random src_img, src_lbl = "images", "labels" dst = "dataset" splits = {"train": 0.7, "val": 0.15, "test": 0.15} stems = sorted(os.path.splitext(f)[0] for f in os.listdir(src_img) if f.lower().endswith((".jpg", ".png", ".jpeg"))) # 若为视频抽帧,保持 sorted 顺序切分;若为独立图,取消下一行注释打乱 # random.seed(42); random.shuffle(stems) n = len(stems) n_train = int(n * splits["train"]) n_val = int(n * splits["val"]) assign = (["train"] * n_train + ["val"] * n_val + ["test"] * (n - n_train - n_val)) for stem, split in zip(stems, assign): for sub in ("images", "labels"): os.makedirs(os.path.join(dst, split, sub), exist_ok=True) shutil.copy(os.path.join(src_img, stem + ".jpg"), os.path.join(dst, split, "images", stem + ".jpg")) lbl = os.path.join(src_lbl, stem + ".txt") if os.path.exists(lbl): shutil.copy(lbl, os.path.join(dst, split, "labels", stem + ".txt"))逻辑说明:先收集所有图片主干名,按需打乱或保持顺序,再按比例算出每张图归属哪个 split,最后把图片和对应标注复制到dataset/{split}/images和dataset/{split}/labels。YOLO 官方要求 images 和 labels 目录结构平行,文件名主干一致,脚本严格遵循这一点。
参数说明:splits三个值加起来必须等于 1;random.seed(42)保证可复现;如果图片后缀不统一,把.jpg换成实际后缀或做后缀映射。注意n_val用int截断,最后 test 用总数减,避免因取整丢图。
3.2 data.yaml 的字段含义与常见写错
划分完要写data.yaml,这是 YOLO 训练的入口配置:
path: /abs/path/to/dataset train: train/images val: val/images test: test/images nc: 5 names: 0: car 1: pedestrian 2: cyclist 3: traffic_sign 4: conepath是数据集根目录,train/val/test是相对path的子路径,nc是类别数,names是 id 到类名的映射。三个高频错误:一是nc和names长度不一致,训练直接报错;二是names顺序和标注里的class_id对不上,模型学出来的类别全错位;三是路径写成相对当前工作目录而不是相对path,导致找不到图。改完 yaml 建议用python -c "import yaml;print(yaml.safe_load(open('data.yaml')))"打印一遍确认解析无误。
3.3 目录结构对照表
| 层级 | 路径示例 | 作用 |
|---|---|---|
| 根目录 | dataset/ | data.yaml 里 path 指向这里 |
| 训练图 | dataset/train/images/ | 训练集图片 |
| 训练标注 | dataset/train/labels/ | 与图片同名的 txt |
| 验证图 | dataset/val/images/ | 验证集图片 |
| 验证标注 | dataset/val/labels/ | 验证集标注 |
| 测试图 | dataset/test/images/ | 测试集图片 |
| 测试标注 | dataset/test/labels/ | 测试集标注 |
| 配置 | dataset/data.yaml | 类别与路径定义 |
目录结构一旦定好就不要中途改,否则缓存和断点续训都会出问题。
4. 环境配置与训练:把 1400 张数据真正跑起来
4.1 环境配置的最小步骤
目标检测环境最容易卡在 CUDA 和 PyTorch 版本匹配上。稳妥顺序是:先确认显卡驱动支持的 CUDA 上限,再装对应 CUDA 版本的 PyTorch,最后装 ultralytics。以常见组合为例:
# 1. 建虚拟环境,避免污染系统 Python conda create -n yolo python=3.10 -y conda activate yolo # 2. 装 PyTorch(CUDA 12.1 示例,按自己驱动改) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 # 3. 装 ultralytics pip install ultralytics # 4. 验证 GPU 是否可用 python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"逻辑说明:虚拟环境隔离依赖;PyTorch 用官方 index 保证 CUDA 版本对应;最后一行必须打印True和显卡名,否则后面训练会退回 CPU,1,400 张也能跑但慢到怀疑人生。参数说明:python=3.10是兼容性较好的版本,3.12 部分依赖还没跟上;CUDA 版本按nvidia-smi右上角显示的版本选,不要超过它。
4.2 训练命令与关键参数
环境通了就可以起训。以 YOLOv8/v11 系列为例:
yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ device=0 \ project=runs/traffic \ name=exp1逻辑说明:model=yolov8n.pt用官方预训练权重做迁移学习,1,400 张数据从零训几乎不可能收敛,必须用预训练。epochs=100配合patience=20,验证指标 20 轮不涨就早停,省时间。imgsz=640是检测任务默认输入尺寸,和大多数预训练权重匹配。
参数说明:batch按显存调,8G 显存用 16,12G 以上可以 32;lr0是初始学习率,迁移学习常用 0.01,如果 loss 一开始就炸到 nan,降到 0.001;device=0指定第一块 GPU,多卡用device=0,1。训练日志里重点盯box_loss、cls_loss和验证集的mAP50,前几轮 loss 下降是正常的,如果 10 轮后 mAP 还是 0,八成是类别或路径配错。
4.3 训练过程要看哪些指标
mAP50是 IoU 阈值 0.5 下的平均精度,最直观;mAP50-95更严格,反映框的定位质量。1,400 张数据、单类目标,mAP50跑到 0.8 以上算正常,长尾类可能只有 0.4~0.6。如果mAP50高但mAP50-95很低,说明框能框住但不够准,可以检查标注框是否偏大或偏小。precision和recall要一起看:precision 高 recall 低,说明模型保守漏检;反过来则是误检多。这些指标在runs/traffic/exp1/results.csv里都有,用 pandas 读出来画曲线比看日志高效。
5. 避坑与排查:1,400 张道路数据训练中最容易翻车的 5 个点
5.1 训练一开始 loss 就是 nan
现象:启动训练后前几个 iter 的box_loss直接变成 nan,之后全崩。原因:学习率过大,或标注里有坐标越界、宽高为负的脏数据,或图片本身损坏。解决:先把lr0降到 0.001 试;再跑第 2 章的体检脚本,把bad_lines全部修掉;最后用 PIL 批量打开图片,捕获打不开的损坏图并剔除。
5.2 验证集 mAP 一直是 0
现象:训练 loss 在降,但验证mAP50始终为 0。原因:data.yaml里names顺序和标注class_id对不上,或val路径写错导致验证集为空。解决:打印data.yaml确认路径存在且图片数不为 0;随机抽一张图,用脚本读出它的标注类别 id,和names对照,确认语义一致。
5.3 显存溢出 OOM
现象:训练中途报CUDA out of memory。原因:batch或imgsz超过显存。解决:先把batch减半,还不行就把imgsz从 640 降到 512 或 416;也可以开amp=True(默认开)用混合精度省显存。不要盲目加workers,那影响的是数据加载不是显存。
5.4 长尾类几乎不召回
现象:车辆行人检测很好,锥桶、交通标志基本检不到。原因:类别极度不均衡,长尾类样本太少。解决:先确认长尾类框数,低于 100 的做图像级过采样;再在训练时调cls损失权重,或改用带类别平衡的损失。不要直接删长尾类,那等于放弃这部分检测能力。
5.5 推理时框位置整体偏移
现象:训练指标正常,但拿新图推理时框整体偏上或偏左。原因:推理时的预处理(letterbox 填充)和训练不一致,或图片被 resize 时没保持长宽比。解决:统一用 ultralytics 的推理接口,它会自动做 letterbox;如果自己写预处理,务必保证填充后坐标能正确映射回原图。这个坑很隐蔽,指标看不出来,只有可视化才暴露。
6. 进阶:用这 1400 张数据做改进实验与效果验证
数据跑通之后,它的真正价值是当改进实验的试验台。1,400 张规模不大,单次训练在单卡上几十分钟到一两小时,适合快速迭代。我一般会固定一个 baseline(比如 yolov8n + 默认配置),记录mAP50、mAP50-95和每个类的 AP,然后每次只改一个变量:换 head、加注意力、换损失函数(比如把 CIoU 换成 NWD 处理小目标)、调数据增强。改完和 baseline 对比,涨了才保留。
验证改进是否真实有效,别只看总体 mAP,要看分类别 AP 和混淆矩阵。道路场景里小目标(远处行人、锥桶)多,如果改进只提升了车辆这类大目标,对小目标没帮助,那对交通场景意义有限。可以专门统计小目标(框面积小于 32×32 像素)的 AP,作为辅助指标。
下面这段用来对比两次实验的分类别结果:
import pandas as pd # ultralytics 训练后会输出每个类的指标,这里以 results.csv 为例 df = pd.read_csv("runs/traffic/exp1/results.csv") df.columns = [c.strip() for c in df.columns] # 关注验证集指标列,列名随版本略有差异,按实际调整 cols = [c for c in df.columns if "mAP" in c or "loss" in c] print(df[cols].tail(10)) # 分类别 AP 一般在 val 阶段打印,可保存日志后解析 # 重点比较 baseline 与改进版在同一验证集上的每类 AP 差值逻辑说明:读训练输出的 csv,取最后若干轮看指标是否收敛;分类别 AP 需要从验证日志里解析,重点是做 baseline 和改进版的逐类对比,而不是只看一个总数。参数说明:列名随 ultralytics 版本会变,用df.columns先打印确认;对比时务必用同一个验证集和同一套评估参数,否则数字没有可比性。
一个我踩过的习惯性教训:每次改完配置一定把data.yaml、训练命令、随机种子一起记下来,否则过两天复现不出当时的结果,只能重训。1,400 张数据不大,但实验一多,没有记录就是一团乱麻。把 baseline 和每次改动当成一次对照实验来做,这份数据能带给你的就不只是「跑通」,而是真正能支撑选型的结论。希望帮到你。
本文还有配套的精品资源,点击获取