简介这份无人机视角多类别目标检测数据集面向从事航拍视觉算法研发的工程师、高校研究者及智慧城市与生态监测方向的开发者用于训练和验证YOLO系列目标检测模型。数据集共包含19个类别覆盖桥梁、飞机、自行车、船只、农作物、建筑、公交车、汽车、十字路口、直升机停机坪、灯光设施、人员、河流、道路、太阳能农场、体育场、储罐设施、卡车与火山横跨基础设施、交通工具与自然地貌三大维度并针对低空拍摄特有的尺度变化与遮挡场景做了标注优化。资源包共2000个文件以983张jpg航拍图像与1015个txt标注文件为主另含1个yaml数据配置和1个docx说明文档压缩包约57.08MB训练集720张、验证集192张、测试集103张标注采用YOLO格式并经过几何验证支持即插即用。目前已有124人学习下载适合快速搭建无人机巡检、城市三维建模、河流与火山监测及应急调度等场景的检测基线。1. 无人机视角多类别目标检测数据集为什么它比模型结构更值得先啃拿到「无人机视角多类别目标检测数据集.zip」这个标题很多人第一反应是去找一个更强的检测网络但真正卡住项目进度的往往不是模型而是数据本身。无人机视角有几个绕不开的特点目标尺度跨度极大、俯视角度导致外观畸变、小目标密集且遮挡严重、背景随高度和光照剧烈变化。同一张图里可能同时出现行人和车辆而车辆只有几十个像素。这类数据集要解决的核心问题是让检测器在「高空俯拍 多类别 小目标」这个组合条件下还能稳定召回。它适合三类人做航拍巡检、做交通流量统计、以及想把通用检测模型迁移到垂直场景的工程师。下面按「先看懂数据、再跑通基线、再调参、最后避坑」的顺序拆开讲。2. 先搞懂无人机视角数据集的类别体系与标注格式2.1 多类别标注为什么比单类别更容易翻车通用数据集里类别是均衡设计的但无人机视角数据集不是。以常见的航拍多类别场景为例类别通常覆盖人、轿车、卡车、公交车、自行车、三轮车等分布极度不均轿车可能占标注框的六成以上而三轮车、特殊车辆可能只有几百个实例。这种长尾分布直接导致两个后果一是训练时损失被头部类别主导尾部类别召回率上不去二是评估时 mAP 看起来还行但一拆到单类别就露馅。我一般会先做一次类别统计而不是急着训练。统计的目的不是看总数而是看每个类别的实例数、平均框面积、宽高比分布。平均框面积能告诉你小目标占比宽高比能告诉你标注是否规范。如果某个类别的平均框面积小于 32×32 像素那它基本属于小目标范畴后续 anchor 设计和输入分辨率都要围绕它调。import os import xml.etree.ElementTree as ET from collections import defaultdict # 统计各类别实例数、平均框面积、宽高比 stats defaultdict(lambda: {count: 0, area: 0.0, ratio: 0.0}) anno_dir annotations # 标注目录按实际路径替换 for fname in os.listdir(anno_dir): if not fname.endswith(.xml): continue tree ET.parse(os.path.join(anno_dir, fname)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text 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) w, h xmax - xmin, ymax - ymin stats[name][count] 1 stats[name][area] w * h stats[name][ratio] w / max(h, 1e-6) for name, s in sorted(stats.items(), keylambda x: -x[1][count]): c s[count] print(f{name}: 实例数{c}, 平均面积{s[area]/c:.1f}, 平均宽高比{s[ratio]/c:.2f})这段脚本遍历 VOC 风格的 XML 标注输出每个类别的实例数、平均面积和平均宽高比。参数上anno_dir指向标注文件夹如果数据集是 COCO 的 JSON 格式把解析部分换成pycocotools读取即可。逻辑说明先聚合再排序让你一眼看出哪些类别是长尾。平均面积低于 1024 像素的类别后面要重点照顾。2.2 VOC、COCO、YOLO 三种格式的取舍无人机数据集常见的标注格式有三种VOC XML、COCO JSON、YOLO txt。选哪个不是喜好问题而是看你下游用什么框架。用 MMDetection 或 Detectron2COCO JSON 最省事用 Ultralytics 系列YOLO txt 最直接如果数据集原始就是 XML转成另外两种都不难但要注意坐标归一化和类别索引映射这两个坑。格式坐标表示类别存储适合框架主要坑VOC XML绝对像素 xmin/ymin/xmax/ymax每文件内嵌 name传统流程、转换中转类别名大小写不一致COCO JSON绝对像素 x/y/w/h全局 categories 列表MMDetection、Detectron2id 必须从 1 开始且连续YOLO txt归一化 cx/cy/w/h全局 classes.txt 索引Ultralytics 系列索引从 0 开始易错位我的习惯是原始标注先原样保留一份所有转换脚本输出到新目录绝不覆盖原始文件。转换后必须做一次可视化抽检随机抽 20 张把框画出来看这一步能拦掉八成的坐标错位问题。3. 用 YOLO 格式在本地跑通第一个基线3.1 从原始标注转成 YOLO 格式的完整脚本假设原始是 VOC XML要转成 YOLO txt。核心是三件事建立类别名到索引的映射、把绝对坐标转成归一化中心点坐标、把每张图的标注写到同名 txt。下面脚本一次做完并顺带生成classes.txt和data.yaml。import os import xml.etree.ElementTree as ET # 类别顺序一旦确定就不要改否则和历史权重对不上 CLASSES [person, car, truck, bus, bicycle, tricycle] cls2id {c: i for i, c in enumerate(CLASSES)} anno_dir annotations img_dir images out_label_dir labels os.makedirs(out_label_dir, exist_okTrue) def convert_one(xml_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in cls2id: 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, xmax max(0, xmin), min(img_w, xmax) ymin, ymax max(0, ymin), min(img_h, ymax) cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h if w 0 or h 0: continue lines.append(f{cls2id[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return lines # 图像尺寸需从图片读取这里用 PIL 示例 from PIL import Image for fname in os.listdir(anno_dir): if not fname.endswith(.xml): continue stem os.path.splitext(fname)[0] img_path os.path.join(img_dir, stem .jpg) if not os.path.exists(img_path): continue with Image.open(img_path) as im: img_w, img_h im.size lines convert_one(os.path.join(anno_dir, fname), img_w, img_h) with open(os.path.join(out_label_dir, stem .txt), w) as f: f.write(\n.join(lines)) with open(classes.txt, w) as f: f.write(\n.join(CLASSES))逻辑说明CLASSES的顺序就是最终类别索引必须和训练配置一致。坐标转换前先裁剪到图像边界是因为航拍标注里偶尔会出现超出画面的框不裁剪会导致归一化后出现负值或大于 1 的值训练时直接报错。w 0 or h 0的过滤是兜底防止退化框。参数上img_w、img_h必须从真实图片读取不能硬编码因为无人机数据集常混有不同分辨率。3.2 data.yaml 与训练命令的最小配置YOLO 格式准备好后写一个data.yaml指向训练集、验证集和类别名。路径用绝对路径最稳相对路径在不同工作目录下容易找不到。# data.yaml path: /data/uav_dataset # 数据集根目录 train: images/train # 相对 path 的训练图目录 val: images/val nc: 6 # 类别数必须和 classes.txt 行数一致 names: [person, car, truck, bus, bicycle, tricycle]训练命令以 Ultralytics 风格为例输入分辨率直接决定小目标能不能被看到yolo detect train \ datadata.yaml \ modelyolov8s.pt \ imgsz1280 \ epochs100 \ batch8 \ device0 \ cacheTrue参数说明imgsz1280是关键无人机小目标在 640 下经常只剩几个像素1280 能显著提升召回代价是显存和训练时间翻倍batch8是 1280 分辨率下的保守值显存够可以往上加cacheTrue把图片缓存到内存加快 epoch 速度但数据集大时注意内存占用。跑完第一轮不要急着看 mAP先看验证集的可视化结果确认框的位置和类别都对。4. 小目标与长尾类别参数怎么调才有效4.1 输入分辨率、anchor 与特征层的联动无人机视角的小目标问题本质是「目标在特征图上的有效响应太小」。检测网络经过多次下采样后小目标在深层特征图上可能只剩不到一个像素。解决思路有三条提高输入分辨率、增加高分辨率特征层、调整 anchor 尺度。这三者要联动单独调一个往往没效果。常见做法是输入分辨率提到 1280 或 1536在 PAN/FPN 结构里保留 P2 层stride4用于小目标anchor 的最小尺度下调到 8 或 16 像素。如果用的是 anchor-free 结构则重点放在提高分辨率和增加高分辨率特征层上。我一般会先固定模型只改imgsz做一组对比确认分辨率带来的增益再决定要不要动结构。# 用 PyTorch 快速检查不同分辨率下小目标在特征图上的尺寸 import torch import torch.nn as nn # 模拟一次下采样stride4 的 P2 层 downsample nn.MaxPool2d(kernel_size4, stride4) # 假设原图 1280目标框 24 像素 for img_size, obj_size in [(640, 24), (1280, 24), (1536, 24)]: feat torch.zeros(1, 1, img_size, img_size) out downsample(feat) obj_on_feat obj_size / 4 # P2 层 stride4 print(f输入{img_size}: 目标在P2特征图上约 {obj_on_feat:.1f} 像素, 特征图尺寸 {out.shape[-1]})逻辑说明这段代码不是训练而是帮你建立直觉——目标在特征图上的像素数只和 stride 有关和输入分辨率无关但输入分辨率越高同样的目标在原图里占的像素越多经过下采样后剩下的有效响应也越多。参数上obj_size换成你数据集里小目标的实际像素尺寸就能估算它在各特征层上的大小。低于 2 像素的基本要靠 P2 层救。4.2 长尾类别的重采样与损失加权长尾类别光靠调结构救不回来得从数据采样和损失函数两头下手。重采样有两种过采样尾部类别、欠采样头部类别。航拍数据集里头部类别动辄几万实例欠采样会浪费数据所以我更倾向过采样尾部配合数据增强。损失加权则是在分类损失上给尾部类别更高权重但权重不能拍脑袋设一般按类别频率的倒数开方来设避免尾部权重过大导致训练不稳定。import numpy as np # 假设统计得到的各类别实例数 counts np.array([50000, 30000, 8000, 5000, 1200, 800]) # 按频率倒数开方计算权重再归一化到均值为1 weights 1.0 / np.sqrt(counts) weights weights / weights.mean() print(类别权重:, np.round(weights, 3))逻辑说明开方是为了缓和极端不平衡直接用倒数会让尾部类别权重高到几十倍训练容易震荡。归一化到均值 1 是为了不改变整体损失量级。参数上counts换成你统计出的真实实例数。这个权重可以直接传给分类损失的weight参数。配合过采样时注意过采样后的 epoch 步数要相应调整否则一个 epoch 里尾部类别被重复看太多次容易过拟合。5. 避坑与排查无人机数据集最容易踩的五个坑5.1 坑一类别索引错位导致全盘皆输现象训练 loss 正常下降但验证时所有框的类别都是乱的mAP 极低。原因classes.txt的顺序和data.yaml里names的顺序不一致或者转换脚本里类别映射和训练配置对不上。解决把类别顺序写死在一个地方转换脚本和训练配置都从同一个文件读取训练前打印一次names和nc核对。5.2 坑二图像与标注文件名不匹配现象训练时报找不到标签文件或者大量图片被跳过。原因图片是.jpg标注是.png.txt之类的前后缀不一致或者大小写不一致。解决转换后做一次文件名比对列出图片有而标注没有、标注有而图片没有的清单人工确认后再训练。# 比对图片和标签文件名找出不匹配项 comm -3 (ls images/train | sed s/\.[^.]*$// | sort) \ (ls labels/train | sed s/\.txt$// | sort)5.3 坑三小目标被数据增强抹掉现象训练 mAP 尚可但推理时小目标漏检严重。原因默认的 mosaic、随机缩放、随机裁剪对小目标不友好缩放后小目标可能只剩一两个像素。解决针对小目标场景降低随机缩放幅度或者关闭随机裁剪mosaic 概率适当调低。我一般会把缩放范围收窄到 0.8 到 1.2 之间。5.4 坑四验证集和训练集来自同一段视频现象验证指标虚高换一批数据就崩。原因无人机数据常按视频抽帧相邻帧高度相似随机划分会让训练集和验证集出现近乎重复的样本。解决按视频或按航次划分而不是按帧随机划分。这一步不做后面所有调参都是在拟合验证集。5.5 坑五忽略标注质量直接开训现象模型学出一些莫名其妙的框或者某些类别始终学不好。原因原始标注里有漏标、错标、框不贴边。解决训练前抽检一批标注重点看小目标和密集区域。漏标严重的数据集要么补标要么在训练时用较低的置信度阈值配合后处理。6. 进阶用切片推理把高空小目标召回再拉一截当输入分辨率已经拉到显存上限小目标还是漏可以考虑切片推理tiling inference。思路是把大图切成有重叠的小块分别推理后再把结果映射回原图做 NMS 合并。这对无人机高空图特别有效因为小目标在整图里太小但在切片里占比就正常了。代价是推理时间随切片数线性增长需要权衡。def slice_inference(model, image, slice_size640, overlap128, conf0.25): 把大图切成重叠小块推理再合并回原图坐标 h, w image.shape[:2] step slice_size - overlap all_boxes [] for y in range(0, h, step): for x in range(0, w, step): patch image[y:yslice_size, x:xslice_size] if patch.shape[0] slice_size or patch.shape[1] slice_size: continue # 边缘不足一块的跳过或做 padding results model(patch, confconf) for box in results[0].boxes: # 把切片坐标加回原图偏移 xyxy box.xyxy[0].cpu().numpy() xyxy[[0, 2]] x xyxy[[1, 3]] y all_boxes.append((xyxy, float(box.conf[0]), int(box.cls[0]))) # 合并后需做一次全局 NMS去掉重叠切片的重复框 return all_boxes逻辑说明slice_size是切片边长overlap是相邻切片的重叠像素重叠是为了避免目标正好被切在边界上。参数上overlap一般取slice_size的 20% 到 25%conf可以比整图推理时略低因为切片里目标更清晰但太低会引入大量误检合并 NMS 时反而麻烦。合并阶段必须做全局 NMS否则同一个目标会在多个切片里被重复检出。切片推理的收益在小目标密集场景非常明显但推理耗时可能翻几倍线上部署要提前压测。我自己的习惯是任何无人机数据集到手先花半天做类别统计和标注抽检再花半天跑通一个 1280 分辨率的基线最后才动结构和损失。跳过前两步直接调模型十有八九是在给数据问题背锅。这套流程不新鲜但每次都能帮我省下大量返工时间。希望帮到你。本文还有配套的精品资源点击获取