简介:这是一份面向计算机视觉与智能交通方向开发者、研究者的交通锥桶与路障目标检测数据集,标签为barricade,可用于训练和验证车辆环境感知、道路障碍物识别等算法,适合具备一定深度学习基础的目标检测实践者。压缩包共1561个文件,包含520张jpg图像、520个xml标注文件与521个txt标签文件,xml对应VOC格式的边界框与类别信息,txt对应YOLO格式的归一化标注,两种格式并行,便于在Faster R-CNN、YOLO等不同框架间直接切换训练。资源包整体约88.64MB,规模适中,便于本地快速加载与迭代实验。目前已有1747人学习下载,说明其在交通场景目标检测任务中具有一定参考价值。读者可借助该数据集完成从数据读取、格式转换到模型训练与精度评估的完整流程,并针对锥桶、路障等小目标在复杂交通场景下的检测难点开展对比实验,为自动驾驶安全行驶与智能交通系统研究提供可复用的数据基础。
1. 交通锥桶路障数据集:从标注文件到可训练模型的完整路径
拿到一个交通锥桶路障数据集,最怕的不是数据量不够,而是标注格式和训练框架对不上。我见过太多人把压缩包解压完,看到一堆 XML、JSON 和 JPG 混在一起就懵了——明明标注框画得挺准,跑起来却全是漏检。问题往往出在格式转换那一步:VOC 的bndbox坐标是左上右下,YOLO 要的是归一化中心点加宽高,中间但凡有一个类别映射写错,训练出来的模型就只认“锥桶”不认“路障”。这个数据集的核心价值在于它同时覆盖了锥桶、水马、警示柱、施工围挡这几类常见路面障碍物,场景从城市快速路到夜间施工区都有,适合做交通场景的细粒度检测。如果你正在做自动驾驶感知、道路巡检或者施工区域安全监控,这套数据能帮你省掉至少两周的采集和标注时间。但前提是,你得先把它跑通。
2. 拆开压缩包先看什么:目录结构与标注格式的对应关系
2.1 典型目录布局与文件命名规律
一个结构清晰的交通锥桶路障数据集,解压后通常长这样:
dataset/ ├── annotations/ # 标注文件 │ ├── 000001.xml │ ├── 000002.xml │ └── ... ├── images/ # 原始图片 │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... ├── ImageSets/ # 训练/验证/测试划分 │ └── Main/ │ ├── train.txt │ ├── val.txt │ └── test.txt └── classes.txt # 类别名称列表先别急着写训练脚本,用三条命令把家底摸清楚。第一条看图片总数和格式分布:
# 统计图片数量和扩展名分布 find dataset/images -type f | sed 's/.*\.//' | sort | uniq -c # 输出示例:1200 jpg 300 png第二条看标注文件数量是否和图片一一对应:
# 对比图片和标注文件数量 echo "images: $(ls dataset/images | wc -l)" echo "annotations: $(ls dataset/annotations | wc -l)"如果两个数字差得远,说明有图片没标或者标注文件命名对不上,这种脏数据直接进训练会引入大量背景误检。第三条抽查一个标注文件的内容:
# 查看第一个标注文件的类别和坐标 head -50 dataset/annotations/000001.xml重点看<name>标签里的类别名有没有拼写不一致——比如cone和traffic_cone混用,这种问题在多人协作标注的数据集里特别常见,训练时会被当成两个完全不同的类。
2.2 VOC 与 YOLO 格式的核心差异
交通锥桶路障数据集最常见的标注格式是 Pascal VOC XML,因为标注工具默认导出这个。但训练时大家普遍用 YOLO 系列,所以转换是绕不开的。两者的核心差异就三点:
| 维度 | VOC XML | YOLO TXT |
|---|---|---|
| 坐标表示 | 左上角 (xmin, ymin) 和右下角 (xmax, ymax) | 中心点 (x_center, y_center) 加宽高 |
| 数值范围 | 绝对像素值 | 归一化到 0~1 |
| 类别存储 | 标注文件内嵌<name> | 独立classes.txt+ 行首类别索引 |
转换公式本身不复杂,但坑在于:VOC 的坐标可能超出图片边界,YOLO 要求所有值在 0~1 之间。我一般会在转换脚本里加一步裁剪,把越界的框拉回来,而不是直接丢弃——因为越界往往意味着标注时手抖了,框本身还是有效的。
2.3 用脚本完成格式转换与数据集划分
下面这个脚本把 VOC 转 YOLO、生成classes.txt、按 8:1:1 划分训练验证测试集,一次跑完:
import os import xml.etree.ElementTree as ET import random from pathlib import Path # 配置路径和类别 DATASET_ROOT = Path("dataset") ANNOTATIONS_DIR = DATASET_ROOT / "annotations" IMAGES_DIR = DATASET_ROOT / "images" OUTPUT_LABELS_DIR = DATASET_ROOT / "labels" CLASSES = ["cone", "water_barrier", "warning_post", "construction_fence"] # 创建输出目录 OUTPUT_LABELS_DIR.mkdir(exist_ok=True) for split in ["train", "val", "test"]: (DATASET_ROOT / "ImageSets" / "Main").mkdir(parents=True, exist_ok=True) def voc_to_yolo(xml_path, img_width, img_height): """将单个 VOC XML 转为 YOLO 格式的行列表""" tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): cls_name = obj.find("name").text.strip() if cls_name not in CLASSES: continue # 跳过未定义类别 cls_id = CLASSES.index(cls_name) bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 裁剪越界坐标 xmin = max(0, min(xmin, img_width)) xmax = max(0, min(xmax, img_width)) ymin = max(0, min(ymin, img_height)) ymax = max(0, min(ymax, img_height)) # 转为中心点+宽高并归一化 x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height width = (xmax - xmin) / img_width height = (ymax - ymin) / img_height lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") return lines # 遍历所有标注文件 all_files = [] for xml_file in sorted(ANNOTATIONS_DIR.glob("*.xml")): img_file = IMAGES_DIR / (xml_file.stem + ".jpg") if not img_file.exists(): img_file = IMAGES_DIR / (xml_file.stem + ".png") if not img_file.exists(): print(f"跳过无对应图片: {xml_file.name}") continue # 读取图片尺寸(用 PIL 或 opencv 均可,这里用 PIL) from PIL import Image with Image.open(img_file) as im: w, h = im.size yolo_lines = voc_to_yolo(xml_file, w, h) if not yolo_lines: continue # 写入 YOLO 标签文件 label_path = OUTPUT_LABELS_DIR / (xml_file.stem + ".txt") label_path.write_text("\n".join(yolo_lines)) all_files.append(xml_file.stem) # 随机划分 8:1:1 random.seed(42) random.shuffle(all_files) n = len(all_files) train_end = int(n * 0.8) val_end = int(n * 0.9) splits = { "train": all_files[:train_end], "val": all_files[train_end:val_end], "test": all_files[val_end:] } for split_name, stems in splits.items(): txt_path = DATASET_ROOT / "ImageSets" / "Main" / f"{split_name}.txt" txt_path.write_text("\n".join(stems)) print(f"{split_name}: {len(stems)} 张") # 写入类别文件 (DATASET_ROOT / "classes.txt").write_text("\n".join(CLASSES)) print("转换完成")这段代码的关键逻辑有三处。第一,voc_to_yolo里先做坐标裁剪再归一化,避免出现负数或大于 1 的值导致训练时 loss 爆炸。第二,类别过滤用if cls_name not in CLASSES: continue,把不在预定义列表里的标注直接跳过,而不是报错中断——实际数据里经常混入几个标错的类别。第三,划分时固定random.seed(42),保证每次运行结果一致,方便复现实验。参数方面,CLASSES列表必须和标注文件里的<name>完全一致,大小写敏感;划分比例 8:1:1 适合数据量在 1000~5000 张之间的场景,如果数据量少于 500 张,建议改成 7:1.5:1.5 或者做交叉验证。
3. 训练参数怎么设:从 anchor 聚类到学习率调度
3.1 先跑 anchor 聚类再定网络结构
交通锥桶和路障的尺度差异很大:远处的锥桶可能只有 20×30 像素,近处的施工围挡能占满半个画面。直接用 COCO 的默认 anchor 去训,小目标召回率会很难看。我一般先用 K-means 在标注框上聚一遍:
import numpy as np from pathlib import Path # 读取所有 YOLO 格式标注的宽高 labels_dir = Path("dataset/labels") wh_list = [] for txt_file in labels_dir.glob("*.txt"): for line in txt_file.read_text().strip().split("\n"): parts = line.split() if len(parts) == 5: w, h = float(parts[3]), float(parts[4]) wh_list.append([w, h]) wh_array = np.array(wh_list) print(f"标注框总数: {len(wh_array)}") print(f"宽高范围: w [{wh_array[:,0].min():.4f}, {wh_array[:,0].max():.4f}]") print(f" h [{wh_array[:,1].min():.4f}, {wh_array[:,1].max():.4f}]") # 简单 K-means 聚类(k=9) from sklearn.cluster import KMeans kmeans = KMeans(n_clusters=9, random_state=42, n_init=10) kmeans.fit(wh_array) anchors = kmeans.cluster_centers_ # 按面积排序输出 anchors = anchors[np.argsort(anchors[:, 0] * anchors[:, 1])] for i, (w, h) in enumerate(anchors): print(f"anchor {i}: {w:.4f} x {h:.4f} (像素参考: {w*640:.0f} x {h*640:.0f})")跑完你会得到 9 组归一化宽高。如果最小那组对应的像素尺寸小于 16×16,说明数据里小目标占比高,网络输入分辨率至少要设到 640,最好用 1280 再训一版对比。anchor 数量保持 9 个不变,但具体数值要替换掉配置文件里的默认值。
3.2 学习率与 warmup 的配合策略
YOLO 系列默认用 SGD 加余弦退火,初始学习率 0.01。但交通锥桶数据集有个特点:背景占比极高,正样本稀疏。如果一上来就用 0.01,前几个 epoch 梯度会被背景主导,模型学到的全是“预测背景”。我的做法是加 3 个 epoch 的 warmup,学习率从 0.001 线性升到 0.01,再走余弦退火:
# 训练配置片段(以 YOLOv8 风格为例) lr0: 0.01 lrf: 0.01 # 最终学习率 = lr0 * lrf warmup_epochs: 3 warmup_momentum: 0.8 warmup_bias_lr: 0.1 box: 7.5 # 框回归损失权重 cls: 0.5 # 分类损失权重 dfl: 1.5 # 分布焦点损失权重box权重设 7.5 是因为锥桶的定位精度比分类更重要——把锥桶认成水马还能接受,但框偏了 50 像素在自动驾驶里就是事故。cls降到 0.5 是因为类别本身区分度够大,不需要太强的分类梯度。如果训练时发现cls_loss降得比box_loss快很多,说明分类太容易了,可以再降cls权重,把容量让给定位。
3.3 数据增强的边界:哪些增强会帮倒忙
交通锥桶数据集的增强策略要克制。Mosaic 和 MixUp 能提升小目标召回,但会把不同场景的锥桶拼在一起,导致模型学到“锥桶可以出现在任何背景上”的错误关联。我的经验是:Mosaic 概率设 0.5 而不是默认的 1.0,MixUp 直接关掉。HSV 增强里,色调扰动范围要收窄,因为锥桶的橙色是强判别特征,色调转太多会变成红色消防栓。
# 增强配置 hsv_h: 0.010 # 色调扰动,默认 0.015 偏大 hsv_s: 0.5 # 饱和度扰动 hsv_v: 0.3 # 明度扰动 degrees: 5.0 # 旋转角度,交通场景锥桶基本直立 translate: 0.1 # 平移 scale: 0.3 # 缩放 shear: 2.0 # 剪切 perspective: 0.0 # 透视变换关掉,会引入不真实的形变 flipud: 0.0 # 上下翻转关掉,锥桶不会倒过来 fliplr: 0.5 # 左右翻转保留 mosaic: 0.5 mixup: 0.0flipud必须关掉,因为倒置的锥桶在物理上不存在,开了只会让模型困惑。perspective也建议关,交通场景的相机畸变在采集时已经固定了,训练时再加透视变换等于引入噪声。
4. 避坑与排查:标注和训练里最容易翻车的五个点
4.1 类别名大小写不一致导致类别数翻倍
现象:训练日志里classes显示 8 个类,但classes.txt里只写了 4 个。
原因:标注文件里同时存在Cone和cone,转换脚本按字符串精确匹配,生成了两个不同的类别索引。
解决:在转换脚本开头加一步统一小写:cls_name = obj.find("name").text.strip().lower(),同时检查classes.txt是否也统一了小写。
4.2 图片和标注文件命名对不上导致静默丢数据
现象:训练完发现验证集 mAP 很高,但实际测试时漏检严重。
原因:部分图片的扩展名是.jpeg而标注是.xml,转换脚本只找了.jpg和.png,这些图片被静默跳过,没进训练集。
解决:在遍历时用img_file = list(IMAGES_DIR.glob(xml_file.stem + ".*"))匹配所有扩展名,并打印跳过的文件列表,不要静默处理。
4.3 越界框未裁剪导致 loss 出现 NaN
现象:训练几个 epoch 后 loss 突然变成 NaN。
原因:VOC 标注里xmax大于图片宽度,归一化后值大于 1,计算宽高时出现负值,开方后 NaN。
解决:转换时强制裁剪,并在裁剪后检查width > 0 and height > 0,不满足的框直接丢弃。同时加一行日志记录被裁剪的框数量,如果超过总框数的 5%,说明标注质量有问题,需要回头修标注。
4.4 背景图未参与训练导致误检率偏高
现象:模型在空旷路面上把白色标线认成锥桶。
原因:训练集里全是含目标的图片,模型没见过“没有锥桶的路面”长什么样,学到的是“白色长条=锥桶”。
解决:从数据集中挑 5%~10% 的纯背景图(没有标注框的图片),在 YOLO 格式下生成空的.txt文件,一起参与训练。这些图会教模型抑制背景误检。
4.5 验证集和训练集场景重叠导致指标虚高
现象:验证集 mAP 0.9,但换一段路测就掉到 0.5。
原因:随机划分时同一段连续视频的帧被分到了训练集和验证集,两边的场景几乎一样。
解决:按视频片段或采集批次划分,而不是按单帧随机分。如果数据里没有片段信息,用图片的 EXIF 时间戳或文件修改时间做分组,同一分钟内的图片归到同一侧。
5. 用五十张图快速验证数据集可用性的技巧
数据集值不值得投入,不用等训完 300 个 epoch 才知道。我的习惯是:从训练集里抽 50 张覆盖不同场景的图,用预训练模型做一次 10 epoch 的微调,看三个指标——box_loss是否稳定下降、cls_loss是否在 5 个 epoch 内降到 0.5 以下、验证集上锥桶的召回率是否超过 0.6。如果这三个都满足,说明标注质量和类别区分度没问题,可以放心跑完整训练。如果box_loss震荡或者召回率低于 0.4,先别调参,回去查标注——大概率是框画歪了或者类别标错了。
# 快速验证命令(以 YOLOv8 为例) yolo detect train \ data=dataset.yaml \ model=yolov8n.pt \ epochs=10 \ imgsz=640 \ batch=16 \ lr0=0.001 \ warmup_epochs=1 \ mosaic=0.0 \ val=True \ plots=True这里mosaic=0.0是为了排除增强干扰,先看模型在原始数据上的拟合能力。lr0=0.001比正常训练低一个数量级,因为只跑 10 个 epoch,学习率太大会直接跳过最优解。跑完后看runs/detect/train/val_batch0_pred.jpg,如果预测框和真实框的重叠度肉眼可见地高,说明数据没问题;如果框到处乱飞,先检查dataset.yaml里的nc和names是否和classes.txt一致。
还有一个更快的办法:用labelimg或anylabeling打开几张训练图,肉眼过一遍标注框。我一般会重点看夜间图和远距离小目标图——这两类最容易标错。夜间图的锥桶反光会导致标注员把反光区域也框进去,远距离小目标则经常漏标。如果这两类图的标注质量过关,整个数据集的质量基本就有保障了。
最后说个血泪教训:别在格式转换上省事。我见过有人直接用网上找的转换脚本,跑完不检查就开训,结果类别索引全错位——锥桶的标签指向了水马,训出来的模型把锥桶全认成水马。后来花了三天返工。现在我的习惯是转换完先跑一个verify.py,随机抽 20 张图把 YOLO 标签画回原图上看一眼,确认框的位置和类别都对得上,再进训练。这个步骤花不了十分钟,但能省掉后面几天的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取