简介:底特律街景目标检测数据集面向计算机视觉入门与实战开发者,提供六类街景目标标注数据,涵盖汽车、交通标志、车道线、行人、摩托车手与骑行者,可直接用于YOLO系列模型训练与效果验证。数据按训练、验证、测试三部分清晰组织,训练集含一千五百七十五张图片及对应标签,验证集四百五十张,测试集二百二十五张,另附类别说明文件,整体目录结构直观,省去格式转换与反复标注的额外工作。压缩包共两千个文件,其中以txt标注文件为主体,另配有一个Python可视化脚本,传入任意图片即可自动绘制边界框并保存至当前目录,非常便于快速检查标注质量与模型输出。包体大小约四百七十二点五兆字节,离线使用方便。目前已有144人学习下载,适合正在开展目标检测课程设计、毕业设计或算法实验的开发者,拿到后即可直接投入训练流程,有效缩短前期数据准备周期。
1. 底特律街景目标检测数据集:拿到就能训的2250张城市道路图
做城市道路目标检测的人,最怕的不是模型不收敛,而是手里没有像样的数据。底特律街景目标检测数据集就是为这个场景准备的——2250张实拍街景图,划分成训练1575张、验证450张、测试225张,每张图都配一个同名txt标注文件,共6类目标:汽车、交通标志、车道线、行人、摩托车手、骑行者。标注格式直接就是YOLO的归一化坐标写法,解压出来就能灌进ultralytics框架里跑,不需要做任何格式转换。适合正在做计算机视觉大作业、目标检测课程设计,或者想拿真实街道数据测试算法的人。下面从标签格式精读开始,把数据体检、训练配置和踩坑记录逐个说清楚。
2. YOLO标注格式精读:txt文件里五个数字的真正含义
2.1 五列数字的结构:类别、中心点、宽高
拿项目里的0453.txt来说,打开后大概是这种行:
3 0.542187 0.441667 0.020312 0.059722这一行五个数字,从左到右依次是:类别编号 class_id、边界框中心点的 x 坐标、中心点的 y 坐标、边界框宽度、边界框高度。注意一个关键点——后四个坐标全部是归一化的,值域在 0 到 1 之间,用相对图片宽高的比例表示,而不是像素绝对值。
我拿到这类标注时,一般会先写个十来行的解析脚本,确认每个文件的行数和数值范围,避免后面训练时被某个脏数据拖死。
# 读取一个YOLO格式txt标注,逐行解析并做基本合法性检查 def parse_yolo_label(txt_path): with open(txt_path, "r", encoding="utf-8") as f: lines = f.readlines() boxes = [] for line in lines: line = line.strip() if not line: continue parts = list(map(float, line.split())) if len(parts) != 5: print(f"跳过非法行: {line}") continue class_id, x_center, y_center, w, h = parts # 归一化坐标超出0~1范围,说明标签大概率有问题 if not (0 <= x_center <= 1 and 0 <= y_center <= 1 and 0 <= w <= 1 and 0 <= h <= 1): print(f"坐标越界: {line}") boxes.append({ "class_id": int(class_id), "x_center": x_center, "y_center": y_center, "width": w, "height": h }) return boxes这里strip()去掉行尾换行符,split()按空格切出 5 个字段。之所以强行校验每组坐标都在 0 到 1 之间,是因为 YOLO 的 txt 标注一旦出现大于 1 的数值,最常见的翻车原因就是某个转换工具把像素坐标直接写进去了,没有归一化。这种标签不查出来,训练时 loss 会特别难看。
2.2 类别编号表:6个分类的顺序不能乱
这个数据集一共 6 类目标,资源包里的类别 txt 文件打开有 6 行,每个类别一行。只是要注意一点——txt 标注文件里的class_id数字,是按照这个类别文件的行序来定义的,那么训练配置里的类别名顺序也必须跟它完全一致,否则第三行本来是车道线,模型却当成行人在学。
这 6 个类别放在一起,几乎是城市道路目标检测的标准答案:
| class_id | 类别 | 训练配置中的英文标识 |
|---|---|---|
| 0 | 汽车 | car |
| 1 | 交通标志 | traffic_sign |
| 2 | 车道线 | lane_marking |
| 3 | 行人 | pedestrian |
| 4 | 摩托车手 | motorcyclist |
| 5 | 骑行者 | cyclist |
上面的 class_id 对应关系是常见的排列方式,具体以你打开类别 txt 文件看到的行为准。摩托车手和骑行者的区别要注意:前者骑摩托,后者骑自行车,两者外观差异不小,训练时容易互相抢特征。
如果你的使用场景不需要区分这两类,比如只做行人和车辆检测,可以在训练前做一次标签合并,把 class_id 4 和 5 都改成 3。这个操作用脚本批量改一下就行,比训练完之后再后处理要省事得多。
2.3 归一化坐标与像素坐标互转
回到那个示例行,x_center = 0.542187、y_center = 0.441667看起来抽象,但转成像素坐标就很直观。假设原图是 1920x1080,那么:
# 把归一化的YOLO坐标还原成像素坐标,用于画框或手工检查 img_w, img_h = 1920, 1080 x_center = 0.542187 y_center = 0.441667 w_norm = 0.020312 h_norm = 0.059722 # 计算左上角和右下角 x1 = int((x_center - w_norm / 2) * img_w) y1 = int((y_center - h_norm / 2) * img_h) x2 = int((x_center + w_norm / 2) * img_w) y2 = int((y_center + h_norm / 2) * img_h) print(f"像素坐标: ({x1}, {y1}) -> ({x2}, {y2})")反过来,如果手头有像素坐标的标注,要转成 YOLO 格式,公式是x_center = (x1 + x2) / 2 / img_w、width = (x2 - x1) / img_w,高度同理。中心点坐标除以图片宽、框宽除以图片宽,重命名四个除法。
一个实用的小技巧:随机抽三五个 txt,打印每行数值,如果看到所有坐标都是 0.xxx 带五位小数的浮点,说明是规范的归一化坐标;如果看到一堆几百上千的整数,说明数据没归一化,后面训练必然出问题。别小看这一步判断,很多数据集下载下来名不副实,先查数值范围能省下大把时间。
3. 数据体检三部曲:划分核对、可视化标注与目录规整
3.1 训练/验证/测试集划分逻辑
这套数据的划分相当规整:训练集 1575 张,验证集 450 张,测试集 225 张,比例接近 7:2:1。这不是随便分的——训练集负责让模型见足够多的样本,验证集用于每轮训练后评估和挑权重,测试集则是在整个训练结束后做一次最终检验,模拟模型在没见过的数据上的真实表现。
| 集合 | 图片数 | 标签数 | 用途 |
|---|---|---|---|
| 训练集 | 1575 | 1575 | 训练模型权重 |
| 验证集 | 450 | 450 | 调参、选 best.pt |
| 测试集 | 225 | 225 | 最终评估 |
很多人拿到别人分享的数据集,习惯性把三个文件夹合并到一起重新划分。其实没必要,官方已经按 7:2:1 分好了,直接沿用这个划分反而更省事。但建议你手动核对一下每个文件夹里的图片数和 txt 数是否一一对应,哪怕差一张都说明有文件丢了。
3.2 可视化标注:show.py 的用法与原理
资源包自带的show.py就是干这个的——传一张图片进去,自动读取同名的 txt 标签,把边界框画出来,保存在当前目录。用法很直接:
python show.py 0453.jpg运行完当前目录下会出现一张带边界框的图,你可以一眼看出标注质量如何:框是否贴合目标、类别有没有标错、有没有漏标。这也是我把可视化放在训练之前的原因——不可见的标注问题会在训练后期集中爆发,而且非常难定位。
show.py内部做的事情并不复杂,如果你要自己实现或者扩展一个更灵活的版本,等价逻辑是这样的:
import cv2 def draw_yolo_boxes(img_path, txt_path, class_names, output_path): img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if not parts: continue cls_id, x_c, y_c, bw, bh = map(float, parts) # 还原到像素坐标 x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) color = (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) label = class_names[int(cls_id)] cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) cv2.imwrite(output_path, img) # 调用示例 draw_yolo_boxes( "0453.jpg", "0453.txt", ["car", "traffic_sign", "lane_marking", "pedestrian", "motorcyclist", "cyclist"], "0453_bbox.jpg" )关键点在于cv2.rectangle需要的是左上角和右下角的像素坐标,而 YOLO 格式存的是中心点加宽高,所以必须先把归一化坐标还原。class_names[int(cls_id)]做数组索引,要求类别列表顺序和标注里的编号一致。
可视化时建议重点看三类图:有行人的、有车道线的、有摩托车的。这三类目标出现频率差异大,最容易暴露标注问题。
3.3 目录规整:让 YOLO 直接读得懂
资源包解压后,图片和 txt 大概率放在同一层目录里。ultralytics 框架默认读取images/和labels/分开的目录结构,所以第一步先把文件按集合归位:
# 以训练集为例,把图片和标签分别归入对应目录 mkdir -p datasets/detroit_street/images/train datasets/detroit_street/labels/train mkdir -p datasets/detroit_street/images/val datasets/detroit_street/labels/val mkdir -p datasets/detroit_street/images/test datasets/detroit_street/labels/test mv train_images/*.jpg datasets/detroit_street/images/train/ mv train_labels/*.txt datasets/detroit_street/labels/train/ mv val_images/*.jpg datasets/detroit_street/images/val/ mv val_labels/*.txt datasets/detroit_street/labels/val/ mv test_images/*.jpg datasets/detroit_street/images/test/ mv test_labels/*.txt datasets/detroit_street/labels/test/归位之后,跑一条命令确认图片和标签一一对应:
# 分别统计图片和txt数量,两张数值一致才算对齐 ls datasets/detroit_street/images/train/*.jpg | wc -l ls datasets/detroit_street/labels/train/*.txt | wc -l如果数字对不上,别往下走,先排查缺失的文件。这一步做好了,后面训练配置只需要写五行的 yaml 就能开跑。
4. 喂给 YOLOv8/YOLOv11 训练:dataset.yaml 与关键训练参数
4.1 写一个五行的 dataset.yaml
ultralytics 框架的统一入口是数据集配置文件。训练、验证、预测全部基于这个 yaml 里的路径和类别名。新建detroit_street.yaml:
# 底特律街景目标检测数据集配置 path: /home/user/datasets/detroit_street # 数据集根目录,换成你实际的绝对路径 train: images/train val: images/val test: images/test names: 0: car 1: traffic_sign 2: lane_marking 3: pedestrian 4: motorcyclist 5: cyclistpath建议写绝对路径,不要用相对路径,因为训练脚本的工作目录和数据集目录经常不是同一个。names的顺序就是 class_id 的映射,和上一章讲的类别 txt 行序必须完全一致。另外注意test字段可以省略,但如果你的测试集目录已经准备好,写上没坏处——训练完成后 eval 阶段可以直接用。
4.2 训练命令与关键参数选择
先确保 ultralytics 已安装:
pip install ultralytics然后执行训练。这里给一条我常用的起步命令:
# 用yolov8s起步,显存不够换成yolov8n yolo detect train \ model=yolov8s.pt \ data=detroit_street.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ workers=4model=yolov8s.pt会从官方下载在 COCO 上预训练的权重,并在你的数据上微调,比自己从零初始化收敛快得多,2250 张图也就 30 到 50 轮能看出效果。如果你用的是最新 ultralytics 版本,也可以换成yolov11n.pt或yolov11s.pt,yaml 配置完全不用改。
几个参数的取舍经验:
| 参数 | 设置 | 说明 |
|---|---|---|
| epochs | 100 | 数据量不大,100 轮足够,多了容易过拟 |
| imgsz | 640 | 默认值,小目标多的场景可加到 960 |
| batch | 16 | 根据显存调,16G 显存跑得动 s 模型 |
| workers | 4 | 数据加载线程数,Windows 上建议设 0/2 |
| patience | 50 | 验证指标连续 50 轮不提升就早停 |
4.3 类别不均衡的应对
这 6 类目标的出现频率差别很大。街景里车道线和汽车几乎每张图都有,但摩托车手、骑行者的出现频率可能低一个数量级。我拿到数据后会先统计一下分布:
import os from collections import Counter counter = Counter() label_dir = "datasets/detroit_street/labels/train" for txt_name in os.listdir(label_dir): if not txt_name.endswith(".txt"): continue with open(os.path.join(label_dir, txt_name), "r") as f: for line in f: cls_id = int(line.strip().split()[0]) counter[cls_id] += 1 print(counter)如果发现尾部类别明显不足,优先做法是给 yaml 里加上每个类别的权重,让 loss 对样本少的类别更敏感。另一种做法是只对尾部类别做 Mosaic 增强比例调整,或者干脆放弃区分摩托车手和骑行者,把这两类合并成一个「两轮车」类别。具体怎么选,取决于你的业务要不要区分这两类。
5. 避坑清单:6条实测踩坑记录
5.1 现象:loss 第一轮就是 nan,或者训练跑着跑着模型输出为空
原因:数据里有 txt 文件是空文件,或者某一行数值全部为 0,导致坐标计算出现除零或者 log(0)。
解决:训练前强制跑一遍扫描,把所有文件字节数小于 10 的标签挑出来删掉或补标注。YOLO 对空标签容忍度还行,但全 0 的标签会造成梯度爆炸。
# 找出体积过小或空行的标签文件 find labels/ -name "*.txt" -size -10c5.2 现象:训练结束 mAP 始终为 0,验证集一张图都没检测出来
原因:多半是标签里的 class_id 超出了 names 的定义范围。比如names只写了 6 类,但某个 txt 里出现了class_id = 6,模型学到的类别编号和验证时的编号对不上,预测阶段全部被过滤。
解决:用脚本扫描全部标签,打印所有出现的类别编号:
# 找出所有txt里的最大类别编号,不能等于或超过names长度 import os label_dir = "labels/train" max_cls = 0 for name in os.listdir(label_dir): if not name.endswith(".txt"): continue with open(os.path.join(label_dir, name)) as f: for line in f: cls = int(line.strip().split()[0]) max_cls = max(max_cls, cls) print("最大类别编号:", max_cls)5.3 现象:可视化出来的框全部跑到图外,或者变形缩成一坨
原因:坐标没有归一化,直接用了像素值,再或者标注框的宽高写成了整张图像的比例。这类情况最常见于用 LabelImg 导出的 VOC 数据转 YOLO 时,转换脚本里忘了除以 图片宽高这一步。
解决:先看 txt 里的数值。如果出现400 300 800 600这种,基本就是像素坐标没归一化。转换公式很简单,写一个批量脚本重新算一遍,除以对应图片的宽高。
5.4 现象:图片和 txt 对不上号,一张图配了另一张图的标签
原因:Windows 下解压时文件名被改写了扩展名,或者某个文件拷贝时被跳过,导致一张图的小轿车被标成了一条车道线。
解决:用 Python 对比images/和labels/两个目录的文件 basename,注意去掉扩展名之后比对:
import os img_dir = "images/train" label_dir = "labels/train" img_names = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")} label_names = {os.path.splitext(f)[0] for f in os.listdir(label_dir) if f.endswith(".txt")} print("有图无标签:", len(img_names - label_names)) print("有标签无图:", len(label_names - img_names))两边集合完全相等再往下走。
5.5 现象:模型对远处小目标一塌糊涂,漏检严重
原因:街景图里车道线和行人都可能很小,实际边界框可能只有几十个像素。imgsz=640 时这些小目标在下采样后只剩十来个像素,特征几乎消失了。
解决:把imgsz提到 960 或 1280,同时注意 batch 要相应减小,因为显存占用会变大。另外这类场景下优先用带 P2 头的版本或者加大 Mosaic 增强,小目标的召回率提升会很明显。
5.6 现象:类别不均衡导致验证集 mAP 虚高,但实际看一眼预测结果全是车
原因:汽车样本占绝对多数,模型把所有东西都预测成汽车,整体 mAP 也还行,但行人和摩托车手基本没学到。
解决:这也是我前面强调先统计类别分布的原因。对策要么给 yaml 加class_weights,要么合并少数类,要么对少数类做过采样。保险起见,训练完看类别别的 AP,不要只看 aggregate mAP。
6. 进阶验证:用训练好的模型做类别统计与热力检验
6.1 批量预测与类别计数脚本
模型训练完成之后,runs/detect/train/weights/best.pt就是验证集上表现最好的权重。先用测试集跑一遍预测,拿到每张图的分类结果:
from ultralytics import YOLO from collections import Counter model = YOLO("runs/detect/train/weights/best.pt") results = model.predict( source="datasets/detroit_street/images/test", conf=0.4, save=True ) # 统计预测出来的每个类别的框数量 pred_counter = Counter() for result in results: cls_ids = result.boxes.cls.cpu().numpy().astype(int) pred_counter.update(cls_ids.tolist()) print("预测框数量分布:", dict(pred_counter))conf=0.4是一个相对合理的阈值,低了对小目标容易误报,高了漏报增多。我在实际使用中通常测 0.25、0.4、0.5 三档,对比一下再决定部署值。
如果资源包里本身有和测试集对应的标签,直接把预测结果和真实标注做逐类比对,能得到每个类别的 precision 和 recall。这一步比看 global mAP 有用得多。
6.2 用中心点聚合图验证模型注意力
除了数值指标,我更习惯做一张视觉上的检验——把测试集所有目标的中心点提取出来,画成一张热力叠加图。这个做法和常见的特征图可视化思路类似,只不过我们用的是检测框的几何中心,实现成本低很多:
import cv2 import numpy as np # 用训练好的模型跑单张图,取所有框的中心点 image = cv2.imread("datasets/detroit_street/images/test/0453.jpg") results = model.predict(source=image, conf=0.4, verbose=False)[0] heatmap = np.zeros((image.shape[0], image.shape[1]), dtype=np.float32) for box in results.boxes.xyxy.cpu().numpy(): x1, y1, x2, y2 = box cx = int((x1 + x2) / 2) cy = int((y1 + y2) / 2) if 0 <= cx < image.shape[1] and 0 <= cy < image.shape[0]: heatmap[cy, cx] += 1 heatmap = cv2.GaussianBlur(heatmap, (51, 51), 0) heatmap = np.uint8(255 * (heatmap / max(heatmap.max(), 1e-6))) # 和原图融合,红色区域就是模型主要关注的位置 overlay = cv2.addWeighted(image, 0.6, cv2.applyColorMap(heatmap, cv2.COLORMAP_JET), 0.4, 0) cv2.imwrite("detroit_heatmap.jpg", overlay)如果热力分布图集中在道路中央和两侧,而不是分散在全图乱点,说明模型学到了街景目标的真实位置规律。反过来,如果热力图明显偏移或者覆盖在全图上,大概率是标注错位问题,重新回到第 5 章的体检步骤去查。
当然,这套流程不止适用于这个数据集。拿到了类似格式的 txt 标注数据,直接用同样的脚本做一遍体检、配置、训练和验证,基本能覆盖 90% 的常见问题。做目标检测项目到后面你会发现,数据集的干净程度比模型大小更影响最终效果。
从那以后我每拿到一个数据集,都会先把三件事走一遍:对比图片与 txt 的 basename、检查 txt 数值范围是否落在 0 到 1 之间、核对类别 txt 行序与 yaml 的 names 是否一致。这个习惯救了我好几次,十分钟的体检能省掉后面一整天的排错。这份底特律街景数据也是一样,建议你在开训前多花这几步,希望帮到你。
本文还有配套的精品资源,点击获取