☰
无人机车辆检测实战:1000张图、三种标签格式与YOLO11一键训练
2026/9/30 1:37:23 网站建设 项目流程

简介:这份资源面向无人机视觉与目标检测方向的开发者、学生及科研人员,提供一套真实场景下的车辆检测数据集,可用于无人机航拍车辆识别项目,也可作为通用检测数据集的场景补充。数据覆盖城市道路行驶车辆、道边停车、停车场、小区车辆以及车辆遮挡、严重遮挡等多种情形,类别划分为轿车car、货车van和巴士bus三类,标注由labelimg完成,质量较高。资源包内含1个PDF文件,约2MB,主要说明数据集基本情况与获取方式,并配套VOC(xml)、COCO(json)、YOLO(txt)三种主流格式标签,可直接接入YOLO等算法训练。此外还附赠YOLO11一键训练脚本,支持GPU(GPUs)、CPU及Mac芯片多平台方案,并提供博主训练结果日志供参考。目前已有261人学习,适合希望快速搭建无人机车辆检测基线、验证模型效果并减少数据准备成本的读者。

1. 无人机视角下的车辆检测:1000 张图、三种标签格式与 YOLO11 一键训练到底能跑多快

无人机航拍做车辆检测,最劝退新手的从来不是模型结构,而是数据这一关。你手里可能有一段航拍视频,想切帧、标框、转格式、配环境、开训,每一步都能卡住半天。这个标题讲的正是把这套流程压成一条链路:1000 张无人机视角车辆图,配 VOC、COCO、YOLO 三种标签格式,再用一份脚本在 GPU、CPU、Mac 三平台上把 YOLO11 跑起来。它解决的是「有数据但跑不通」和「想复现但格式对不上」这两类问题,适合做无人机视觉感知、遥感图像目标检测、边缘部署预研的从业者和研究生。下面我按自己踩过的顺序,把选型理由、目录结构、格式转换、训练参数和翻车点一次讲清。

2. 先想清楚:无人机车辆检测的数据集该怎么选、怎么切、怎么标

2.1 为什么无人机车辆检测不能直接拿 BDD100K 凑合

很多人的第一反应是去下 BDD100K 或 COCO 里的 car 类,觉得数据量大、标注全。但无人机视角和车载视角的成像差异是结构性的:车载是低角度、近地、透视强,车辆在画面里占的像素多、遮挡关系简单;无人机是俯视或斜俯视,车辆变成几十像素的小目标,密集停车场景下框与框贴在一起,背景里还有大量相似纹理(车顶、路面标线、阴影)。直接拿车载数据训出来的权重,换到航拍图上召回率会明显掉,尤其是小目标增强模块没开的时候。

我一般会先确认三件事:拍摄高度是否集中在 50 到 200 米、车辆像素尺度是否落在 16 到 64 像素区间、是否存在大量静止车辆。这三点决定了你要不要专门做小目标增强、要不要按尺度分层采样。1000 张图不算多,所以切分策略比模型选择更关键,通常按 8:1:1 分训练、验证、测试,并且保证同一段视频的帧不要跨集合,否则验证集精度会虚高。

2.2 1000 张图的目录结构与三种格式的对应关系

数据集到手后,先别急着转格式,把目录理清楚。常见做法是保留一份原始标注,再派生出三套格式,互不覆盖。下面这个结构我用了很久,好处是转换脚本可以幂等重跑:

uav_vehicle_dataset/ ├── images/ # 全部原图,jpg/png 混放也行 │ ├── 000001.jpg │ └── ... ├── annotations_voc/ # VOC:每张图一个 xml │ ├── 000001.xml │ └── ... ├── annotations_coco/ # COCO:单个 json │ └── instances.json ├── labels_yolo/ # YOLO:每张图一个 txt │ ├── 000001.txt │ └── ... └── splits/ ├── train.txt # 每行一个图片绝对/相对路径 ├── val.txt └── test.txt

VOC 的 xml 里size记录宽高、object记录name和bndbox;COCO 的 json 用images、annotations、categories三张表组织;YOLO 的 txt 每行是class cx cy w h,全部归一化到 0 到 1。三者信息等价,差别只在组织方式。转换时最容易丢的是difficult标志和图片宽高,前者影响评测口径,后者一旦写错,YOLO 的归一化坐标会整体偏移。

2.3 标注工具与类别定义:别把 car 和 truck 混成一个类

标注工具用 LabelImg 出 VOC、用 CVAT 或 Roboflow 出 COCO 都行,关键是类别定义要一次定死。无人机车辆检测里,我建议至少区分 car、truck、bus 三类,如果业务只关心「有没有车」,那就只留 vehicle 一类,但不要中途改。类别数变了,YOLO11 的检测头输出维度就变了,旧权重不能直接续训。

标注时注意两个细节:一是截断车辆(画面边缘只露一半)要不要标,我一般标,但会在转换时按可见比例过滤;二是密集停车时框不要重叠过多,IoU 超过 0.7 的重复框要在清洗阶段去掉,否则训练时正样本会打架。1000 张图如果人工标,按每张 30 到 60 个目标算,大概需要 8 到 15 人时,建议先标 100 张跑通全流程再批量标,避免返工。

3. 三种标签格式互转:脚本、参数与四个必查边界

3.1 VOC 转 YOLO:归一化坐标和类别映射

VOC 的bndbox是左上角和右下角的绝对像素,YOLO 要的是中心点加宽高再归一化。转换脚本本身不长,但类别映射表必须和data.yaml里的names顺序完全一致:

import os, xml.etree.ElementTree as ET # 类别顺序必须与 data.yaml 的 names 一致,否则标签全错 CLASSES = ["car", "truck", "bus"] CLS2ID = {c: i for i, c in enumerate(CLASSES)} def voc_to_yolo(xml_dir, out_dir, img_dir): os.makedirs(out_dir, exist_ok=True) for fn in os.listdir(xml_dir): if not fn.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, fn)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in CLS2ID: # 未定义类别直接跳过,避免静默错标 continue bb = obj.find("bndbox") x1 = float(bb.find("xmin").text) y1 = float(bb.find("ymin").text) x2 = float(bb.find("xmax").text) y2 = float(bb.find("ymax").text) # 裁剪到图像范围内,防止标注越界导致归一化出负数 x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(w, x2), min(h, y2) cx = (x1 + x2) / 2 / w cy = (y1 + y2) / 2 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{CLS2ID[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, fn.replace(".xml", ".txt")), "w") as f: f.write("\n".join(lines))

逻辑说明:先读size拿宽高,再逐 object 取框,裁剪到图像边界后做归一化。参数上,CLASSES的顺序就是最终类别 id,:.6f保留六位小数足够,YOLO 读取时不会因为精度丢框。跑完一定要抽查:随便挑一张图,用cx*w反算回像素,和原 xml 对比,误差应在 1 像素内。

3.2 COCO 与 YOLO 互转:json 结构里的三个坑

COCO 的 json 里bbox是[x, y, w, h]绝对像素,且category_id常常从 1 开始,而 YOLO 从 0 开始。转换时最容易翻车的就是这个偏移:

import json, os def coco_to_yolo(json_path, out_dir, img_dir): os.makedirs(out_dir, exist_ok=True) data = json.load(open(json_path)) # COCO 的 category_id 可能不连续,先建立 id -> 连续索引 的映射 cats = sorted(data["categories"], key=lambda x: x["id"]) cat_map = {c["id"]: i for i, c in enumerate(cats)} img_info = {im["id"]: im for im in data["images"]} per_img = {} for ann in data["annotations"]: if ann.get("iscrowd", 0): # crowd 区域不参与训练 continue im = img_info[ann["image_id"]] w, h = im["width"], im["height"] x, y, bw, bh = ann["bbox"] cx, cy = (x + bw / 2) / w, (y + bh / 2) / h per_img.setdefault(im["file_name"], []).append( f"{cat_map[ann['category_id']]} {cx:.6f} {cy:.6f} {bw/w:.6f} {bh/h:.6f}") for fname, lines in per_img.items(): stem = os.path.splitext(fname)[0] with open(os.path.join(out_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines))

逻辑说明:cat_map把原始 category_id 重映射成从 0 开始的连续索引,这一步不做,训练时类别会错位。iscrowd为 1 的标注要跳过,否则密集区域会被当成单个大框。参数上,bbox的宽高是绝对像素,除以图像宽高即得归一化值。转完用wc -l统计 txt 行数,和 json 里非 crowd 标注数对比,数量应一致。

3.3 转换后必查的四个边界

第一,图片宽高是否和标注一致,尤其是有 EXIF 旋转的 jpg,读出来的宽高可能和标注时相反。第二,空标签文件要不要保留,YOLO 训练时全背景图可以留,但要在data.yaml里确认不会被当成损坏样本。第三,类别 id 是否连续且从 0 开始,中间断号会让模型输出维度对不上。第四,坐标是否越界,归一化后出现负数或大于 1 的值,训练时会被裁掉或报错。这四条我每次转完都跑一遍,比训到一半发现标签错要省事得多。

4. YOLO11 一键训练脚本:三平台环境、参数与显存控制

4.1 GPU、CPU、Mac 三平台的环境差异

YOLO11 基于 Ultralytics 框架,三平台的核心差异在推理后端。GPU 走 CUDA,需要装对应版本的 PyTorch;CPU 走默认后端,速度慢但兼容性最好;Mac 走 MPS,Apple Silicon 上能吃到统一内存的红利,但部分算子支持不全。我一般用 conda 建独立环境,避免和系统 Python 打架:

# GPU 机器(以 CUDA 12.1 为例,具体版本按显卡驱动选) conda create -n yolo11 python=3.10 -y conda activate yolo11 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics # Mac(Apple Silicon) conda create -n yolo11 python=3.10 -y conda activate yolo11 pip install torch torchvision # 默认带 MPS 支持 pip install ultralytics

参数说明:Python 选 3.10 是因为 3.12 上部分依赖轮子还不全;--index-url指定 CUDA 版本,装错会出现torch.cuda.is_available()返回 False。装完先验证:

import torch print(torch.__version__, torch.cuda.is_available()) # GPU 机器应为 True print(torch.backends.mps.is_available()) # Mac 应为 True

4.2 data.yaml 与一键训练脚本

data.yaml是训练入口,路径写错是最常见的失败原因。我习惯用绝对路径,避免工作目录变化导致找不到图:

path: /data/uav_vehicle_dataset train: splits/train.txt val: splits/val.txt test: splits/test.txt nc: 3 names: ["car", "truck", "bus"]

一键训练脚本把设备判断、参数和日志串起来,三平台共用一份:

from ultralytics import YOLO import torch def pick_device(): if torch.cuda.is_available(): return "0" # 单卡 GPU if torch.backends.mps.is_available(): return "mps" # Apple Silicon return "cpu" if __name__ == "__main__": model = YOLO("yolo11n.pt") # n 版最轻,适合 1000 张图起步 model.train( data="data.yaml", epochs=100, imgsz=640, # 无人机小目标可提到 960,但显存翻倍 batch=16, # CPU/Mac 建议降到 4 或 8 device=pick_device(), workers=4, project="runs/uav_vehicle", name="exp1", patience=20, # 20 轮无提升就早停 cache=True, # 1000 张图可全缓存进内存 )

逻辑说明:pick_device按可用性依次选 GPU、MPS、CPU,避免手动改代码。imgsz是输入分辨率,无人机小目标建议 960,但显存占用约为 640 的 2.25 倍,8G 显存卡上 batch 要相应降到 8。patience设 20 能省下大量无效训练时间。cache=True对 1000 张图很划算,首次读盘后后续 epoch 不再 IO 等待。

4.3 关键参数怎么调:从 batch 到小目标增强

batch和imgsz是一对联动参数,显存不够时优先降 batch,再考虑降 imgsz,因为分辨率直接决定小目标能否被检出。学习率默认 0.01,1000 张图这种小数据集建议降到 0.001 到 0.005,否则前期 loss 震荡明显。workers在 Windows 上设太高会卡死,4 到 8 比较稳。如果车辆普遍小于 32 像素,可以开 YOLO11 的小目标增强相关配置,或者把imgsz提到 1280 并配合 mosaic 增强,但要注意标注框在缩放后是否仍然准确。

训练启动后重点看三个信号:box_loss是否稳定下降、mAP50是否在 30 轮后开始爬升、验证集 loss 是否早于训练集反弹。如果 mAP 一直不动,先查标签而不是调模型,十有八九是类别映射或路径问题。

5. 避坑与排查:训练跑不通时先看这五条

5.1 现象:训练一开始就报找不到图片

原因:data.yaml里的path和train拼接后路径不对,或者 txt 里写的是相对路径但工作目录变了。解决:把train.txt里的路径改成绝对路径,或者统一用path加相对路径的写法,跑之前用head splits/train.txt确认路径真实存在。

5.2 现象:mAP 始终为 0 或极低

原因:类别 id 错位,或者 VOC 转 YOLO 时CLASSES顺序和names不一致。解决:抽一张图,把 txt 里的类别 id 反查names,看是否和图上目标对得上;再检查nc是否等于len(names)。这个坑我踩过不止一次,标签对不上时模型学到的全是噪声。

5.3 现象:GPU 显存溢出(CUDA out of memory)

原因:imgsz或batch太大,或者cache=True把全部图片缓存进显存。解决:先把 batch 减半,再考虑降 imgsz;cache改成disk或 False。Mac 上如果 MPS 报算子不支持,退回 CPU 跑通流程再换设备。

5.4 现象:CPU 训练慢到无法接受

原因:CPU 上 640 分辨率、1000 张图、100 epoch 可能要跑十几个小时。解决:先用yolo11n加imgsz=416、epochs=30跑一个基线,确认流程通了再上 GPU 全量训。CPU 只适合验证脚本正确性,不适合出最终权重。

5.5 现象:验证集精度高但实测漏检严重

原因:切分时同一段视频的帧跨了训练和验证集,导致验证集泄漏。解决:按视频源或拍摄架次切分,而不是随机切帧。另外检查测试集是否包含不同光照和高度,1000 张图如果全是一个场景,泛化能力本身就有限,别指望模型能跨场景直接可用。

6. 把 1000 张图用出 10000 张的效果:增强、验证与迭代习惯

数据量固定时,提升空间主要在增强策略和验证方法上。我一般先跑一个不做额外增强的基线,记录 mAP50 和各类别 AP,再逐步加 mosaic、mixup、随机缩放。无人机视角下,随机缩放和随机裁剪对小目标帮助最大,但裁剪比例不要超过 0.3,否则车辆被裁碎反而伤召回。mosaic 能提升小目标,但训练后期建议关掉,让模型在真实分布上收敛。

验证时别只看 mAP,要分尺度看。把测试集按目标像素面积分成小(小于 32²)、中、大三档,分别统计召回率。无人机车辆检测的瓶颈几乎总在小目标档,如果小目标召回低于 0.4,优先提imgsz或加小目标增强,而不是换更大的模型。下面这张表是我常用的验证记录模板:

指标基线加 mosaic加缩放提 imgsz 到 960
mAP500.520.580.610.67
小目标召回0.310.380.420.55
推理耗时(GPU)8ms8ms8ms15ms

迭代习惯上,我坚持每次只改一个变量,并把runs/下的结果目录按实验名归档,避免「上次那个好结果找不到了」。1000 张图训到 mAP50 0.6 以上是合理预期,再往上要么补数据,要么做针对性增强。最后一句血泪经验:先把标签和切分做对,再谈模型和参数,顺序反了,调多久都是玄学。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询