
简介底特律街景目标检测数据集面向计算机视觉入门与实战开发者提供六类街景目标标注数据涵盖汽车、交通标志、车道线、行人、摩托车手与骑行者可直接用于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, encodingutf-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汽车car1交通标志traffic_sign2车道线lane_marking3行人pedestrian4摩托车手motorcyclist5骑行者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。这不是随便分的——训练集负责让模型见足够多的样本验证集用于每轮训练后评估和挑权重测试集则是在整个训练结束后做一次最终检验模拟模型在没见过的数据上的真实表现。集合图片数标签数用途训练集15751575训练模型权重验证集450450调参、选 best.pt测试集225225最终评估很多人拿到别人分享的数据集习惯性把三个文件夹合并到一起重新划分。其实没必要官方已经按 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, encodingutf-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.yamlultralytics 框架的统一入口是数据集配置文件。训练、验证、预测全部基于这个 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 \ modelyolov8s.pt \ datadetroit_street.yaml \ epochs100 \ imgsz640 \ batch16 \ workers4modelyolov8s.pt会从官方下载在 COCO 上预训练的权重并在你的数据上微调比自己从零初始化收敛快得多2250 张图也就 30 到 50 轮能看出效果。如果你用的是最新 ultralytics 版本也可以换成yolov11n.pt或yolov11s.ptyaml 配置完全不用改。几个参数的取舍经验参数设置说明epochs100数据量不大100 轮足够多了容易过拟imgsz640默认值小目标多的场景可加到 960batch16根据显存调16G 显存跑得动 s 模型workers4数据加载线程数Windows 上建议设 0/2patience50验证指标连续 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 现象模型对远处小目标一塌糊涂漏检严重原因街景图里车道线和行人都可能很小实际边界框可能只有几十个像素。imgsz640 时这些小目标在下采样后只剩十来个像素特征几乎消失了。解决把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( sourcedatasets/detroit_street/images/test, conf0.4, saveTrue ) # 统计预测出来的每个类别的框数量 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))conf0.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(sourceimage, conf0.4, verboseFalse)[0] heatmap np.zeros((image.shape[0], image.shape[1]), dtypenp.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 是否一致。这个习惯救了我好几次十分钟的体检能省掉后面一整天的排错。这份底特律街景数据也是一样建议你在开训前多花这几步希望帮到你。本文还有配套的精品资源点击获取