简介:面向智能小车赛道自动驾驶场景,这份目标检测数据集按YOLOv5目录规范整理,包含左转、右转、红灯、绿灯、人行道等8类交通指示牌标注,共901张训练图与225张验证图,每张图均配有txt标签。图像分辨率为200×120,适合模型快速迭代与小车端部署验证。资源总量约10.31MB,总计2000个文件,以jpg图像、txt标签为主,附带类别字典txt文件和一个可直接运行的Python可视化脚本,便于随机抽取图片绘制边界框、检查标注效果。目前已有151人学习下载。对于正在做自动驾驶竞赛、智能小车课程设计或YOLOv5目标检测入门实践的读者,该数据包免去自行采集与转换格式的麻烦,目录结构清晰,训练集/验证集划分完备,开箱即用。结合可视化脚本,可快速核对标注质量,有效提升项目起步效率。
1. 目标检测数据集(YOLOV5目录格式)到底解决的是什么事
我见过不少做智能小车赛道自动驾驶的团队,第一反应是拿通用目标检测数据集练模型,结果一上小车就翻车。某高校车队A同学跟我抱怨过,模型在办公室测试集上mAP能到0.9,跑真实赛道时直行牌和左转牌傻傻分不清。问题不在模型,在数据域完全不同。这份“智能小车赛道自动驾驶、交通指示牌目标检测数据集(8类别)”之所以值得讨论,是因为它已经把赛道场景下的图片、标签、目录结构全部按YOLOV5的要求整理好了,拿到手不用改格式就能直接开训。打开压缩包,里面有images和labels两个平级目录,按照train/val切好,再写一个data.yaml,就能跑到YOLOV5训练脚本里。适合谁呢?做智能车竞赛、课程设计、低速园区自动驾驶验证的开发者,尤其是没时间自己采集标注、又不想在格式转换上折腾的人。
2. 赛道场景和通用数据集的数据鸿沟:为什么不能拿现成的凑合
2.1 从相机视角看,小车赛道目标到底难在哪
通用自动驾驶数据集里的交通标志是平视视角,相机装在车顶,目标位于画面中部,周围是开阔的路面。智能小车完全不是一回事。小车的摄像头高度通常只有10到20厘米,离地近,视角低矮,看到的指示牌是仰角;镜头为了兼顾赛道线和远处路况,往往配广角,导致画面边缘的牌子被拉变形。更要命的是,小车跑起来速度虽然不快,但车身轻、抖动大,直行牌在画面里可能只出现两三帧,运动模糊非常常见。
更麻烦的是背景。赛道场景里地面有引导线、挡板、锥桶,光照条件从室内灯光到室外太阳直射都有,指示牌本身的颜色在强光下会过曝,反光严重时牌面内容几乎看不见。通用数据集里那种标准化的、清晰可读的交通标志,在实际赛道里是少数。所以如果直接拿通用数据集训练,模型学到的是“一个规则的、清晰的牌面”,到了赛道上遇到歪的、糊的、偏色的牌子,自然会漏检误检。这属于典型的数据域gap,不是调参能补回来的。
这也是为什么这个方向要单独做数据集、单独训练。赛道自动驾驶检测模型要学的不是通用交通标志分类,而是“在这个特定视角、特定光照、特定背景下的交通指示牌”,并且要能从一张连续视频帧里稳定检出来,丢一帧就可能导致小车决策出错。看一个数据集好不好,不能只看类别数,要看它的图片是不是贴近你实际部署时的相机视角。标注再准确,如果视角不对,训练出来的模型也只是“看起来很准”。
2.2 8个类别与实际标注规范:正样本、负样本和框的取舍
这份数据集包含8个类别,对应小车跑赛道时真正会遇到的指示牌类型:直行、左转、右转、停车让行、限速、人行横道、施工警示和禁行。类别不多,但每一项都是决策节点。检测不只是要“看见牌子”,还要告诉控制模块“这条路接下来该直行还是转弯”。所以类别设计的核心原则是:只保留会影响驾驶决策的牌,冗余类别宁缺毋滥。比如“掉头”这种小车赛道里永远不会出现的牌,就不该放在类别清单里。
标注规范也是同样的逻辑。如果你自己采集标注,要求是:标注框只框牌面,不框立杆;遮挡面积超过50%的目标不标;小于30x30像素且完全无法辨认的不标;同一张图里同一个牌子只标一次,不重复框。这些规则看起来严格,但都是为了训练稳定性。框立杆会把背景噪声带进特征学习;小目标标太多,模型注意力会被稀释;重复框会让置信度得分变得不可信。
还有一个容易被忽略的点:要专门保留一部分“没有目标”的纯赛道背景图,作为负样本。放在val目录里,训练时模型能学到“没有牌子的时候不要乱报”。数据集如果所有图片都至少含一个目标,模型会倾向把什么区域都往目标上靠,误检率会特别难看。我建议负样本比例控制在总图片数的10%左右,太多会让模型变懒,太少则压不住误检。这份数据集如果已经按8类别整理好,那就直接拿来用;如果是你自己做数据集,这个比例是排过坑的。
3. 从目录树到训练管线:YOLOV5目录格式逐层拆解
3.1 标准目录结构:images、labels、train、val怎么对得上
YOLOV5要求训练数据和标签分别存放在images和labels两个目录下,各自再按train和val子目录划分。标签文件名必须和对应图片文件名完全一致,只是后缀从.jpg换成了.txt。目录长什么样:
dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── ... │ └── val/ │ ├── 000100.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 000001.txt │ │ └── ... │ └── val/ │ ├── 000100.txt │ └── ... └── data.yaml这里最值得注意的一点:YOLOV5在训练时会自动检查images和labels路径是否能对应上。它做的不是扫描目录里有多少个文件,而是遍历图片文件名,然后去labels目录里找同名txt。如果图片和标签的文件名前缀不一致,哪怕只差一个下划线,训练就会直接跳过对应的图,而且跑完之后不会明确告诉你哪些图被跳过了。这是新手最容易掉进去的坑。拿到数据集后,第一步永远是写个清单核对一遍,而不是直接开训。
关于划分比例,赛道类数据集样本量一般不大,40到200张图之间很正常,训练时train和val的比例可以按8:2或者9:1切。如果总数少,可以不用单独建test目录,直接复用val做最终评估就行。需要说明的是,YOLOV5的train.py默认只接收train和val两个目录,test是预测脚本用的,训练阶段不会用到。很多人在这里多建了一个test目录,结果训练时报路径错误,其实不是必需的。
3.2 data.yaml和txt标签内容:一个描述类别,一个描述坐标
data.yaml是连接数据集和模型的关键文件,内容非常简短。以这个8类别数据集为例,用它训练时配置是固定的:
train: dataset/images/train val: dataset/images/val nc: 8 names: ['straight', 'left', 'right', 'stop', 'speed_limit', 'crosswalk', 'construction', 'no_entry']四个字段分别表意:train和val给的是图片目录的路径,YOLOV5会根据图片路径自动推导对应的labels路径,规则是“把images替换成labels,把.jpg替换成.txt”。nc是类别总数,names是类别名称列表,顺序和编号必须与txt标签里的第一个数字一致。这里的顺序一旦定下来就不要动了,以后训练、验证、部署都得用同一份names。改动顺序等于把所有标签里对应的编号含义翻了个底朝天。
每个labels下的txt文件是纯文本,每一行对应一个目标,格式为:
0 0.48594 0.36268 0.22813 0.17236 3 0.81771 0.65104 0.08646 0.23438第一列是类别编号,从0开始计数,对应data.yaml里names的索引。中间两列是归一化后的目标中心x和y坐标,最后一列是归一化后的框宽度和框高度。归一化是指将原始像素坐标除以图片的宽度和高度。比如图片宽1280,某个目标中心的x像素坐标为622,那么归一化坐标就是622/1280=0.48594。这里必须强调,YOLOV5的txt里存的不是左上角坐标加宽高,该格式和COCO数据集不同,和VOC的XML表达也不同。直接把XML转成YOLO txt时,最容易出错的就是归一化这一步,很多人会忘了除以图片尺寸,把几百像素的坐标值直接写进txt,那训练时损失会直接飙到十几。
3.3 用一段Python脚本做数据集自检,打开压缩包先跑这个
拿到一个数据集,不管来源是什么,我习惯先写一段自检脚本,把格式问题一次性暴露出来。脚本做四件事:检查每个图片名是否在labels里有对应txt;检查txt里的坐标是否在0到1区间内;检查类别编号是否在nc范围内;统计每个类别的目标数量。脚本逻辑很长,但每段都需要说明:
import os from pathlib import Path data_root = Path("dataset") splits = ["train", "val"] for split in splits: img_dir = data_root / "images" / split lab_dir = data_root / "labels" / split img_files = list(img_dir.glob("*.jpg")) no_label, out_of_range, bad_cls = [], [], [] cls_counter = {} for img_path in img_files: lab_path = lab_dir / (img_path.stem + ".txt") if not lab_path.exists(): no_label.append(img_path.name) continue for line in lab_path.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: continue cls_id = int(parts[0]) cx, cy, w, h = map(float, parts[1:]) if cx < 0 or cx > 1 or cy < 0 or cy > 1 or w < 0 or w > 1 or h < 0 or h > 1: out_of_range.append((img_path.name, line)) if cls_id < 0 or cls_id >= 8: bad_cls.append((img_path.name, line)) cls_counter[cls_id] = cls_counter.get(cls_id, 0) + 1 print(f"[{split}] images: {len(img_files)}") print(" missing label:", no_label[:5], "..." if len(no_label) > 5 else "") print(" coord out of range:", out_of_range[:3]) print(" bad cls id:", bad_cls[:3]) print(" class distribution:", cls_counter)脚本先遍历jpg文件,拿stem去拼labels路径,检查是否存在;不存在就直接记录为missing label。坐标检查是数值层面的把关,把所有五个字段读出来,逐项判断是否在0到1之间,防止标签转换时像素坐标没归一化。类别编号检查则保证不会出现“txt里写了类别9但names只有8个”的错位。最后统计类别分布,这个输出最大的用处是用来判断要不要做类别重加权。如果直行牌占了全部目标的60%,限速牌只有3%,那训练时就要考虑用class weights或者多采集限速牌的样本。脚本本身不会修改任何文件,它只负责暴露问题。跑完之后如果missing label非空,先补标签或者删掉对应图片,否则那些图片会被训练流程静默跳过,你只会看到有效图片数变少,根本不知道少了谁。
4. 把数据集接进YOLOV5训练:模型选型、命令参数、输出解读
4.1 n/s/m到底选哪一档:小车部署是个实时性约束下的选择题
YOLOV5有n、s、m、l、x五档模型,从轻到重计算量递增量。智能小车赛道是个典型的嵌入式或边缘计算场景,主控板可能是Jetson Nano级别的ARM平台、RK3588之类的国产边缘设备,或者干脆是一块带NPU的开发板。算力有限,但又要满足实时检测需求,一般是10到30帧每秒就要完成整个检测管线。用x档模型在小车上推理,单帧耗时可能到几百毫秒,根本不具备可用性。
所以我的推荐是,先跑n或s档。n档模型最小,参数量低,在低算力平台上能达到较高的帧率,但小目标检测能力弱一些;s档比n档精度稳一点,推理耗时也多一点,适合有基本NPU加速条件的板子。m档除非你的设备算力特别强,否则在小车场景里属于“看起来高端但用不起”的选项。没有绝对的高低之分,就看你的推理框架能不能跑起来、帧率够不够。先训n档拿到基线,再换s档对比一次,如果精度提升明显且帧率仍能满足控制需求,再往上走。
这里有一个常见的误区:有人觉得检测不准就换更大的模型。在小车赛道这个任务里,目标类别少、场景相对固定,如果n档mAP只有0.5,换s档可能有效果,但远不如去排查数据质量、光照一致性、标注框精度来得快。数据层面的收益通常比模型层面的收益大一个量级。我自己做的时候,是先花大力气把数据集理干净,再回来选模型档位。不要一上手就抱着m档训,翻车概率很大。
4.2 训练命令与必调参数:epochs、batch、imgsz和那两个隐藏开关
目录结构没问题后,训练命令非常简单,难的是参数不是默认值。给出我常用的完整调用:
cd yolov5 python train.py \ --data /path/to/dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --workers 4 \ --device 0 \ --project runs/train_car \ --name v1_s \ --exist-ok参数里最值得解释的是img和batch。img=640是输入分辨率,意味着训练时所有图片会被等比缩放并填充到640x640。如果原始图片目标偏小,可以尝试img=960,小目标检测会明显改善,但训练和推理时间都会增加。小车赛道这类目标通常不小,640够了。batch大小取决于显存,一般显卡设置16或32。batch太小会导致训练震荡,太小了归一化层的统计量不稳定;batch太大对小数据集来说容易过早收敛到局部最优。epochs在赛道这个场景里,150轮属于“能看趋势”的量级,数据量小的时候300轮也常见,但要注意过拟合,观察val loss曲线是否在训练后期抬头。
还有两个容易被忽略的训练开关,常用做法是第一次训练时不加任何额外参数,直接让YOLOV5自己跑;但如果你想把赛道场景吃透,第二轮建议加上--multi-scale。它会随机在0.5到1.5倍之间缩放输入图片,等于免费做数据增强,对光照变化大的赛道场景特别有用。另一个是--cache,加了这个参数会把图片提前缓存到内存或磁盘,训练时不用每轮都重新读硬盘,能节省一部分时间。第一次训练不要用--cache,因为数据没验证过是否格式正确,早一点暴露问题比训练跑到一半再报错强。
4.3 训练输出怎么解读:先看混淆矩阵,再看PR曲线和小图
训练结束后,runs目录下会生成一批图表。新手最容易盯着loss曲线看,其实loss不是最该看的。第一优先是confusion_matrix.png,它告诉你模型到底把哪个类别的指示牌误认成了哪个类别。如果直行牌大量被识别成左转牌,说明这两个类别在视觉特征上有重叠,需要回头检查标注是否准确,或者给这两类加更多带角度的样本。第二是PR曲线,mAP@0.5的值就是曲线下的面积;赛道任务一般要求mAP@0.5达到0.85以上才算可用,低于0.7就要警惕是数据量不足还是标注问题。
第三个值得打开的是val_batch0_prediction.jpg之类的预测可视化图,实现在正常训练输出的images文件夹下。图表上每个目标上面会标类别和置信度,包括漏检的目标。看这张图的作用是确认“模型预测出来的框到底贴不贴牌面”。如果框明显偏大偏小,或者置信度很高但位置不对,大概率是标注框本身不准,而不是模型问题。
最后要注意一个细节,训练完的weights里会有best.pt和last.pt。best.pt是根据验证集上mAP最优保存的,last.pt是最后一个epoch的产物。部署和继续训练都用best.pt,不要拿last.pt做推理。这是个很小的习惯,但我见过有人拿错了权重文件,跑了半天效果不对,回来检查才发现是last.pt而不是best.pt,浪费一整轮调试时间。
5. 训练翻车现场:目标检测数据集的5个典型坑
5.1 标签坐标全变0:归一化只发生在条件分支里
现象:训练能正常启动,但train的loss下降极慢,曲线看起来像一条挣扎的曲线;进一步看标签,发现txt文件里所有坐标字段全是0。 原因:标签转换脚本里做归一化时,只在某个if分支里执行了除法,而这个分支由于文件名判断不准根本没进去。常见写法是把坐标除以图片尺寸,但图片是用OpenCV读的,读取失败返回None,除法直接报错或置0;用异常的代码吞掉了错误。 解决:先用第3.3节的脚本全量检查一遍,有0坐标的行先挑出来重标;然后在转换脚本里把归一化放到函数开头,不依赖文件路径条件;最后养成习惯,每转换一个文件就打印一行坐标,人工抽查中间值。
5.2 训练能跑但mAP为0:cls索引和labels编号错位
现象:训练过程正常,最后的PR曲线全在左下角,mAP@0.5打印出来是0.0几甚至直接为0。 原因:data.yaml里names的顺序和标注文件中的类别编号对应不上。比如标注脚本写的是第0类是左转,但data.yaml里第0类写成了直行,模型学到的类别名称和实际类别错了一半。严格来说模型学到了东西,只是名字标错了。 解决:每次训练前用脚本统计每个cls_id的目标数量,和8个类别核对,确保数量和语义能对上。最稳妥的做法是先固定names,再让标注脚本从names里读索引,而不是在标签文件里写死数字。
5.3 小目标漏检严重:用大目标的标准去标小牌子
现象:赛道远处的指示牌经常漏检,近处的没问题。置信度低的目标集中在画面下半部。 原因:标注人员会把很小的、模糊的牌也框进去,但这些目标可能只有20x20像素,特征根本不够。模型训练时被大量低质量小框带偏,反而忽略了大目标。另外,如果图片分辨率高但训练时被压到640,小目标的信息进一步丢失。 解决:给数据集设定最小像素阈值,小于30x30或20x20且不清晰的目标直接不标;在训练时开启--multi-scale,或者直接调高训练分辨率到960;如果条件允许,采集时尽量让图片里目标占更大比例。
5.4 验证集表现不错但实车冲出去:分时段光照差异没纳入训练
现象:数据集里全是白天强光下拍的牌子,模型在户外阴天或傍晚时直接漏检,小车冲过弯道。验证集mAP很高,但实车就是不行。 原因:训练集和验证集是从同一时间段采集的,光照分布一致,验证结果自然好看。赛道真实跑起来,光照随时间、天气变化很大。数据集图片域太单一,模型只会识别“强光下的牌面特征”。 解决:采集数据时按早中晚三个时段分别拍,把暗光、逆光、反光的图片都放进去;训练时开启--multi-scale和--hsv类的颜色增强参数;验证时单独留出一段“最难时段”的连续帧,专门用来测模型的边界。
5.5 类别不平衡到离谱:直行牌300张,限速牌只有10张
现象:混淆矩阵里限速牌的召回率特别低,很多被当成背景;只有直行牌和左转牌的mAP高。 原因:训练数据分布不均匀,模型把概率空间几乎全给了高频类别。这不是模型故障,是优化目标的正常现象。 解决:先统计类别分布,直接使用YOLOV5的--cls参数,比如--cls 0.6会在loss里放大分类损失的权重;更有效的方法是搞数据增强来扩量。对样本量少的类别,可以单独复制它们的样本做水平翻转、旋转、亮度扰动,凑到接近其他类别的一半数量,训练效果会比单纯加loss权重稳定。
6. 进阶:验证集怎么用才不算白标——阈值校准和赛道实测顺序
每份数据集做出来都要过验证关。我有两个习惯。第一个是在验证集上做置信度阈值扫描,而不是用默认的0.5。具体做法是写一个小脚本,用训练好的weights的best.pt跑val模式,把每张预测结果的置信度记录下来,然后从0.3到0.8每隔0.05扫一次,计算对应的precision和recall。目标是在这个数据集上找一个使得F1分数最高的阈值,实际部署推理时用它替代默认值。这样做的原因是赛道上经常出现“宁可不检,不可错检”的驾驶策略,阈值太高会漏牌子,太低会在赛道边沿乱报,阈值扫描能帮你直接找到平衡点。同类工作有人只看着验证集mAP就收工,实车一跑就发现置信度阈值完全不合理。
我的第二个习惯是:拿数据集里的连续视频帧做顺序测试,而不是随机抽样单张图片。小车是实时系统,上一帧漏检可以靠下一帧补回来,但如果连续10帧都漏检,车就可能在转弯前没有任何减速依据。所以我会挑一段没参与训练的长视频,逐帧跑一次模型,统计“漏检连续帧数”这个指标。如果某一段连续漏检超过5帧,就要回到数据层面看那个路段的视角和光照是否在训练集里有覆盖。数据集质量好不好,不看单帧mAP,看连续帧稳定性。
到了这一步,这套“8类别智能小车赛道交通指示牌数据集”才算真正用透了。我吃过亏的地方是,训练集里几乎全是大晴天拍的牌子,结果在阴天试车时多跑了十几米才识别到停牌,幸好速度不快,不然后果真的很严重。那次之后,我的所有项目里都把分时段数据采集写进了验收清单,不会再犯同一个错误。把数据集的格式、类别、验证流程都提前确认好,训练和部署反而成了最简单的一环。希望帮到你。
本文还有配套的精品资源,点击获取