简介面向无人机视觉应用与目标检测研究这套航拍多目标检测数据集提供525张训练图、148张验证图、79张测试图覆盖天线、建筑、电线杆、线路、树木、人员、汽车、无人机等8类目标。所有图片均按YOLO格式完成边界框标注标注信息包含位置与类别可直接接入YOLOv5/v8等主流检测框架进行训练与验证。压缩包共1506个文件主体为752张jpg图像与对应的752个txt标签文件并附带yaml配置和docx说明文档整体大小约35.98MB。数据源自真实航拍环境包含复杂背景干扰与目标遮挡案例适用于避障算法训练、电力线路巡检、城市交通监控、绿化覆盖分析等场景。目前已有337人浏览学习适合从事目标检测、无人机自主导航及工业巡检的开发者作为基准数据集使用。1. 无人机航拍多目标检测数据集.zip你拿到的不是一个压缩包是一套防线无人机拍出来的画面和常规行车记录仪完全不同目标小、姿态俯视、背景纹理复杂一个单独把无人机航拍多目标检测数据集.zip 解压丢进训练的同学很快会发现模型在下载包里的验证图刷得不错换到一段新航线上立刻掉点。这套 zip 通常装的是不同高度、不同时段拍摄的场景图配合 VOC 或 COCO 风格的标注文件目的是让模型学会在几百像素的车辆、行人和设备上把检测框画准。它能补上的正是纯地面视角训练集带不来的小目标与密集目标泛化能力适合在做巡检、车流统计、应急搜救以及准备用 YOLO 系列训练自己的数据集的人和团队。打开压缩包之前先弄清它内部的标注坐标系不然很容易白折腾一场。2. 解压前先验货看清目录结构、标注格式和类别分布2.1 用 unzip -t 和 unzip -l 做两分钟完整性检查下载这种东西最容易翻车的不是模型而是解压。网上分发的数据集一般经过多次拷贝zip 里不可避免混入损坏条目。我不建议用 Windows 资源管理器双击解压尤其在文件比较大的时候拖拽解压失败后你很难定位是哪个文件坏了。常见做法是在命令行先做一次完整的测试再决定要不要解压。# 在 Linux 或 WSL 里先测试压缩包完整性 unzip -t drone_aerial_multi_object_dataset.zip # 只看压缩包内部清单不解压 unzip -l drone_aerial_multi_object_dataset.zip逻辑说明第一行的-t参数不是把内容解出来而是逐条读取 zip 的条目并按 CRC 校验数据完整性输出 OK 就表示对应文件能正常还原第二行的-l是 list会列出版本、属性、压缩前后大小和日期用它能快速确认包内有没有images、labels之类的目录避免解压后才发现拿到的只是一个空壳。全部校验通过之后再执行unzip -q 数据集.zip -d ./drone_data解压到独立目录千万不要把图片直接撒进项目根目录后面生成 train.txt 时这些路径会非常难看。参数说明-t逐项读 zip 中央目录与每个条目的 CRC 校验值出现bad CRC或invalid zip archive时不要继续整包重新下载比修复工具靠谱得多。现在不少平台也提供同样的 zip 分发比如竞赛站点的数据页和 Hugging Face 的 datasets 目录下载后先用平台给的哈希值核对一遍能对得上再解压这套动作花不了两分钟但能帮你节省后面几天排错时间。提示解压前先建好目录。命令里统一用-d指定目标省得个别 zip 里的绝对路径把你的目录结构冲乱。2.2 认目录图片和标注怎么配对解压完成后先看目录不要急着写训练配置。一个典型的航拍多目标检测数据集目录长这样drone_data/ images/ # 原始航拍图常见 jpg / jpeg / png annotations/ # 标注文件常见 xml / json / txt classes.txt # 类别清单 README.txt # 数据说明务必先读图片和标注的对应关系通常是同名不同后缀flight01_0032.jpg对应flight01_0032.xml。这里最容易出问题的不是文件名不同而是大小写不一致、多批数据命名风格混用。常见做法是先跑一段配对校验把「有图无标注」和「有标注无图」两条都查出来#!/usr/bin/env python3 # check_pairing.py from pathlib import Path img_dir Path(drone_data/images) ann_dir Path(drone_data/annotations) imgs {} for p in img_dir.iterdir(): if p.suffix.lower() in (.jpg, .jpeg, .png): imgs.setdefault(p.stem.lower(), p) anns {} for p in ann_dir.iterdir(): if p.suffix.lower() in (.xml, .txt): anns.setdefault(p.stem.lower(), p) missing [k for k in imgs if k not in anns] extra [k for k in anns if k not in imgs] print(图片数:, len(imgs), 标注数:, len(anns)) print(有图无标注:, len(missing)) print(有标注无图:, len(extra)) if missing: print(缺失例子:, missing[:5])逻辑说明脚本把去扩展名后的文件名作为 key统一转成小写再比较避开DSC_0001.JPG与dsc_0001.xml这种大小写差异。setdefault处理的是同一张图存在.jpg和.jpeg两份的罕见情况此时保留第一个实例避免字典覆盖导致重复计数。有图无标注说明这批内容被漏标或负样本混入有标注无图往往是重复导出后面训练时会报找不到图的错误。这一步还要确认同一个包里是否同时存在 XML 和 TXT 两套标注。碰到这种情况时以 README 说明为准只选一套格式进入转换流程不要两个都读。我们后面要统一转成 YOLO 格式如果这里不先定清楚转换脚本会因为同时命中两个文件而写出重复内容。2.3 类别统计多目标「多」在哪几类多目标检测的核心是「多」。开训之前先统计每一类有多少标注框、小目标占比有多高这决定了你后续的参数设置。一段快速统计脚本# analyze_annotations.py import xml.etree.ElementTree as ET from collections import Counter from pathlib import Path ann_dir Path(drone_data/annotations) cls_counter Counter() n_boxes 0 n_small 0 for xml_file in ann_dir.glob(*.xml): tree ET.parse(xml_file) for obj in tree.iter(object): name obj.findtext(name, ).strip() cls_counter[name] 1 n_boxes 1 bnd obj.find(bndbox) if bnd is None: continue w float(bnd.findtext(width)) h float(bnd.findtext(height)) if w 32 and h 32: # 按 COCO 小目标约定的阈值 n_small 1 print(总标注框:, n_boxes) print(小目标比例: %.1f%% % (100.0 * n_small / max(n_boxes, 1))) print(类别 Top20:, cls_counter.most_common(20))逻辑说明XML 里的objectname与bndbox是 VOC 标注的标准结构很多航拍数据集直接沿用这套。strip()去掉类别名前后空格因为多批标注工具常会留下car这种带空格的类别不去掉它模型会把car和car当成两类。小目标阈值用的是 COCO 2017 数据集结构里的定义宽高都小于 32 像素算小目标。航拍数据里这个比例通常超过一半如果统计出来确实如此后面对模型的imgsz就要从 640 往上提否则特征图下采样几轮之后目标直接消失。这个脚本虽然只针对 XML也够用了。想快速瞟一眼类别词频可以直接跑一行命令查看但 grep 看不到框尺寸最终还是要靠脚本把数值拿到手。类别清单不合理的先返回去整理而不是硬训。多目标检测数据集的价值全在这些统计里。3. 按场景分组拆分train/val/test 分配与数据泄露排查3.1 为什么随机切图在航拍数据上会翻车航拍数据最怕的是相邻帧高度相似。无人机悬停或缓慢巡航时连续拍摄的几十张里地面目标几乎相同同一个对象可能会出现在好几张图里。如果按图片随机 split训练集里出现的车验证集里大概率也有同一条街的同几辆车验证 mAP 虚高。真正部署时换一条航线场景分布一变就立刻掉点。这不是模型的问题是数据泄露。所以拆分时首选按拍摄组、架次或场景分组而不是按图片 ID 随机打散。区分点在于常规自然图像数据集里同一物体出现在多张图里的概率很低随机切分基本安全航拍不同航线的连续帧之间重叠率很高。解决思路是让一个架次的图片整体只进一个 split验证集和测试集里的场景在训练阶段完全不可见。这样做确实会把训练集变小换来的是评估结果可信值得。3.2 按文件名前缀分组的脚本常见做法是假设文件名有意义比如flight01_0032.jpg里的flight01代表一个架次。写脚本时把前缀作为分组键组与组之间不打散# split_by_group.py from pathlib import Path import random random.seed(42) img_dir Path(drone_data/images) groups {} for p in img_dir.glob(*.jpg): prefix p.name.split(_)[0] groups.setdefault(prefix, []).append(p) items list(groups.items()) random.shuffle(items) n len(items) train_items items[: int(n * 0.7)] val_items items[int(n * 0.7) : int(n * 0.85)] test_items items[int(n * 0.85) :] for split_name, chunk in [(train, train_items), (val, val_items), (test, test_items)]: with open(f{split_name}.txt, w) as f: for group_name, paths in chunk: for p in paths: f.write(str(p.resolve()) \n)逻辑说明p.name.split(_)[0]把flight01_0032.jpg变成flight01一个架次的全部图片被放进同一个字典项切分时整组走不会被拆到两个 split 里。先random.shuffle(items)打乱的是组顺序不是图片顺序这一步保证不同架次的图片在三个集合里都有分布。边界情况如果文件名没有清晰前缀可以退而求其次按视频时长切段落每段若干秒作为一个组实在没有时间信息就只能按目录分或者人工看缩略图分批。几千张图手动分批也就一两个小时比模型在场景泄露的验证集上自嗨划算得多。随机种子固定为 42确保每个人复现时得到同样的划分这在后续对比实验时很重要。图片总量类别数建议划分说明小于 2000多于 10 类8:1:1验证集至少保证每类都有框2000 到 10000中等7:1.5:1.5默认方案兼顾训练量与评估可信度大于 10000单场景为主按架次预留 2 到 3 个架次测试集按架次隔离模拟新航线3.3 拆完还要验证分布类别占比要和总数一致按分组切完之后还不能直接开训。一个架次有可能整体缺某一类比如某条航线刚好没拍到船只结果训练集里有 2000 个船的框验证集里一个都没有模型在验证集上永远回避该类mAP 看起来还挺高。这是典型的类别分布偏移。写个小脚本检查# check_split_distribution.py from collections import Counter import xml.etree.ElementTree as ET from pathlib import Path for split_name in [train, val, test]: cls_counter Counter() with open(f{split_name}.txt) as f: image_paths [line.strip() for line in f if line.strip()] for img_path in image_paths: xml_path Path(img_path).with_suffix(.xml) if not xml_path.exists(): continue tree ET.parse(xml_path) for obj in tree.iter(object): name obj.findtext(name, ).strip() cls_counter[name] 1 print(split_name, dict(cls_counter.most_common(3)))逻辑说明脚本逐行读train.txt里的图片路径换算到同名 XML统计每个 split 的类别分布。对比三个集合的 Top3 和尾部类别如果 val 里完全没有 train 里的大量类别就要回到拆分组脚本把包含该类别的一个架次挪进 val 和 test。判断原则是三个集合的类别覆盖尽量一致头部类和尾部类都不能缺。把这一步放在训练之前后面能省掉很多因为评估指标失真导致的返工时间。4. 从 VOC/COCO 换到 YOLO 训练格式坐标归一化与两个必调参数4.1 三种标注格式的坐标系差异无论你打算用 YOLOv5、YOLOv8 还是更新版本训练自己的数据集第一步就是把标注格式对齐。这块最容易踩的坑不是文件后缀而是坐标系定义。格式存储形式坐标表示VOCXML每张一个文件xmin, ymin, xmax, ymax绝对像素COCOJSON整个集合一个文件x, y, width, height绝对像素、左上角基准YOLOTXT每张一个文件cx, cy, w, h中心点基准归一化到 0 到 1VOC 和 COCO 的坐标是绝对像素YOLO 是归一化的中心点宽高。转换的核心就是一次坐标映射。好多人写脚本只改格式不改坐标训练 loss 直接飞掉这是最常见的翻车原因。YOLO 内部把输入图缩放到网络输入尺寸绝对像素坐标一旦超过归一化范围损失函数里的宽高项就会给出离谱梯度。4.2 VOC 转 YOLO 的最小脚本这套逻辑我每次都用基本流程固定#!/usr/bin/env python3 # voc2yolo.py import xml.etree.ElementTree as ET from pathlib import Path # 类别清单顺序必须和训练配置里的 data.yaml 保持一致 CLASSES [car, person, truck, motorcycle, bicycle] def convert_xml(xml_file: Path, out_dir: Path) - None: tree ET.parse(xml_file) root tree.getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) lines [] for obj in root.iter(object): name obj.findtext(name, ).strip() if name not in CLASSES: print(跳过未映射类别:, name) continue cls_id CLASSES.index(name) bnd obj.find(bndbox) xmin float(bnd.findtext(xmin)) ymin float(bnd.findtext(ymin)) xmax float(bnd.findtext(xmax)) ymax float(bnd.findtext(ymax)) # 转换到中心点坐标并归一化 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h # 坐标越界时夹到 [0, 1] cx max(0.0, min(1.0, cx)) cy max(0.0, min(1.0, cy)) bw max(0.0, min(1.0, bw)) bh max(0.0, min(1.0, bh)) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: out_dir.mkdir(parentsTrue, exist_okTrue) out_file out_dir / (xml_file.stem .txt) out_file.write_text(\n.join(lines) \n) if __name__ __main__: for xml_file in Path(drone_data/annotations).glob(*.xml): convert_xml(xml_file, Path(drone_data/labels_yolo))逻辑说明CLASSES列表的顺序决定类别 ID这是两个必调参数里的第一个。数据集的classes.txt里是person, car, truck你写的是car, person, truck训练和推理时类别就对不上画出来的框标签全是错位的。转换用(xmin xmax) / 2算中心点而不是xmin width / 2两者数学上等价但 VOC 的xmax有时包含边界偏移直接做均值在极端情况下误差更小。输出保留六位小数YOLO 读的时候对这种精度已经足够。第二个必调参数是img_w和img_h的取法。一定要从 XML 的sizewidth节点读不能自己猜也不能统一当 640 算。航拍数据集里混着多种分辨率是常事用错了整张图的坐标全部偏移。转换完每个框做一次 clamp是因为部分标注工具的 bbox 会出界 1 到 2 个像素YOLO 训练遇到负坐标直接报错不如预先夹到 0 到 1 之间。4.3 COCO JSON 读取与旋转框数据的边界如果压缩包里给的是 COCO 风格 JSON逻辑类似但读取路径不同。COCO 的images里放id, width, height, file_nameannotations里放image_id, category_id, bbox而且bbox是[x, y, width, height]。转换时按category_id映射到类别 ID基本片段如下import json with open(annotations/instances.json) as f: coco json.load(f) img_meta {im[id]: im for im in coco[images]} for ann in coco[annotations]: meta img_meta[ann[image_id]] img_w, img_h meta[width], meta[height] bbox ann[bbox] # [x, y, width, height] class_id ann[category_id] cx (bbox[0] bbox[2] / 2) / img_w cy (bbox[1] bbox[3] / 2) / img_h w bbox[2] / img_w h bbox[3] / img_h逻辑说明COCO 的category_id不等于 YOLO 的类别 ID它只是 JSON 内部的索引需要自己维护一张category_id - 类别序号的映射表。写成上面的形式后把输出按 image_id 归组写到同名 TXT 即可。如果包里的坐标不是水平框而是旋转框情况就不一样。旋转框数据一般是为 mmrotate 训练 DOTA 类任务准备的DOTA 常用[x1, y1, x2, y2, x3, y3, x4, y4]或[cx, cy, w, h, angle]两种表示转换时要特别注意角度是弧度还是角度、相对于哪个轴这属于另一个话题。普通无人机航拍多目标检测数据集大多数是水平框不要默认所有航拍数据都要旋转检测头除非确认标注字段里有角度信息。转完后建议再跑一次 2.2 的配对脚本确认labels_yolo与 images 一一对应每个 TXT 至少有一行内容。这一步缺失的话训练时会以空数据集的形式报错干扰排错方向。5. 无人机航拍多目标检测数据集.zip 的 5 个避坑点现象、原因、解决5.1 解压报错bad CRC / 文件损坏现象解压到一半报bad CRC或者某张图片打开是花的、训练时读图报错。原因zip 在多次云端存储、网盘转存后个别条目数据受损大文件在传输过程中没有完整落盘。解决先在命令行做unzip -t全量校验单独重新下载损坏的条目不要整包重复下载。如果平台页面上给了哈希值下载完先核对对不上直接重下。这类问题在航拍数据集里出现频率不低因为原始文件体积大传输损耗概率高校验这一步省不掉。5.2 标注坐标越界xmax 比图宽还大现象转换后训练不久 loss 掉不下去日志里出现大量 NaN。原因标注软件手动拖框时轻微出界部分老版本 VOC 转换脚本把坐标算错xmax - xmin出现负数。解决转换脚本里加 clamp 和过滤。坐标小于 0 或大于图像宽高的丢弃框宽高小于 3 像素的丢弃因为下采样后这种小框基本是噪声。注意只丢弃异常的那一个目标框保留同一文件里其他正常标注不要整个文件删掉。5.3 类别名混乱Car 和 car 被当成两个类现象类别统计出来有 50 类实际只有 8 类两个相近标签长得几乎一样。原因数据来自两批标注一批用car另一批用Car或标注工具自动补了前后空格。解决读标注时统一strip()并转小写维护一张规范名映射表训练配置里只用映射后的名称。还要留意编码问题有些压缩包里的 README 是 GBK 编码解压后显示乱码类别名跟着乱在 Python 里统一按 UTF-8 读出来重新核对一遍。5.4 数据里混着大量无标注图片现象图片数 3 万张标注文件只有 1.2 万份但验证 mAP 虚高。原因数据集的负样本往往没有配套标注有些分发者特意混入无目标图片作为难例有些则是漏标。解决不要把无标注图直接丢进训练集。正确做法是把这类图单独放一个目录训练时不读评估时用来统计置信度误报。有人会给每张无目标图写一个0 0.5 0.5 0.01 0.01的空白框这种做法容易把背景学坏不建议用在正式训练里。5.5 类别极度不平衡头部类刷分、尾部类不学现象训练后期 mAP 从 0.6 涨到 0.8但尾部类别 AP 只有 0.1。原因航拍数据里车辆、行人占绝大多数船只、设备等样本数不足模型把公共特征全部用头部类学掉了。解决在训练配置里调整 per-class 损失权重或做在线采样保证每个 batch 至少出现一次尾部类。更稳的办法是给尾部类做离线增强翻转、旋转、亮度扰动而不是直接重复贴原图后者会让模型记住纹理而不是泛化特征。如果 README 里写了原始采集时段优先把夜景、雨雾条件下的稀缺样本补齐航拍模型对光照变化非常敏感。6. 训练完别急着信 mAP用置信度与可视化验证真实效果训练结束先别急着看指标用测试集跑出一批带框的可视化图重点观察置信度分布。航拍画面目标小误检比漏检更隐蔽。# inference_preview.py import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) img cv2.imread(test_flight_image.jpg) results model.predict(sourceimg, conf0.25, imgsz1280, saveTrue) print(results[0].boxes.conf)conf0.25是展示所有候选框数值高低并不能说明模型好坏。对航拍小目标先看 0.25 阈值下的误检再看 0.5 阈值下的漏检两个阈值下框都稳定才能再下结论。有条件的话把测试集的几十帧连续图片串成视频片段看框在时间轴上抖不抖静态单张看不出来的问题放到视频里会非常明显。还有一个值得做的验证把误检框裁剪出来单独放一个目录作为难例回到训练集里补一轮。航拍数据集里最稀缺的是「看起来像目标但不是目标」的负样本把这些错检框加入训练模型对虚假目标的抑制会好不少。我现在的习惯是所有的实验固定用同一份验证集不会随手换图这样不同轮次的指标才有可比性。mAP 只能说明整体情况不能告诉你模型在逆光、雾天漏了哪个行人和哪辆车。把这些细节都看过一遍确认没有低级错误再考虑要不要换更大的模型。希望帮到你。本文还有配套的精品资源点击获取