
简介面向航空视觉中的旋翼无人机检测任务这份数据集汇集了7000多张已标注的UAV图片类别统一为drone可同时用于目标检测、跟踪与识别等算法开发。资源中图片与标签一一对应标签同时提供txt和xml两种格式适配YOLO系列与Pascal VOC等常见训练框架大幅降低数据整理成本。整个压缩包共包含22675个文件其中jpg图像7558个、txt标签7559个、xml标签7558个包体约876.78MB结构规范便于直接划分训练集和验证集。对正在搭建无人机检测系统的研究者或开发者而言这类带完整标注的旋翼无人机数据集较为稀缺可直接用于模型微调或精度评估。目前已有1111人学习使用是验证算法性能和开展对比实验的实用基础数据。1. 空中无人机的检测数据集为什么差一个像素就决定漏检还是误报「空中无人机的检测数据集」这个标题覆盖两类经常被混为一谈的任务一是把无人机当作被检测目标反无人机、空域安防、机场净空管理二是用无人机当作成像平台去检测地面目标航拍车辆、行人、基础设施巡检。两条路线的数据组织方式、标注粒度和训练难点完全不同。我在实际项目里见过最大的误判是拿航拍车辆的数据集去训练反无人机模型结果模型对着飞鸟和杂物输出一串高置信度框。这个方向真正难的点在于目标尺度小、背景动态变化、光照跨度大以及公开数据集的来源和标注规范差异巨大。这篇文章会从数据选型、采集标注、格式转换到训练排错完整拆一遍。2. 公开数据集怎么选从 VisDrone 到 DOTA 的选型清单2.1 先分清两件事检测无人机还是用无人机做检测很多刚接触这个方向的人搜索时看到「无人机检测数据集」就直接下载等训练完才知道数据集里的目标是汽车和行人。这是两类任务数据组织形式差异非常大。第一类以无人机为检测目标。这类数据集的图像由地面相机、安防摄像头或另一架无人机拍摄目标大概率只有几十个像素甚至几个像素背景包含天空、云、楼宇、树木、电线杆。标注是「单类或少数类」的目标检测困难集中在远距离小目标和与鸟、风筝、塑料袋的形状混淆。Anti-UAV、SKYNET 这类数据集属于这个方向。第二类以无人机为成像平台。图像来自无人机机载相机常见视角是俯视或斜视检测目标是车辆、行人、船只、农作物病虫害等。VisDrone、UAVDT、DOTA 是这一类里最常被用到的。困难和第一类不一样目标虽然相对大一些但遮挡、截断、阴影和旋转角度问题突出而且画面里常常有几十上百个目标标注一致性很难保证。两者的预处理流程也不一样。第一类数据集视野中目标数量少但背景复杂度高负样本纯天空、纯地面的价值比正样本还大第二类数据集需要关注目标间相互遮挡和密集排列评估指标也更看重 AP50 在小目标子集上的表现。选型第一步不是比较哪个数据集精确而是判断你的部署场景属于哪一类。2.2 六个能直接落地的公开数据集与适用场景下面这张表只覆盖我实际用过或做过复现的数据集列出的是关键特征方便你做粗略筛选。数据集检测目标标注形式场景适合谁VisDrone行人、汽车、自行车、三轮车等约 10 类水平框无人机俯视、多城市、多高度无人机视角下的地面目标检测DOTA飞机、车辆、船只、球场等 15 类旋转框 水平框大型航拍影像需要旋转框识别的遥感检测UAVDT汽车、卡车、公交车水平框无人机俯视复杂交通场景无人机交通巡检Anti-UAV无人机单类水平框 时序轨迹地面/机载视角含昼夜反无人机安防DroneVehicle车辆可见光 红外成对水平框无人机红外与可见光同步视角多光谱融合检测HRSC2016船只旋转框海面遥感港口水域巡检、舰船识别VisDrone 是无人机视角里我做训练时第一个会推荐的数据集类别分布和真实航拍场景接近。DOTA 适合需要旋转框的项目配合 mmrotate 这类框架可以直接训练旋转检测头如果你最后要接的是一条自动化处理管线旋转框往往比水平框在拥挤场景里更有用。Anti-UAV 做反无人机方向的 baseline 比较合适但注意它在某些子集里目标较大直接用它训练出来的模型在小目标上的表现需要再验证。HRSC2016 类别少、目标清晰适合做迁移学习的起点先把训练管线跑通再换到复杂数据集。这几个数据集都不是同一时间、同一相机、同一标注规范产出的混用前必须做标签对齐。比如 DOTA 里的「飞机」和 VisDrone 里没有飞机类混合训练时类别序号重排是必然的后续转换脚本里要格外小心。2.3 选型不看名气看这三个参数高度、视角、目标尺度公开数据集的「名气」经常误导人。VisDrone 很好但它是无人机俯视地面如果你的场景是机场跑道旁的地面摄像头检测无人机用它做预训练效果会很差。我选数据集只看三个参数。第一个是飞行高度或拍摄高度。高度决定了目标在图像里的像素尺度。Anti-UAV 很多画面里无人机占几十到上百像素而真实的远程反无人机场景里目标可能只有 8 乘以 8 像素。高度不匹配时可以用图像缩放模拟但真实退化效果和大气扰动很难完全模拟。第二个是相机视角。俯视、平视、斜视三种视角下目标外观差别巨大尤其是无人机这种形状不规则的物体俯视看到的旋翼布局和平视看到的机身轮廓完全不是一回事。第三是目标尺度分布。拿到数据集第一件事就是统计 bbox 宽高分布如果宽度小于 32 像素的目标占比低于 10%你的模型在小目标上的表现基本不可信。我一般用下面这段代码快速检查一个数据集的标注分布不管什么格式先转成统一的 JSON 再统计。这个步骤能立刻暴露数据的真实可用性也是后续任何预处理的基础。# check_bbox_dist.py —— 统计数据集中边界框的尺寸分布 import json from pathlib import Path ann_path Path(annotations/instances_train.json) data json.loads(ann_path.read_text()) widths, heights [], [] for ann in data[annotations]: x, y, w, h ann[bbox] widths.append(w) heights.append(h) small sum(1 for w, h in zip(widths, heights) if w 32 and h 32) print(f总目标数: {len(widths)}) print(f小于32x32的目标占比: {small / len(widths):.2%}) print(f目标宽度中位数: {sorted(widths)[len(widths) // 2]:.1f})这段代码的关键是 bbox 的读取方式COCO 里bbox是[左上角x, 左上角y, 宽, 高]不要和 YOLO 的[中心x, 中心y, 宽, 高]混淆。输出结果如果「小于 32x32 占比」过低后面做小目标增强时就要加大权重如果中位数宽度超过 100 像素说明这个数据集更适合评估中大目标而不是小目标识别。这一步做的不是数据分析是在给自己设置训练预期。3. 自己采集并标注一套无人机检测数据两周流水线3.1 采集方案设计航线、高度、云台角度怎么组合才有泛化性公开数据集永远和你的场景有偏差到一定阶段就需要自己采集。采集方案直接决定数据质量上限比标注更影响最终效果因为标注只能还原拍摄到的信息不能补回拍丢的细节。我一般把采集分成三个维度来设计。第一个维度是高度分层按 50 米、100 米、150 米三档采集如果设备允许再加 30 米低空档。无人机视觉感知中高度直接影响目标像素尺寸只在一个高度采集会让模型变成高度敏感换个高度就失效。第二个维度是视角云台俯角从 0 度到 60 度每隔 15 度采一轮平视视角对应远距离探测大俯角对应近距离盘旋巡检。第三个维度是背景多样性城市楼顶、郊外农田、水面、树林边缘、机场跑道周围每类背景下至少保证 200 张有效帧。只在一个场地采集的数据模型会把场地特有的纹理当成目标特征。航线设计也有一个常见做法不要只飞规则直线要加入圆弧、8 字绕飞和悬停旋转。这样同一个目标在不同帧里有不同姿态角相当于免费的数据增强。悬停时目标几乎不动连续抽帧会得到大量高度相似画面后面清洗时要按相似度去重。这里必须提一句合规问题。采集前确认空域是否允许飞行、是否需要报备这个不是流程建议是前提条件。飞行平台的选型上我见过两种主流组合一种是成品飞控加开源无人机平台另一种是带 RTK 定位的工业级机型。做检测数据集不需要极端的定位精度但相机需要提前做畸变标定尤其是广角镜头边缘畸变会让小目标的边界框位置误差被放大。3.2 标注工具与标注规范小目标、遮挡、截断怎么标标注工具的选择上小型项目用 CVAT 或者 X-AnyLabeling 就够大型多人在线标注再用产线级的标注平台。CVAT 的好处是支持 AI 预标注先用一个初步模型跑一遍标注员只需要修正漏框和错框效率能提升 3 倍左右但预标注的质量必须抽检模型漏掉的目标标注员容易习惯性忽略。标注规范不能靠感觉要落到文档上。我实际用的规范包含下面几条。第一类别定义要有明确外延。比如「无人机」到底包不包括固定翼、包括不包括玩具级四旋翼都要写死。第二遮挡和截断的处理规则遮挡面积超过 30% 时照常标完整框因为 YOLO 这类水平框检测器学习的是目标整体位置截断目标只标可见部分会导致目标中心偏移。第三小目标的标注底线我一般规定长边不小于 8 像素的目标必须标注小于 8 像素的舍弃并记录为「未标注小目标」。这个底线来自经验——小于 8 像素的目标在训练中基本学不出稳定特征标了反而增加标注噪声这个尺度问题也让模型无法获得一致的梯度信号。第四密集场景下的重叠处理目标间重叠严重的两个都标不要只标前景遮挡者。标注质量检验方面我会将已标注数据的 10% 做二次复核重点看小目标和遮挡目标。判定的方法是逐类计算平均框尺寸如果某一类的平均框明显小于其他类检查是不是漏标了背景中的大目标或者误把边角料当成了目标。3.3 清洗与增强别把压缩噪声当成纹理学进去采集回来的原始素材不能直接进训练集。第一道清洗是剔模糊帧无人机振动、快速转向都会产生运动模糊这类画面里的目标边缘不清晰会让模型学到错误的轮廓特征。第二道清洗是去重复帧连续视频里相邻两帧的重叠度经常超过 80%如果全部保留数据集的有效多样性很低但训练时每个 epoch 都在反复看这些几乎相同的画面典型的表现就是 loss 降得快、验证集分数虚高。重复帧检测可以用感知哈希加余弦相似度来做也可以简单点每隔 5 帧抽 1 帧再人工抽看一遍。抽帧间隔至少要大于运动位移一整个目标宽度的距离才能真正产生新视角。数据增强要区分预训练阶段和微调阶段。预训练阶段可以用 Mosaic-4 拼接增强把四张图拼成一张变相增大 batch 中的场景多样性对密集小目标尤其有效。但拼图会切断目标上下文比如汽车周围的道路纹理变了如果只做预训练没问题微调阶段就要降低 Mosaic 概率。另一个建议是 HSV 扰动色调偏移设 0.01、饱和度 0.5、明度 0.4这个范围不会让颜色失真但能让模型对光照变化不敏感。这方面的配置经验最终都要通过验证曲线来确认不要照抄任何人的参数。4. 把标注转为 YOLO 格式转换脚本、数据划分与四个训练参数4.1 从 COCO 标注转到 YOLO转换脚本与参数说明公开数据集的格式五花八门YOLO 系模型要的是每张图对应一个同名 txt 文件每行是类别 cx cy w h坐标归一化到 0 到 1。准备一套健壮的转换脚本是整个流程里最值得花费时间的事情。# coco_to_yolo.py —— 把 COCO 格式标注转成 YOLO 训练格式 import json import os from pathlib import Path coco_path annotations/instances_train.json image_dir images/train out_labels labels/train # 类别映射表训练时 data.yaml 的 names 必须和这里顺序完全一致 category_map { 0: drone, 1: bird, 2: helicopter, } os.makedirs(out_labels, exist_okTrue) coco json.loads(Path(coco_path).read_text()) # 先建立 image_id 到文件名的映射 img_id_to_name {img[id]: img[file_name] for img in coco[images]} # 按 image_id 分组一个图像的 bbox 合在一起写一个 txt from collections import defaultdict anns_by_img defaultdict(list) for ann in coco[annotations]: anns_by_img[ann[image_id]].append(ann) for img_id, anns in anns_by_img.items(): img_name img_id_to_name[img_id] img_width next(img[width] for img in coco[images] if img[id] img_id) img_height next(img[height] for img in coco[images] if img[id] img_id) txt_path Path(out_labels) / (Path(img_name).stem .txt) lines [] for ann in anns: cat_id ann[category_id] if cat_id not in category_map: continue x, y, w, h ann[bbox] # COCO: 左上角 x, 左上角 y, 宽, 高 # 转成 YOLO 需要的中心点坐标和宽高再归一化 cx (x w / 2) / img_width cy (y h / 2) / img_height nw w / img_width nh h / img_height # clamp 到 [0, 1] 防止越界这是最容易忽略的边界问题 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) nw min(max(nw, 0.0), 1.0) nh min(max(nh, 0.0), 1.0) lines.append(f{category_map[cat_id]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) txt_path.write_text(\n.join(lines))这段脚本有几个地方容易翻车。category_map的键是 COCO 标注里的数字 ID值要写成字符串因为在 data.yaml 里类名就是字符串列表顺序错位会直接导致训练时类别语义对不上。坐标归一化前必须先拿到图像真实宽高有些数据集标注里图像的宽高字段是 EXIF 信息和实际解码出来的像素尺寸不一致这种数据会以很隐蔽的方式污染所有框的坐标。clamp 的意义在于处理那些标注时框画出图像边界的异常情况不 clamp 训练时有些框架会报错而且越界的框往往伴随极端 loss 波动。4.2 数据划分与目录结构按场景切分不是按文件随机切数据划分直接决定验证集的可信度。一个常见错误是按文件名随机打散后划分如果同一条飞行航线的连续帧被同时分进训练集和验证集验证分数会虚高因为模型已经见过几乎一样的背景。按场景划分的做法是把同一次飞行任务产生的所有帧归为一组整组划分。# split_by_scene.py —— 按场景分组划分数据 import random from pathlib import Path # 假设 images 目录下按场景存放子目录images/scene_01/*.jpg, images/scene_02/*.jpg train_ratio, val_ratio 0.7, 0.2 random.seed(42) scene_dirs [p for p in Path(images).iterdir() if p.is_dir()] random.shuffle(scene_dirs) n_train int(len(scene_dirs) * train_ratio) n_val int(len(scene_dirs) * val_ratio) train_scenes scene_dirs[:n_train] val_scenes scene_dirs[n_train:n_train n_val] test_scenes scene_dirs[n_train n_val:] def write_split(scenes, split_name): lines [] for scene in scenes: for img in scene.glob(*.jpg): lines.append(str(img.resolve())) Path(f{split_name}.txt).write_text(\n.join(lines)) write_split(train_scenes, train.txt) write_split(val_scenes, val.txt) write_split(test_scenes, test.txt)YOLO 训练时可以直接用这个三个 txt 文件作为训练集列表配合data.yaml指定。比例上我习惯 7:2:1如果采集场景只有 5 个验证集至少保留 1 个完整场景。测试集严格意义上应该永远不参与调参只用来最终评估。这里的关键参数是random.seed(42)固定种子保证每次划分结果一致否则模型调参过程中验证集成员一直在变你的对比实验根本不成立。4.3 训练配置里最容易改错的四个参数训练阶段我见过最多的反复试错都集中在四个参数上。第一个是imgsz。很多项目直接从 YOLOv8 默认的 640 开始但无人机检测里目标偏小640 分辨率下目标可能只有几个像素。常见做法是先用 640 跑通整个流程再尝试 1280 或 1536如果你的显卡显存有限用 1280 训练但推理时切片切块效果比直接上大树图更好。不要一开始就数值拉满你没法判断模型上限低是数据问题还是输入分辨率问题。第二个是batch。batch 大小受显存约束常见值是 8、16、32。batch 小于 16 时BatchNorm 的统计量不稳定典型现象是验证集上指标忽高忽低。用 T4 这类推理卡时训练侧通常不会用它来训练但推理侧批量大小和帧率估算可以参考单路 1080p 25 帧输入640 分辨率下用 TensorRT 优化后通常能跑 3 到 5 路具体取决于检测头和 NMS 的开销这个数值我一般现场压测完才敢承诺。第三个是epochs配合早停。不要拍脑袋定 300 epoch设一个patience20验证集 loss 20 轮不降就停。无人机数据的背景复杂度高训练标签又有噪声过拟合往往发生在最后几十个 epoch早停能省时间还能拿到更稳的权重。第四个是lr0。YOLO 系默认值通常是 0.01但小数据集和小 batch 下学习率要相应下调到 0.001 左右。判断方法是看前 10 个 epoch 的 box_loss如果快速降到 1.0 以下大概率过冲了如果几乎不动可能是学习率太低或标签有问题优先检查标签文件是否有空文件、类别序号越界。5. 排查与避坑小目标漏检、类间混淆与光照翻车5.1 误检一串鸟或把鸟看成无人机现象模型在黄昏和多云天空背景下连续把飞鸟、风筝、塑料袋识别成无人机一张图上出现五六个高置信度框。原因反无人机检测任务里目标和鸟在形状上高度相似都是细长、有翼展、深色轮廓。如果训练集中无人机只有几百个实例而负样本里包含大量含鸟或含运动物体的天空图模型的特征空间根本拉不开两个类。另一个原因是类别不均衡无人机类数量远小于背景类模型倾向于用高召回阈值来减小 loss结果就是把背景里一切动的东西都框出来。解决分两步走。第一步是数据层收集至少 1000 张纯天空、含鸟、含风筝、含电线杆的负样本图标注时单独建一个「bird」类而不是直接当背景。让检测器要么单独输出 bird要么从输出中过滤掉。第二步是推理层在模型输出后加一个目标尺寸过滤无人机的最小可探测尺寸设为 12 像素小于这个值的检测全部丢弃。这样做的代价是极小目标漏检但误报率能下降一个数量级对于安防场景误报比漏报更不可接受。5.2 远距离小目标漏检现象训练时 AP50 在 0.85 以上但部署后 100 米外的无人机基本检测不到输出的置信度全部低于 0.3。原因训练集中确实有小目标样本但占比过低。我在第 2.3 节里写的那个统计脚本在这里就派上用场了如果小于 32x32 的目标占比不足 20%训练时模型在损失函数里对小目标的梯度贡献被大目标淹没。解决三个手段叠加。数据层用复制粘贴增强把小目标从原图中裁剪下来随机粘贴到背景图上同时保留原始标注一张图可以多贴几次。训练层把imgsz从 640 提到 1280让目标在输入图中占更多像素。推理层用切片推理把大图切成 640 或 832 的块分别推理再合并结果NMS 阈值保持默认的 0.5。切片重叠率至少 20%否则目标跨切片边界时会被切开漏检会更严重。这个方法能立刻提升小目标召回但推理耗时增加需在帧率预算内权衡。5.3 白天好用、黄昏就翻车现象正午测试效果不错傍晚光线变暗后大量漏检早晚低角度阳光下的侧光场景更是全面失效。原因训练数据集的拍摄时间集中在白天光照分布单一。黄昏和低角度阳光会显著改变目标的对比度和色温尤其是无人机这类深色目标在逆光下和背景融为一体边缘特征几乎消失。解决采集阶段把一天分成清晨、正午、黄昏三个时段每个时段的背景各占三分之一。实在补拍不了时用图像增强模拟降低亮度、提高色温、加随机阴影遮罩。注意模拟的色温和真实黄昏的物理特征有差距只能作为补充手段。真正的解法是标注时对黄昏样本多标一些让模型学会在低对比度下感知边缘。部署时如果条件允许最好采用「可见光 红外」的双通道配置DroneVehicle 这类成对数据集就是为此设计的红外在黄昏场景的边缘保持远好于可见光。5.4 标注漂移与数据集文件损坏现象训练中期 loss 异常升高或者某个类别突然全部不识别。原因最常见的是标注文件损坏比如 txt 里出现了空行、负坐标、坐标数值大于 1。这类脏数据在训练时会产生极大 loss反向传播的梯度把前面训练好的特征都冲乱了。第二常见的原因是标注工具自动保存时产生的漂移一个人标完没保存另一个人覆盖了类别同名但框的位置全变。解决训练前做一次全量校验遍历所有标签文件检查行数、坐标范围、类别序号是否在合法区间内。下面的脚本可以在数据进入训练前直接给出异常文件列表。这个步骤我每次都会跑一遍虽然看起来浪费时间但它拦截过的问题比任何训练技巧都值钱。# validate_labels.py —— 检查 YOLO 标签文件是否干净 from pathlib import Path label_dir Path(labels/train) num_classes 3 # 和 data.yaml 的类别数一致 errors [] for txt in label_dir.glob(*.txt): for line_no, line in enumerate(txt.read_text().splitlines(), 1): parts line.strip().split() if len(parts) ! 5: errors.append(f{txt.name}:{line_no} 列数不对) continue cat, cx, cy, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if cat 0 or cat num_classes: errors.append(f{txt.name}:{line_no} 类别越界) if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): errors.append(f{txt.name}:{line_no} 坐标越界) print(f共检查 {len(list(label_dir.glob(*.txt)))} 个文件发现 {len(errors)} 个问题) for err in errors[:20]: print(err)这个脚本只解决数据合法性解决不了语义漂移。语义漂移要靠第 3.2 节提到的二次复核抽检来盯。抽检时重点看同一类目标的框是否覆盖同一部位比如「无人机」有的标了旋翼整体有的只标了机身这种不一致在训练时会让模型学出一个很钝的目标中心定位精度的上限被锁死。6. 落地前先做一次反向验证算的不是 mAP 而是安全裕度模型训练完成不等于可以部署。我需要用一个独立验证脚本模拟真实推理场景来测一遍重点关注召回率而不是精确率。因为反无人机场景漏检一架的代价远高于误报十次误报可以由后续的跟踪策略过滤掉漏检则直接击穿安全底线。# evaluate_recall.py —— 在固定置信度下统计召回率和误报率 from pathlib import Path import numpy as np # preds: 每行 [cx, cy, w, h, conf, cls] # gts: 每行 [cx, cy, w, h, cls] # 这里只做演示真实实现用 IoU 匹配并取最大值 def evaluate(preds, gts, conf_threshold0.25, iou_threshold0.5): preds [p for p in preds if p[4] conf_threshold and p[5] 0] # 只看无人机类 matched set() tp 0 for pred in preds: best_iou 0 best_gt -1 for gi, gt in enumerate(gts): if gi in matched: continue iou compute_iou(pred[:4], gt[:4]) # 需要单独实现 if iou best_iou: best_iou iou best_gt gi if best_iou iou_threshold: tp 1 matched.add(best_gt) fn len(gts) - len(matched) fp len(preds) - tp recall tp / (tp fn) if (tp fn) 0 else 0 precision tp / (tp fp) if (tp fp) 0 else 0 return recall, precision在部署前我会专门准备一组「极端情况」测试集大雾天、逆光、极小目标、目标贴地飞行。这组数据不参与训练也不参与调参只在最终版本上跑一次。如果极端集上的召回率低于 95%我不会把模型放上去而是回到数据或推理策略里加保险。这个习惯来自我上一次把置信度阈值调到 0.6结果测试集指标很好看现场却漏掉了几个低对比度的小目标从那以后我再也不只看总榜分数了。这个方向值得投入但你得先能诚实地回答一个问题你的模型在最差的光线、最小的目标下还敢不敢拍胸脯。希望帮到你。本文还有配套的精品资源点击获取