☰
自动驾驶多类别交通目标检测数据集处理与YOLO训练实战
2026/9/28 7:22:35 网站建设 项目流程

简介:一份面向自动驾驶感知、智能交通与车载高级驾驶辅助系统开发者的多类别交通目标检测数据集,覆盖汽车、公交车、警车、救护车、交通标志、行人、自行车、摩托车等30类核心交通要素,场景涵盖日间/夜间、城市/山区、静态/动态,并纳入东南亚特色交通工具,可用于L2-L4级自动驾驶多目标实时检测、交通流量监控、异常事件识别等模型训练。资源包共2000个文件,以JPG图像与TXT标注文件为主,配套YOLO格式的yaml配置及docx说明文档,整体约65.55MB。已有75人学习下载。数据集按训练802张、验证229张、测试114张划分,标注经专业团队校验,对交通标志定位精确、车辆重叠场景标注完整;可直接适配YOLO等主流目标检测框架,支持生成交通要素分布热力图、识别特殊车辆优先级、开展复杂场景鲁棒性研究,便于从算法开发到工程落地快速验证。

1. 下载了这个数据集,先别急着喂给检测模型

拿到「自动驾驶多类别交通目标检测数据集7.zip」这类压缩包,第一反应不是解压后立刻开训。多类别交通目标检测是自动驾驶感知里最吃数据质量的环节,压缩包里几十个类别、几万张图,如果直接丢进 YOLO 训练,大概率会在类别 ID 错位、标注越界、小目标丢失这些坑里翻车。这份笔记按我平时处理这类数据集的工作流来写:从解压看结构、跑质检脚本、转 YOLO 格式、切分训练集,到最后的难例挖掘和部署侧验证,每一步都给可直接复用的代码和参数。适合正在做自动驾驶感知算法验证、用 YOLO 系列跑自己的交通目标检测数据集、或者被数据标注格式折腾过的工程师。

2. 解压后先看结构:目录、标注文件与多类别类别体系

2.1 目录层级与文件命名规则

这类数据集解压后先别急着数图片,第一步是把目录结构完整列出来。常见做法是先用tree或者find摸清层级关系,同时统计图片数量和标注文件数量是否对上。

# 解压,保留权限和目录结构 unzip 自动驾驶多类别交通目标检测数据集7.zip -d autodata # 统计图片数量和标注数量 find autodata -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.jpeg" -o -name "*.bmp" \) | wc -l find autodata -type f \( -name "*.xml" -o -name "*.json" -o -name "*.txt" \) | wc -l # 输出前两层目录结构,方便快速看清组织方式 find autodata -maxdepth 2 -type d | sort

这里find的两个统计命令要分别执行,如果图片数和标注文件数不一致,说明有漏标或空标注样本,后续训练时这些样本会被当作负样本或直接报错。目录层级决定数据加载方式:如果每类一个文件夹,是 ImageNet 式组织;如果所有图片平铺、标注文件同名不同后缀,是 VOC 式组织;如果标注全部汇总成一个大 JSON,则是 COCO 式组织。

交通目标检测数据集常见的是平铺结构,标注文件与图片同名,只是后缀从.jpg变成.xml或.txt。文件名里常带采集时间、路段编号或天气标记,比如city_road_a_day_000123.jpg这种命名里往往藏着场景信息,后续分层切分要用到。

2.2 标注格式:框、类别、截断与遮挡字段

多类别交通数据集的标注格式不外乎三种:VOC XML、COCO JSON、KITTI Txt。打开一个标注文件看一眼,马上就能判断属于哪种。VOC 格式用 XML 描述object节点,每个目标包含name、bndbox;KITTI 格式用纯文本行,每行依次是类别、截断程度、遮挡程度、2D 框坐标;COCO 格式则把图片信息放images数组、目标信息放annotations数组。

我一般会写一个小脚本把三种格式统一解析成内存里的通用结构,再做后续处理。以下代码兼容 VOC 和 KITTI 两种文本格式,COCO JSON 也一并支持:

import json import xml.etree.ElementTree as ET from dataclasses import dataclass, field @dataclass class Target: class_name: str x1: float y1: float x2: float y2: float truncated: float = 0.0 # 截断比例 0~1 occluded: int = 0 # 遮挡等级 0~3 @dataclass class Sample: image_path: str width: int height: int targets: list = field(default_factory=list) def parse_voc(xml_path, image_dir): tree = ET.parse(xml_path) root = tree.getroot() sample = Sample( image_path=image_dir + "/" + root.findtext("filename"), width=int(root.findtext("size/width")), height=int(root.findtext("size/height")), ) for obj in root.iter("object"): sample.targets.append(Target( class_name=obj.findtext("name"), x1=float(obj.findtext("bndbox/xmin")), y1=float(obj.findtext("bndbox/ymin")), x2=float(obj.findtext("bndbox/xmax")), y2=float(obj.findtext("bndbox/ymax")), )) return sample def parse_kitti(txt_path, image_dir): sample = Sample(image_path=txt_path.replace(".txt", ".jpg"), width=-1, height=-1) with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) < 15: continue sample.targets.append(Target( class_name=parts[0], truncated=float(parts[1]), occluded=int(parts[2]), x1=float(parts[4]), y1=float(parts[5]), x2=float(parts[6]), y2=float(parts[7]), )) return sample

KITTI 格式缺图片宽高,需要后续用cv2.imread补上,不能依赖-1直接做归一化。VOC 和 KITTI 的坐标都是像素坐标,COCO 的 bbox 是[x, y, width, height]也是像素坐标。所有格式在转 YOLO 前必须统一成像素坐标系下的x1, y1, x2, y2,否则归一化会出错。

2.3 类别体系与不均衡分布

多类别交通数据集典型类别包括:car、truck、bus、motorcycle、bicycle、pedestrian、rider、traffic light、traffic sign 这几大类。如果数据集采集自欧洲或日本路况,可能还有 tram、train;自卸卡车和拖车有时单独成类。先跑一次类别统计,看看每类有多少目标、多少张图包含该类:

from collections import Counter def count_classes(samples): class_counter = Counter() class_image_counter = Counter() for sample in samples: seen = set() for t in sample.targets: class_counter[t.class_name] += 1 if t.class_name not in seen: class_image_counter[t.class_name] += 1 seen.add(t.class_name) return class_counter, class_image_counter

如果 car 有三万目标而 tram 只有两百个,后面训练时tram的 AP 基本靠运气,只能靠数据增强兜底或者干脆合并成粗粒度类别。另外注意类别名大小写混用,Person和person会被当成两个类,需要先做一次小写归一化。

提示:别忽略被截断目标和被遮挡目标。truncated > 0.5的框在转 YOLO 时可以直接过滤掉,这类样本对训练贡献小,反而会引入背景噪声。

3. 跑自动化质检与可视化复核:不让脏标注进训练

3.1 标注质量统计脚本

数据集的坑往往藏在标注里而不是图片里。最常见的四类问题:坐标越界(框超出图片边界)、坐标倒置(x2 小于 x1)、零尺寸框、类别名与预期类别表不一致。这些脏标注如果不清洗,训练时 loss 会异常震荡,表现就是验证集 mAP 上不去但训练 loss 也不降。

先写一个遍历全部标注文件的质检脚本:

import cv2 def validate_sample(sample, class_list, report): h, w = sample.height, sample.width if h <= 0 or w <= 0: # 自动读图补尺寸 img = cv2.imread(sample.image_path) h, w = img.shape[:2] sample.height, sample.width = h, w for t in sample.targets: cls = t.class_name.lower() if cls not in class_list: report.append(f"{sample.image_path}: 未知类别 {t.class_name}") continue if t.x1 < 0 or t.y1 < 0: report.append(f"{sample.image_path}: 框越界({t.class_name})") if t.x2 >= w or t.y2 >= h: report.append(f"{sample.image_path}: 框超出右/下边界({t.class_name})") if t.x2 <= t.x1 or t.y2 <= t.y1: report.append(f"{sample.image_path}: 坐标倒置或零面积({t.class_name})")

执行时把报告重定向到report.txt,逐行看。越界分两种情况:轻微越界 1~2 像素,直接 clamp 到边界;严重越界超过图片宽高 20%,说明标注工具崩了,该样本直接剔除。零面积框大多是标注员误操作产生的残留节点,这类框会直接破坏 YOLO 的 Anchor 匹配逻辑,必须删。

3.2 类别维度分布与难例定位

按天气、时段、道路类型统计目标数量,是这个阶段最值得做的事。很多多类别数据集的类别总量均衡,但拆到夜间场景就严重偏斜——白天 car 占 60%,夜间只拍到车灯和局部轮廓,标注数量掉到 5%。如果最终要评估夜间检测效果,必须在切分时保证夜间样本比例。

我会在质检脚本里顺便输出一张按类别的统计表,按目标数量降序排:

类别目标数图片数平均每图目标数
car31245189021.65
pedestrian15234101201.51
traffic light421028151.50
tram156891.75

平均每图目标数小于 1 的类别要特别注意,说明大量图片里该类只出现一次且多半是小目标。交通灯、交通标志就属于典型的小目标类别,它们在图里只占几十个像素,转 YOLO 后如果输入分辨率低,几乎必丢。

3.3 随机抽样可视化复核

统计脚本查不出类别贴错这种语义问题,只能靠人眼抽样。我一般每类抽 20~40 张图,用 OpenCV 把框画出来拼成网格图,一次看一个类别:

import cv2 import math def draw_grid(samples, grid=(4, 4), cell_size=(640, 640)): rows, cols = grid canvas = [] for i, sample in enumerate(samples[: rows * cols]): img = cv2.imread(sample.image_path) img = cv2.resize(img, cell_size) for t in sample.targets: x1, y1, x2, y2 = map(int, (t.x1, t.y1, t.x2, t.y2)) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, t.class_name, (x1, max(20, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) canvas.append(img) canvas = [cv2.hconcat(canvas[r * cols:(r + 1) * cols]) for r in range(rows)] grid_img = cv2.vconcat(canvas) return grid_img

看可视化图时重点确认三件事:框是否紧贴目标轮廓、类别名是否贴错、互相遮挡的目标是否被标全。交通灯和交通标志是重灾区,红色圆灯被标成 stop sign、远处行人只标了上半身,这些错误靠脚本查不出来,只能抽样看。一个类别如果错标率超过 5%,建议回头跟标注方核对,别硬着头皮训完再后悔。

4. 转成 YOLO 格式并切分:最小可用训练流程

4.1 VOC/COCO 转 YOLO 的核心映射

YOLO 系列(从 v5 到 v11)需要的标注是归一化格式:每行一个目标,五个数值依次是class_id, x_center, y_center, width, height,全部除以图片宽高归一化到 0~1。转换的坑不在于公式,在于类别 ID 的映射表——必须用一个固定的classes.txt把所有类别名按行排序,索引即 ID。

先创建一个类别映射文件,然后写转换脚本:

# classes.txt 示例:每一行一个类别名,顺序即 ID # car # pedestrian # traffic light # ... def convert_to_yolo(sample, class_to_id, out_txt_path): lines = [] for t in sample.targets: cls_id = class_to_id[t.class_name.lower()] x1, y1, x2, y2 = t.x1, t.y1, t.x2, t.y2 x_center = (x1 + x2) / 2 / sample.width y_center = (y1 + y2) / 2 / sample.height box_w = (x2 - x1) / sample.width box_h = (y2 - y1) / sample.height # 过滤非法框,防止训练崩溃 if box_w <= 0 or box_h <= 0 or box_w > 1 or box_h > 1: continue lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") with open(out_txt_path, "w", encoding="utf-8") as f: f.write("\n".join(lines))

class_to_id字典必须只保留classes.txt中出现的类别,遇到未知类别直接抛异常而不是静默跳过,否则会出现漏标但不知道漏在哪的情况。前面统计脚本里的未知类别告警,这里就是转 YOLO 时的硬校验。小目标转完 YOLO 后数值会很小,比如宽度只占图片 0.02,这时检查精度——0.020000和0.019999在浮点上没有实质影响,但在做分析时可以留意。

4.2 训练/验证/测试切分脚本

切分最忌讳纯随机。交通数据是从连续视频帧抽出来的,相邻帧几乎一样,如果随机切分,同一条路上的相似帧会同时落在训练集和验证集,验证集指标虚高,部署到新路段立刻崩。正确做法是先按采集时间段或路段分组,再把组随机分到三个集合里:

import random, os, shutil from collections import defaultdict def split_by_group(sample_list, group_key_fn, ratios=(0.8, 0.1, 0.1), seed=42): random.seed(seed) groups = defaultdict(list) for s in sample_list: groups[group_key_fn(s)].append(s) train_groups, val_groups, test_groups = [], [], [] all_groups = list(groups.keys()) random.shuffle(all_groups) n = len(all_groups) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) train_groups = all_groups[:n_train] val_groups = all_groups[n_train:n_train + n_val] test_groups = all_groups[n_train + n_val:] return ( [s for g in train_groups for s in groups[g]], [s for g in val_groups for s in groups[g]], [s for g in test_groups for s in groups[g]], )

group_key_fn可以取文件名里的时间戳前缀或路段名。如果文件名没带这些信息,就按文件名的哈希值模一个组号,至少保证同序列相邻帧大概率落进同组。切分完记得重构目录结构:images/train、images/val、images/test和对应的labels/目录,YOLO 训练器的数据配置直接指向这两个根目录。

提示:验证集和测试集不要用同一批组。很多人在 COCO 上习惯只用 train/val,但多类别交通数据集最后要做车型或场景泛化评估,单独留一个 test 集能救命。

4.3 数据增强与类别平衡

转完格式、切完数据后,先别研究 Mosaic 和 MixUp 参数。第一优先是核对每类目标在 train/val/test 三个集合中的分布是否一致。写一行统计就能验证:

# 统计三个集合中每个类别的目标数,并输出占比 # 如果某个类别在 train 中占 5%,在 val 中却占 12%,说明切分不均匀,需要重新调 seed 或按类别分层

切分不均匀时,重新换 seed 不一定有效,更可靠的是做类别分层切分:按图片里包含的主类别比例来分配。交通灯这种小目标类别在训练集里的占比如果低于验证集,训练出来 AP 被拉低是必然的。数据增强方面,我一般只用随机翻转和轻度色彩抖动,交通数据集不像通用目标检测那么耐剪裁——裁掉交通灯的上文,语义就变了。

5. 训练与推理时常见的坑:一次说清这些问题

5.1 类别 ID 与配置文件错位

现象:loss 正常下降,mAP 也正常,但推理时 car 的框标成了 pedestrian。 原因:转 YOLO 时按classes.txt的某一行顺序生成了 ID,但训练用的 YAML 里names列表顺序跟它不一致,于是class_id=0在两个文件里指向不同类别。 解决:用训练配置的names文件反向生成一次class_to_id,让转换脚本读同一个yaml的names列表,而不是自己维护一份。改完重新转换标注,不要只改配置文件——YOLO 训练时标签文件里的数字已经写死,改配置文件治标不治本。

5.2 小目标直接消失:下采样与输入分辨率

现象:验证集上 car 和 truck 的 AP 能到 0.85,traffic light 只有 0.1,几乎全部漏检。 原因:YOLO 的下采样倍数通常到 32,输入 640x640 时特征图只有 20x20,一个 10x10 像素的交通灯在原图上映射不到一个网格,Anchor 匹配直接失败。 解决:把输入分辨率提到 1280 或 1536,代价是显存占用翻倍、推理时延增加;或者针对小目标类别单独加一层 P2 检测头。工程上先提分辨率,验证 AP 提升后再评估时延是否还能接受。TRT 部署时分辨率写死,训练和推理必须用同一个尺寸。

5.3 验证集泄题:相似帧串集合

现象:验证集 mAP 0.92,测试集换一批新路段只有 0.61,差距大得离谱。 原因:纯随机切分导致同一车辆在同一路段拍摄的连续帧一部分进了训练集、一部分进了验证集。验证集相当于开卷考试,模型见过的场景直接背答案。 解决:按文件名的采集时间戳归组,同一秒内的帧归到同一组,以组为单位做切分,前面split_by_group就是用在这里。如果文件名没带时间,可以用图像哈希求相似度,把相似度超过阈值的图片聚成同一组。这件事在动工前做,事后补会废掉一半标注。

5.4 挡风玻璃反光与夜间泛化:场景分布太单一

现象:白天模型放到夜间路测,行人 AP 掉一半,车辆检测也漏。 原因:数据集里夜间样本只占很小比例,且夜间标注框常被反光和灯光干扰,有些目标直接标成了背景。 解决:把天气和光照信息做成数据集的元数据字段,训练时按场景比例上采样——夜间样本重复 3 到 5 个 epoch,或对夜间图做亮度增强、Gamma 变换扩样。如果数据集里带了雨雾样本,也照此办理。自动驾驶的视觉感知本质上是多个场景模型的集合,一个模型吃遍所有天气是理想情况,多数落地靠的是针对夜间工况微调一个分支。

5.5 标注文件编码与路径大小写

现象:训练时报Label Error或读不到图片,且报错样本随机出现,看起来像玄学。 原因:部分标注文件是 UTF-8 带 BOM,或 GBK 编码,脚本按 UTF-8 解析时首字符混入不可见字符;另外 Linux 下路径大小写敏感,City_Road.jpg和city_road.jpg指向不同文件。 解决:转换脚本统一用encoding="utf-8-sig"打开所有文本文件,自动吞掉 BOM;文件名在解压后先做一次批量小写归一化,图片路径和标注路径保持完全一致。压缩包解压后如果是在 Windows 上转换过再拷贝到 Linux 训练,路径分隔符也容易出问题,建议所有路径处理统一用os.path.join而不是手写斜杠。

6. 进阶:用难例挖掘和部署侧验证把模型推到能上车

数据集清洗干净、模型训练收敛之后,别急着写报告。自动驾驶场景的验收标准不是 mAP 有多高,而是决策延迟能不能卡住。常见参考指标是决策延迟 32.8 毫秒——差不多对应 30 FPS 的处理节奏,城市道路的 L2/L2+ 场景按这个预算做推理时序是够用的;如果模型分辨率提到 1280 以上,或者用了大模型检测头,这个预算会被吃掉不少,需要 TensorRT FP16 甚至 INT8 量化来换时间。精度再高,延迟卡不住,系统就不考虑上车。

难例挖掘是这个阶段最值钱的操作。拿已经训练好的模型在验证集上跑一遍推理,把置信度在 0.2~0.5 之间的预测全部导出来。这些不一定是误检,很多是模型「犹豫」的目标——真实存在但特征不清晰。把它们按类别聚类,挑出重复出现的难例帧,加入训练集重训一轮:

# 用训练好的模型跑验证集,输出低置信度预测 # 按类别统计出现频次,频次最高的前 1000 帧提取为难例集 # 与原始训练集合并,重训一个 epoch 做增量学习

增量重训时把学习率调低到初始值的 1/10,防止难例把已学到的特征冲掉。同时监控每一类的 AP 变化:如果某个类别在难例集加入后 AP 不升反降,说明难例和原始样本分布冲突,要检查是标注错位还是类别定义边界模糊。

另一件值得做的事是类别合并与重定义。多类别数据集里的 pedestrian 和 rider 如果标注边界不统一,模型训练时就会持续摇摆。我习惯在难例挖掘之后做一次类别归并决策:把 motor、motorcycle、rider 合并成一类two_wheeler,把 bus 和 truck 合并为heavy_vehicle,类别少了,每个类的样本量涨上去,AP 往往比强行维持细粒度更高。这是做自动驾驶感知数据集的落地经验——多类别不是越多越好,类别体系要和下游规划模块的需求对齐,决策模块关心的是「前方路面上有没有可移动障碍物、它有多快」,而不是「这辆是丰田还是大众」。做清楚这一步,整个数据集的价值才算真正兑现。

最后落地前一定要做的验证:随机抽一段模型从未见过的连续帧序列,跑一遍推理,记录每一帧的漏检和误检,数一下连续多少帧能稳定锁定同一个目标。单帧 mAP 高不代表时序上可用,目标在第 3 帧丢了又出现在第 7 帧,这种抖动在自动驾驶里比稳定误检更危险。我习惯把这个验证脚本固化下来,每次换数据集、换模型都先跑一遍,省得在车上翻车。希望这个流程能帮你在自己的多类别交通目标检测数据集上少走几趟弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询