
简介这是一份面向目标检测学习与开发者的俯拍道路场景数据集聚焦城市交通监控与自动驾驶辅助等应用适合使用YOLO系列网络进行训练与验证的研究人员和开发者。压缩包共2000个文件以1999个txt标签文件和1个py脚本为主整体约463.11MB标签文件与图片一一对应py脚本可用于数据读取或格式转换方便直接接入训练流程。数据集包含训练集、验证集及对应标注已统一处理为YOLO格式并完成旋转、缩放、裁剪、颜色变化等数据增强有助于降低过拟合、提升模型泛化能力。类别共10类涵盖汽车、摩托车、行人、卡车等常见道路目标具体类别可参考class文本文件。目前已有1614人学习下载读者可据此快速搭建检测实验、验证算法效果省去图像收集与标注成本也可作为教学演示与项目原型的素材模板。1. 俯拍道路数据集3000 张图背后的检测任务到底难在哪俯拍视角的道路交通目标检测和你在 COCO 上刷 mAP 完全不是一回事。摄像头架在路灯杆、天桥或楼顶往下拍行人变成一坨几十像素的色块电动车和自行车在俯视投影下几乎重叠远处车辆被透视压缩成一条线。这个数据集给了超过 3000 张图片和对应标签覆盖交通工具和行人两类核心目标解决的正是「高空监控场景下目标小、遮挡重、尺度跨度大」这三个老大难问题。它适合谁做智慧交通、园区安防、无人机巡检的算法工程师以及想拿真实俯拍数据跑通 YOLOv8 训练全流程的从业者。行人检测和交通工具检测在这个视角下共享同一套标注体系意味着你可以一次训练、多任务复用省掉分别建两个数据集的成本。但别急着开训俯拍数据的坑从你打开第一张图就开始了。2. 俯拍数据集的目标分布与标注格式拆解2.1 俯拍视角下交通工具与行人的尺度分布规律俯拍图像的目标尺度分布和水平视角有本质差异。水平拍摄时近处行人和远处车辆可能占据相近像素面积俯拍时目标像素面积主要由「离画面中心的距离」和「安装高度」共同决定。安装高度 15 米、焦距 8mm 的典型路灯杆机位画面中心的行人约 40×20 像素画面边缘可能只有 15×8 像素。车辆更极端一辆 4.6 米轿车在画面中心约 120×60 像素到边缘压缩到 50×25 像素。这意味着什么你在 COCO 上表现良好的模型直接拿来跑俯拍数据小目标召回率会掉 20 个点以上。原因是俯拍数据里小目标占比远高于常规数据集。3000 张图里如果按 COCO 的 small/medium/large 划分标准面积 32²、32²~96²、96²俯拍道路场景的 small 目标占比通常在 55%~70%而 COCO 只有 41%。所以选型上第一件事是确认你的 anchor 或 anchor-free 策略能不能覆盖这个尺度分布。YOLOv8 默认 anchor 在 COCO 上聚类得到直接迁移到俯拍数据最小的 anchor 可能还是偏大。常见做法是先用 k-means 在自己的标注框上重新聚类 anchor或者直接用 anchor-free 的 YOLOv8 并调小检测头的 stride 覆盖范围。2.2 标签格式确认从 XML/JSON 到 YOLO txt 的转换拿到数据集第一件事不是写训练脚本是确认标签格式。俯拍道路数据集常见的标注格式有三种Pascal VOC XML、COCO JSON、YOLO txt。超过 3000 张图的规模如果标签是 XML 或 JSON你需要先转成 YOLO 格式才能喂给 YOLOv8。转换逻辑不复杂但边界条件容易翻车。VOC XML 里 bbox 是xmin, ymin, xmax, ymax绝对坐标YOLO 需要class_id, x_center, y_center, width, height归一化坐标。转换脚本如下import xml.etree.ElementTree as ET import os from pathlib import Path # 类别映射根据数据集实际类别名调整 CLASS_MAP {person: 0, car: 1, bus: 2, truck: 3, bicycle: 4, motorbike: 5} def voc_to_yolo(xml_path, img_w, img_h, output_dir): 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 CLASS_MAP: continue # 跳过未定义类别避免训练时索引越界 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_w)) ymin max(0, min(ymin, img_h)) xmax max(0, min(xmax, img_w)) ymax max(0, min(ymax, img_h)) if xmax xmin or ymax ymin: continue # 宽高为0的无效框直接丢弃 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{CLASS_MAP[cls_name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path Path(output_dir) / (Path(xml_path).stem .txt) out_path.write_text(\n.join(lines))这段脚本有三个关键处理类别过滤、边界裁剪、无效框丢弃。俯拍数据里经常出现标注框超出图像边界的现象因为标注员在画框时可能把画面外的部分也算进去了。如果不裁剪归一化坐标会出现负数或大于 1 的值YOLOv8 训练时虽然不会报错但这些框的梯度会污染回归分支。参数说明CLASS_MAP必须和你的data.yaml里names列表顺序一致否则类别索引错位训练出来的模型会把行人识别成车。img_w和img_h从对应图片读取不要硬编码因为数据集里可能混有不同分辨率的图像。2.3 data.yaml 配置与数据集划分策略转换完标签下一步是写data.yaml。YOLOv8 的配置文件结构如下path: /data/fisheye_road # 数据集根目录 train: images/train val: images/val test: images/test names: 0: person 1: car 2: bus 3: truck 4: bicycle 5: motorbike划分策略上3000 张图按 7:2:1 分 train/val/test 是常见做法。但俯拍数据有个特殊点如果数据采集是分时段、分路段的随机划分会导致同一路段同一时段的数据同时出现在训练集和验证集验证指标虚高。正确做法是按采集批次划分比如前 70% 的采集日期做训练中间 20% 做验证最后 10% 做测试。这样验证集才能真正反映模型在新场景下的泛化能力。注意如果数据集本身已经划分好了 train/val不要重新打乱。作者划分时通常已经考虑了场景分布重新随机划分反而可能引入数据泄漏。3. 用 YOLOv8 在俯拍道路数据集上跑通训练3.1 环境搭建与预训练权重选择训练环境用 PyTorch Ultralytics 是最省事的路径。CUDA 版本根据你的显卡驱动选30 系卡用 CUDA 11.8 以上40 系卡建议 CUDA 12.1。安装命令pip install ultralytics8.1.0 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118预训练权重选yolov8s.pt还是yolov8m.pt俯拍道路场景的目标类别少通常 5~8 类但小目标多。我的经验是如果你只有一张 8G 显存的卡yolov8s是性价比最高的选择输入 640 时 batch size 能开到 16。如果有 24G 显存直接上yolov8m小目标召回率能高 3~5 个点。不要一上来就用yolov8x3000 张图的规模撑不起大模型的参数量过拟合风险很高。3.2 训练命令与关键超参设置训练脚本用命令行或 Python API 都行我习惯用 Python API 方便记录参数from ultralytics import YOLO model YOLO(yolov8s.pt) results model.train( data/data/fisheye_road/data.yaml, epochs150, imgsz640, batch16, device0, workers8, optimizerAdamW, lr00.001, lrf0.01, momentum0.937, weight_decay0.0005, warmup_epochs3, cos_lrTrue, close_mosaic15, augmentTrue, hsv_h0.015, hsv_s0.7, hsv_v0.4, degrees0.0, translate0.1, scale0.5, fliplr0.5, flipud0.0, mosaic1.0, mixup0.1, patience30, save_period10, projectruns/fisheye_road, nameyolov8s_640, )逐项说明关键参数。imgsz640是起点但俯拍小目标多如果显存允许可以试imgsz960或1280小目标召回率提升明显代价是训练时间翻倍。close_mosaic15表示最后 15 个 epoch 关闭 mosaic 增强让模型在真实分布上收敛这个设置对俯拍数据尤其重要因为 mosaic 拼接会进一步缩小目标像素面积。degrees0.0是因为俯拍图像旋转后目标朝向语义会变行人旋转 90 度还是行人但车辆旋转后可能和道路方向不一致除非你的应用场景允许任意朝向。flipud0.0同理上下翻转在俯拍场景中不自然。scale0.5是缩放增强幅度俯拍数据本身尺度跨度大适度的缩放增强有帮助但不要超过 0.5否则小目标缩完就没了。mixup0.1是轻量混合增强再高会模糊小目标边界。3.3 训练过程监控与指标解读训练启动后重点盯三个指标metrics/mAP50(B)、metrics/mAP50-95(B)、train/box_loss。俯拍数据上mAP50 通常能到 0.85 以上但 mAP50-95 可能只有 0.5 左右差距大是因为小目标定位精度天然偏低这是正常现象不用慌。如果train/box_loss持续下降但val/box_loss在 20 个 epoch 后开始上升说明过拟合了。俯拍数据 3000 张不算多过拟合很常见。对策有三个降低模型规模s 换 n、增强数据增强提高 mosaic 和 scale、加早停patience30已经设了。另一个常见现象是metrics/mAP50(B)在 0.7 附近卡住不涨。这时候先看混淆矩阵大概率是「car 和 bus 混淆」或「bicycle 和 motorbike 混淆」。俯拍视角下轿车和公交车的俯视投影都是矩形只是尺寸不同模型在特征层面确实难分。解决办法是在data.yaml里合并类别或者加一个基于面积的分类后处理。4. 俯拍道路检测的避坑与排查清单4.1 小目标漏检严重mAP 卡在 0.6 上不去现象训练 loss 正常下降但验证集上小目标行人、远处车辆召回率极低mAP50 卡在 0.6 左右。原因YOLOv8 默认的 P3 检测头 stride 为 8对应 640 输入下最小感受野约 8×8 像素。俯拍数据里大量目标在 10~20 像素之间P3 特征图上的响应很弱。另外默认 anchor 聚类自 COCO最小 anchor 偏大。解决两个方向。一是提高输入分辨率到 960 或 1280让目标在特征图上的像素面积翻倍。二是修改模型结构增加 P2 检测头stride4Ultralytics 支持通过model.yaml自定义。实测增加 P2 头后小目标召回率能提升 8~12 个点代价是推理速度下降约 30%。4.2 验证集指标虚高测试集一跑就崩现象验证集 mAP50 到 0.9测试集只有 0.6。原因数据划分时没有按采集批次隔离同一路段同一时段的数据同时进了训练集和验证集。模型记住了场景背景而不是目标特征。解决重新按采集日期或路段 ID 划分数据集。如果数据集没有提供采集元信息用图像文件名前缀或 EXIF 时间戳做分组。分组后验证集指标会下降但测试集指标会更真实。4.3 训练到一半 loss 变成 NaN现象前 30 个 epoch 正常突然 loss 变 NaN训练中断。原因俯拍数据标签里存在归一化坐标超出 [0,1] 范围的框或者宽高为 0 的无效框。YOLOv8 的损失函数在计算 CIoU 时如果宽高为 0 会除零。解决在转换脚本里加边界裁剪和无效框过滤参考 2.2 节代码。已经转好的标签用脚本批量检查import os from pathlib import Path def check_labels(label_dir): bad_files [] for txt in Path(label_dir).glob(*.txt): for i, line in enumerate(txt.read_text().strip().split(\n)): if not line: continue parts line.split() if len(parts) ! 5: bad_files.append((txt.name, i, 字段数不对)) continue cls, x, y, w, h map(float, parts) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_files.append((txt.name, i, f坐标越界: {x},{y},{w},{h})) return bad_files bad check_labels(/data/fisheye_road/labels/train) print(f发现 {len(bad)} 个问题标签) for f in bad[:10]: print(f)4.4 推理时同一目标被重复检测现象一辆车被画了两个框或者一个行人被检测成 person 和 bicycle 两个类别。原因俯拍视角下目标重叠严重NMS 的 IoU 阈值默认 0.7 可能偏高导致重叠框没被抑制。另外如果训练时类别定义有歧义比如电动车既标了 bicycle 又标了 motorbike模型会学到矛盾标签。解决推理时降低 NMS IoU 阈值到 0.5用model.predict(..., iou0.5)。如果是类别歧义问题回到标注层面合并类别重新训练。4.5 模型在夜间或阴影路段完全失效现象白天场景检测正常夜间或树荫遮挡路段漏检率飙升。原因俯拍数据集的采集时段如果集中在白天模型没学过夜间光照分布。另外阴影路段的目标对比度低RGB 特征区分度不够。解决如果数据集本身没有夜间样本这个问题无法靠训练技巧解决只能补数据。如果数据集有夜间样本但模型没学好检查数据增强里的hsv_v是否设得太低适当提高亮度扰动范围。另一个方向是引入红外或事件相机数据做多模态融合但这超出了单数据集的范畴。5. 把 3000 张俯拍数据用出 3 万张的效果3000 张图在目标检测领域不算富裕但俯拍道路场景有个天然优势目标类别少、场景结构固定。这意味着你可以用「强增强 迁移学习 伪标签」的组合拳把数据效率拉满。先说强增强。常规的 mosaic、mixup、HSV 扰动只是基础俯拍数据真正有效的是「随机裁剪拼接」和「多尺度训练」。随机裁剪拼接的逻辑是从不同图像中裁出包含目标的区域拼成一张新图这样每张图的目标数量和背景分布都变了模型见过的组合数指数级增长。Ultralytics 的 mosaic 增强本质上就是这个思路但默认只拼 4 张你可以通过自定义 dataloader 拼 9 张甚至 16 张。多尺度训练则是每 10 个 epoch 随机换一次imgsz比如 640、768、896 轮换让模型适应不同尺度下的目标外观。再说迁移学习。如果你手头有 COCO 预训练权重不要只做初始化就完事。更好的做法是分阶段微调第一阶段冻结 backbone只训检测头 20 个 epoch让检测头先适应俯拍数据的类别分布第二阶段解冻全部层用较小的学习率比如 0.0001再训 100 个 epoch。这样比端到端直接训收敛更快最终 mAP 也更高。我实测过分阶段微调比直接训的 mAP50 高 2~3 个点。伪标签是最后一步。用训练好的模型在未标注的俯拍视频帧上跑推理把置信度高于 0.7 的检测框作为伪标签和原始 3000 张图合并训练。注意伪标签要加一个「弱增强」策略因为伪标签本身有噪声强增强会放大噪声。伪标签的类别平衡也要控制如果模型对行人检测偏弱伪标签里行人样本会偏少需要手动过采样。验证这套组合拳是否有效不能只看 mAP。我习惯看三个数小目标召回率、类别间混淆矩阵的对角线均值、以及模型在「训练集未覆盖路段」上的 mAP。前两个从 Ultralytics 的验证输出里直接读第三个需要你手动留出一个路段做测试。如果这三个数都在涨说明数据效率确实提升了如果只有 mAP50 涨但小目标召回率没动大概率是过拟合了训练场景。最后说一个我踩过的坑伪标签迭代不要超过两轮。第一轮伪标签能带来 3~5 个点的提升第二轮可能只有 1 个点第三轮开始掉点。原因是模型自己的错误会被伪标签固化越训越偏。我一般跑完第一轮伪标签就停把精力花在补真实标注上。希望帮到你。本文还有配套的精品资源点击获取