简介:这份资源面向无人机视觉与目标检测方向的开发者、研究生及算法工程师,提供一套真实场景下的车辆检测数据集,可用于无人机航拍车辆识别项目,也可作为通用车辆检测数据的场景补充。数据集共1000张高质量图片,覆盖城市道路行驶车辆、道边停车、停车场、小区车辆以及车辆遮挡与严重遮挡等多种情形,类别划分为轿车car、货车van和巴士bus三类,均经labelimg精细标注,并同步提供VOC(xml)、COCO(json)、YOLO(txt)三种主流格式,可直接投入YOLO等算法训练。资源包为1个PDF文件,约2MB,内含数据集基本情况介绍与获取方式,因数据本体较大故托管于网盘。随附YOLO11一键训练脚本,支持GPU、CPU及Mac(M芯片)多平台方案,并给出博主训练结果日志供参考。目前已有261人学习下载,适合希望快速验证无人机车辆检测方案、减少数据准备成本的读者。
1. 无人机视角下的车辆检测:1000 张图、三种标签格式与 YOLO11 一键训练到底能不能落地
拿到这个标题,多数人第一反应是「数据集 + 训练脚本」的常规组合,但真正卡住工程进度的往往不是模型本身,而是标签格式转换和跨平台环境这三件脏活。无人机航拍车辆检测和地面固定摄像头完全是两个问题:视角俯仰变化大、车辆像素占比小、运动模糊和光照突变频繁,公开的车辆检测数据集 bdd100 这类车载视角数据迁移过来会明显掉点。这个方案给的是 1000 张无人机视角图,配 VOC、COCO、YOLO 三种格式标签,外加支持 GPU、CPU、Mac 三平台的 YOLO11 一键训练脚本。它适合两类人:一是想快速验证无人机视觉感知链路是否跑得通的算法工程师,二是手头有少量自有航拍图、需要一个能直接改的基线工程的学生或独立开发者。1000 张不算多,但作为迁移学习的起点和格式转换的模板,够用。
2. 三种标签格式的差异与转换:VOC、COCO、YOLO 到底该用哪个
2.1 三种格式的坐标系与文件组织差异
VOC 格式的核心是 XML 文件,每张图对应一个同名.xml,边界框用xmin/ymin/xmax/ymax四个绝对像素值表示,坐标原点在左上角。它的好处是可读性强、工具链老牌,LabelImg 默认就输出这个。缺点是文件数量翻倍,1000 张图就是 1000 个 XML,批量处理时 IO 压力不小。
COCO 格式把所有标注塞进一个 JSON 文件,结构分images、annotations、categories三段,边界框用[x, y, width, height]表示,同样是绝对像素,但id映射关系必须严格对应,错一个 id 整批数据就废了。它适合做多类别、多任务(检测 + 分割 + 关键点)的统一管理,但手写容易出错。
YOLO 格式最简洁,每张图一个.txt,每行class_id x_center y_center width height,全部是归一化到 0~1 的相对值。它没有冗余信息,训练时读取最快,但可读性差,脱离图片根本看不出框在哪。
| 格式 | 单图标注文件 | 坐标类型 | 类别表示 | 典型工具 |
|---|---|---|---|---|
| VOC | .xml | 绝对像素 | 标签名 | LabelImg |
| COCO | 单一 .json | 绝对像素 | 数字 id + 名称映射 | Labelme、CVAT |
| YOLO | .txt | 归一化相对值 | 数字 id | LabelImg、Roboflow |
选哪个取决于你的下游任务。只跑 YOLO11 训练,直接用 YOLO 格式最省事;要做多模型对比或接入 MMDetection,COCO 更通用;VOC 适合作为中间交换格式,因为转换脚本最多、容错性最好。
2.2 VOC 转 YOLO 的完整脚本与四个边界坑
下面这个脚本是我常用的 VOC 转 YOLO 版本,处理 1000 张图大约几秒跑完。关键点在于归一化时的除零保护和类别映射的一致性。
import os import xml.etree.ElementTree as ET # 类别映射,必须与 data.yaml 中的 names 顺序一致 CLASS_MAP = {"car": 0, "truck": 1, "bus": 2, "van": 3} def voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() # 读取图片宽高,用于归一化 size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) if img_w == 0 or img_h == 0: print(f"skip zero size: {xml_file}") continue 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)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) if xmax <= xmin or ymax <= ymin: continue 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 = os.path.join(out_dir, xml_file.replace(".xml", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines)) voc_to_yolo("./annotations_xml", "./images", "./labels_yolo")逻辑说明:先解析 XML 拿到图片宽高,再把每个 object 的绝对坐标转成归一化中心点加宽高。参数方面,CLASS_MAP必须和训练时data.yaml的names列表顺序完全一致,否则模型学到的类别会整体错位。四个容易翻车的边界坑:一是xmin可能为负或超过图宽,无人机图边缘截断车辆很常见,必须裁剪;二是xmax <= xmin的退化框要直接丢弃,否则归一化后宽高为负,训练时 loss 直接 NaN;三是类别名大小写和空格,"car"和"Car "会被当成两个类;四是 XML 里size字段偶尔缺失,需要加异常捕获或从图片实际尺寸读取。
2.3 COCO 与 YOLO 互转时 id 映射的坑
COCO 转 YOLO 时,annotations里的category_id往往不是从 0 连续开始的,比如 COCO 官方 80 类里person是 1,但你的数据集可能只有 4 类且 id 是 1、3、7、9。直接拿category_id当 YOLO 的class_id会导致类别索引越界或空洞。正确做法是建一个category_id -> 连续索引的映射表,再写入 txt。反过来 YOLO 转 COCO 时,image_id和annotation_id必须全局唯一,1000 张图如果每张平均 5 个框就是 5000 个 annotation,id 重复会让评估脚本报错。我一般用递增计数器生成,不用图片名哈希,避免碰撞。
3. YOLO11 在 GPU、CPU、Mac 三平台的训练脚本拆解
3.1 一键脚本的目录结构与参数入口
一个能跨三平台跑的脚本,核心是把设备检测和路径处理做成自动的,而不是写死device=0。下面是我常用的train.py骨架,配合data.yaml使用。
import os import torch from ultralytics import YOLO def pick_device(): if torch.cuda.is_available(): return "0" # 单卡 GPU if torch.backends.mps.is_available(): return "mps" # Apple Silicon return "cpu" def main(): device = pick_device() print(f"using device: {device}") model = YOLO("yolo11n.pt") # 从预训练权重起步 model.train( data=os.path.abspath("./data.yaml"), epochs=100, imgsz=640, batch=16 if device != "cpu" else 4, device=device, workers=4 if device != "cpu" else 0, project="./runs", name="drone_vehicle", patience=20, cache=False, ) if __name__ == "__main__": main()逻辑说明:pick_device按 CUDA、MPS、CPU 的优先级返回设备字符串,Ultralytics 内部会自动解析。参数上,imgsz=640是无人机小目标检测的常用起点,再小会丢车辆细节,再大 CPU 和 Mac 显存扛不住。batch在 CPU 上必须降到 4 甚至 2,否则内存直接爆。workers在 Mac 和 Windows 上设 0 可以避免多进程 dataloader 的玄学卡死。patience=20表示 20 轮无提升就早停,1000 张图通常 60~80 轮就收敛。
data.yaml的内容如下,路径建议用绝对路径,相对路径在不同平台的工作目录下容易翻车:
path: /abs/path/to/dataset train: images/train val: images/val names: 0: car 1: truck 2: bus 3: van3.2 GPU、CPU、Mac 三平台的实测差异与参数调整
GPU 平台(以单卡 8G 显存为例):batch=16、imgsz=640下显存占用约 5~6G,100 轮大约 40 分钟。如果显存只有 4G,把batch降到 8,同时开amp=True(默认开启)用混合精度省显存。注意cache=True会把 1000 张图全部读进内存,GPU 机器内存够的话能提速 20% 左右,但内存小于 16G 别开。
CPU 平台:这是最容易被低估的场景。batch=4、imgsz=640下,1000 张图 100 轮可能要跑 6~10 小时,取决于 CPU 核心数。建议把imgsz降到 512,epochs降到 50,先验证链路通不通。workers设 0 避免进程调度开销。CPU 训练时torch的线程数可以用torch.set_num_threads(os.cpu_count())显式设置,默认有时只用一个核。
Mac(Apple Silicon):MPS 后端对 YOLO11 的支持已经比较稳定,但有几个已知问题。一是某些算子会回退到 CPU,导致速度不如预期,可以在训练日志里看mps和cpu的切换频率。二是batch超过 16 容易触发内存不足,建议 8。三是 MPS 上amp混合精度支持不完整,遇到 NaN 就把amp=False关掉。M 系列芯片跑 1000 张图 100 轮大约 1.5~3 小时,比 CPU 快但远慢于 GPU。
提示:三平台切换时,
runs目录下的权重文件不通用,GPU 训练的.pt可以在 CPU 和 Mac 上推理,但反过来 MPS 训练的权重在 CUDA 上加载偶尔会报设备不匹配,建议推理时统一用model.to("cpu")再加载。
3.3 训练前必须检查的数据集划分与缓存
1000 张图按 8:1:1 划分,训练 800、验证 100、测试 100。划分脚本要保证同一场景的连续帧不会同时出现在训练和验证集里,否则验证指标虚高。无人机航拍如果是视频抽帧,相邻帧几乎一样,随机划分会让验证集泄漏。我一般按拍摄批次或时间戳分组划分。
import os, random, shutil def split_dataset(img_dir, label_dir, out_root, ratios=(0.8, 0.1, 0.1)): imgs = sorted([f for f in os.listdir(img_dir) if f.endswith((".jpg", ".png"))]) random.seed(42) random.shuffle(imgs) n = len(imgs) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) splits = { "train": imgs[:n_train], "val": imgs[n_train:n_train + n_val], "test": imgs[n_train + n_val:], } for split, files in splits.items(): img_out = os.path.join(out_root, "images", split) lbl_out = os.path.join(out_root, "labels", split) os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for f in files: shutil.copy(os.path.join(img_dir, f), os.path.join(img_out, f)) lbl = f.rsplit(".", 1)[0] + ".txt" src_lbl = os.path.join(label_dir, lbl) if os.path.exists(src_lbl): shutil.copy(src_lbl, os.path.join(lbl_out, lbl)) split_dataset("./images", "./labels_yolo", "./dataset")逻辑说明:random.seed(42)保证可复现,shutil.copy保留原文件不动。参数ratios可以按数据量调整,1000 张用 8:1:1 合适,如果只有 300 张建议 7:2:1 给验证集多一点。划分后检查labels/train和images/train的文件名是否一一对应,缺 label 的图会被 Ultralytics 自动跳过,但不会报错,容易漏掉。
4. 无人机车辆检测的避坑与排查:从 loss 不降到框全偏
4.1 训练 loss 不下降或震荡
现象:前 10 轮box_loss和cls_loss几乎不动,或者来回跳。原因通常是学习率过大或数据标签有问题。先检查data.yaml的names数量和标签里的class_id最大值是否一致,不一致会静默忽略越界类别。再确认图片路径没有中文和空格,Ultralytics 在某些版本对中文路径支持不好。解决:把lr0从默认 0.01 降到 0.001,batch调小,先用 10 张图过拟合测试,如果 10 张都学不会,一定是标签或路径问题,不是模型问题。
4.2 验证集 mAP 正常但推理框全偏
现象:训练日志里mAP50有 0.6 以上,但拿单张图推理时框位置明显偏移或尺寸不对。原因多半是推理时的imgsz和训练不一致,或者图片在预处理时被 letterbox 填充后坐标没还原。解决:推理时显式指定imgsz=640,和训练保持一致。如果用的是自己写的后处理,检查 letterbox 的缩放比例和 padding 偏移有没有正确逆变换。另一个常见原因是验证集和测试集的图片方向(EXIF)不同,手机或无人机拍的图带旋转信息,PIL 读取时可能自动旋转而 OpenCV 不会,导致坐标错位。
4.3 Mac 上 MPS 报错或速度异常慢
现象:训练几轮后报MPS backend out of memory或Placeholder storage has not been allocated。原因是 MPS 的内存管理和 CUDA 不同,缓存不会自动释放。解决:把batch降到 4 或 8,imgsz降到 512,关掉cache。如果报算子不支持,设置环境变量PYTORCH_ENABLE_MPS_FALLBACK=1让不支持的算子回退 CPU,但速度会掉。速度异常慢时检查是否误用了device="cpu",MPS 设备字符串是"mps"不是"gpu"。
4.4 小目标漏检严重
现象:大车能检出,远处小车几乎全漏。原因是 640 分辨率下,无人机 100 米高度拍的车辆可能只有 10~20 像素,YOLO11 的 P3 特征图下采样 8 倍后只剩 1~2 个像素。解决:把imgsz提到 1280 做对比实验,或者用切片推理(SAHI)把大图切小块分别检测再合并。另一个方向是改模型结构,在 YOLO11 里增加 P2 检测头,但 1000 张图训 P2 容易过拟合,建议先试切片推理。
4.5 类别不平衡导致 bus 和 van 几乎检不出
现象:car 的 AP 有 0.7,bus 和 van 只有 0.1。原因是 1000 张图里 car 占 80% 以上,bus 和 van 样本太少。解决:先用cls_pw类别权重,Ultralytics 默认开启但效果有限。更直接的是对少数类做过采样,把含 bus 和 van 的图复制多份,或者用 mosaic 和 mixup 增强时提高少数类参与概率。如果 bus 和 van 加起来不到 50 个框,建议合并成一个large_vehicle类,先保证检出不漏,再考虑细分。
5. 用 1000 张图把 YOLO11 微调到可用的进阶技巧
1000 张图做无人机车辆检测,如果直接从头训,mAP50 大概在 0.4~0.5 徘徊。我的习惯是分两阶段:第一阶段用 COCO 预训练的yolo11s.pt冻结 backbone 训 30 轮,让检测头先适配车辆类别;第二阶段解冻全部层,用余弦退火学习率训 70 轮。冻结阶段lr0=0.001,解冻后lr0=0.0005,lrf=0.01。这样比直接端到端训收敛更稳,最终 mAP50 通常能到 0.65~0.75,取决于标注质量。
验证时不要只看mAP50,无人机场景要单独统计小目标(面积小于 32x32)的AP_small。Ultralytics 的验证输出里默认不细分尺寸,可以用model.val(split="test")后手动按框面积过滤统计。另一个实用技巧是导出 ONNX 后用onnxruntime测单图延迟,GPU 上 YOLO11n 在 640 分辨率大约 5~8ms,CPU 上 80~150ms,Mac MPS 上 20~40ms。这些数字决定你能不能上机载实时推理。
最后说个我踩过的坑:有次训完发现验证集 mAP 很高,但实际飞的时候框全在车后面拖一截。查了两天才发现是标注时用了视频抽帧的上一帧坐标,标签和图片错位了一帧。1000 张图里如果有几十张这种错位,模型就会学出系统性偏移。所以拿到数据集第一件事不是训,是随机抽 20 张把框画到图上肉眼过一遍。这个习惯帮我省了无数次后悔药。希望帮到你。
本文还有配套的精品资源,点击获取