
简介无人机目标检测数据集第三弹正式发布面向计算机视觉研究者、算法工程师及有目标检测与跟踪需求的开发者聚焦航拍场景下的小目标识别难题。数据集包含1万余张真实无人机飞行图片目标尺度小、背景复杂类别统一为drone适合用于小目标检测、跟踪与模型泛化能力验证。配套标注同时提供YOLO格式txt与VOC格式xml两种可直接用于YOLO系列、SSD、Faster R-CNN等主流框架训练免去格式转换的额外工作。压缩包约920MB内含10146组jpg、txt、xml三类文件共3万余个文件数据组织清晰、命名规范。系列前两批资源已获较多关注本批与前两批无重复数据可放心下载扩充训练集。目前已有2286人浏览学习适用于希望补充小目标无人机数据、开展算法对比实验或推进落地项目的进阶学习者。1. 无人机小目标检测数据集drone-dataset-3.zip 解决的是什么问题做目标检测的人应该都有同感公开数据集里大目标太多真正“目标小到只有几十个像素”的数据反而难找。drone-dataset-3.zip 就是冲着这个缺口来的——上万张无人机飞行图片类别只有 drone 一个目标在画面里往往占比极小刚好用来验证小目标检测和跟踪算法在低空场景下的真实表现。这份数据集同时提供了 YOLO 格式的 txt 标签和 VOC 格式的 xml 标签省掉了最常见的格式转换前置工作。适合三类人来用做小目标检测算法对比实验的研究者、做低空安防或无人机反制的工程人员、以及刚学 YOLO 想搞清楚标签格式怎么设计的入门者。下文从目录结构开始一步步把它接进训练链路。2. 双格式标签对应关系从 txt 到 xml 不靠猜2.1 文件名里藏着场景信息这份数据集的标签文件命名非常规律比如swarm_dji_phantom_3679.txt这种格式拆开看就三部分swarm表示集群飞行场景dji_phantom表示设备型号大疆精灵系列末尾的3679是该条样本的采集序号。后面还有3391、3255、3460等不同序号对应不同飞行时刻和不同拍摄角度。图片文件和标签文件是严格同名的只是后缀不同swarm_dji_phantom_3679.jpg对应swarm_dji_phantom_3679.txt和swarm_dji_phantom_3679.xml。这一点非常重要因为很多训练框架在读取数据时就是靠文件名前缀去匹配图片和标签的前缀对不上直接报“no labels found”。我把这份资源解压后第一件事就是做了一次文件名校验确认三个目录里同名文件的数量一致才敢往下走。2.2 txt 标签归一化坐标YOLO 的母语YOLO 系框架YOLOv5、YOLOv8、YOLOv11读取的 txt 标签是归一化后的坐标每一行对应一个目标框格式是class x_center y_center width height拿swarm_dji_phantom_3679.txt来说里面可能长这样0 0.482030 0.371122 0.052412 0.029631 0 0.561254 0.443877 0.048971 0.026512四个数值的含义要记牢x_center是目标框中心点的 x 坐标除以图片宽度后的比值y_center同理是中心点 y 除以图片高度width是框宽除以图宽height是框高除以图高。所有值都在 0 到 1 之间跟图片原始尺寸无关所以在不同分辨率下训练都不用改标签。这份数据集的类别只有drone一个类别序号固定是 0。在 YOLO 的类别配置文件里写names: [drone]就能对齐不需要额外做类别映射。2.3 xml 标签像素坐标VOC 的老规矩VOC 格式的 xml 标签是给人读的也是给 Faster R-CNN、SSD 这类检测框架用的。打开同名 xml 文件里面是标准的 Pascal VOC 结构annotation folderdrone-dataset-3/folder filenameswarm_dji_phantom_3679.jpg/filename size width1920/width height1080/height depth3/depth /size object namedrone/name bndbox xmin925/xmin ymin400/ymin xmax1025/xmax ymax432/ymax /bndbox /object /annotation这里的bndbox给的是像素坐标xmin、ymin是目标框左上角xmax、ymax是右下角。注意同一个 xml 文件里可能有多个object节点每个 node 对应一个目标框类别名必须严格写成drone。txt 和 xml 两种格式的坐标体系完全不一样一个是归一化相对坐标一个是绝对像素坐标。实际使用时如果你发现某个标签文件里目标坐标看起来“离谱”先确认是不是把两种格式搞混了——这是最容易被忽略的问题。字段txtYOLOxmlVOC坐标基准相对图片宽高的归一化值绝对像素坐标目标框定义中心点 宽高左上角 右下角类别表示数字序号0字符串名称drone数值范围0 ~ 10 ~ 图片宽/高3. 把数据集接进 YOLO 训练链路转换、可视化与校验3.1 先整理目录结构不管用 YOLOv5 还是 YOLOv8数据集的目录结构最好统一成下面的样子避免各框架在读取时产生歧义drone-dataset-3/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── annotations/ # 存放原始 xml备用txt 标签统一挪到labels/train和labels/val图片挪到images/train和images/valxml 单独放一个annotations目录留着以后要转回 VOC 格式时用。我一般会写个简单的 Python 脚本做这一步批量移动import os import shutil src_images 原始图片目录 src_txts 原始txt标签目录 src_xmls 原始xml标签目录 dst_images drone-dataset-3/images dst_txts drone-dataset-3/labels dst_xmls drone-dataset-3/annotations for name in os.listdir(src_txts): stem os.path.splitext(name)[0] # 同一个 stem 对应三份文件 shutil.move(os.path.join(src_images, stem .jpg), os.path.join(dst_images, train, stem .jpg)) shutil.move(os.path.join(src_txts, name), os.path.join(dst_txts, train, name)) shutil.move(os.path.join(src_xmls, stem .xml), os.path.join(dst_xmls, stem .xml))这里按stem不含后缀的文件名作为关联键同一时刻的三份文件一起移动防止图片和标签被拆散到不同目录。实际用时改成你自己的路径就行建议先打印几个文件名确认stem的提取结果正确再批量执行。3.2 从 VOC 转 YOLO一份自用的转换脚本虽然这份数据集两种格式都给全了但训练时如果你更信任 xml 的像素坐标或者以后要拿别的 VOC 数据集来扩充训练集转换脚本还是得常备一份。核心逻辑就是从 xml 里读bndbox再除以图片宽高做归一化import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name ! drone: continue # 只保留 drone 类别其他类别跳过 box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 边界裁剪防止越界 xmin max(0, xmin); ymin max(0, ymin) xmax min(img_w, xmax); ymax min(img_h, ymax) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines))转换逻辑里有两处容易写错一是坐标中心点计算(xmin xmax) / 2得到像素中心点再除以img_w才是归一化值顺序不能反二是w和h的长度计算用的是xmax - xmin不是xmax xmin。写完后拿一两张图手动核对一下数值再全部批量执行。3.3 可视化校验画框看真相标签转完或者整理完最直观的检查方式是画框可视化。用 OpenCV 把 txt 里的归一化坐标还原成像素框画在图上肉眼看框是否贴合目标。这一步能提前发现标签坐标错位、框大小异常、目标漏标等一堆问题import cv2 import os label_dir drone-dataset-3/labels/train image_dir drone-dataset-3/images/train save_dir drone-dataset-3/vis os.makedirs(save_dir, exist_okTrue) for txt_name in os.listdir(label_dir): stem os.path.splitext(txt_name)[0] img_path os.path.join(image_dir, stem .jpg) img cv2.imread(img_path) h, w img.shape[:2] with open(os.path.join(label_dir, txt_name)) as f: for line in f.readlines(): parts line.strip().split() if len(parts) 5: continue _, x_c, y_c, bw, bh map(float, parts) x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(os.path.join(save_dir, stem .jpg), img)这段脚本把归一化中心点坐标换算成左上角(x1, y1)和右下角(x2, y2)画框后另存到vis目录。跑完随机抽几十张图看看重点检查两类情况框是否完全包住无人机以及一个图片里是否有多个目标无人机集群场景里一张图四五架飞机很常见。如果发现大批图片框偏移问题基本出在坐标换算公式上。4. 训练配置与小目标参数从输入分辨率到模型选型4.1 数据划分先做再训别让验证集漏风目标检测的数据划分不能直接按文件名顺序切因为同一组无人机飞行图片可能在时间上连续、场景高度相似顺序切会导致训练集和验证集分布重叠度太高评估指标虚高。我一般按序号做随机划分保证 train/val 里都有各种场景的样本import os import random from shutil import copy2 label_dir drone-dataset-3/labels/train train_dir drone-dataset-3/images/train val_dir drone-dataset-3/images/val train_lbl drone-dataset-3/labels/train val_lbl drone-dataset-3/labels/val all_txts os.listdir(label_dir) random.seed(42) random.shuffle(all_txts) split_idx int(len(all_txts) * 0.8) train_txts all_txts[:split_idx] val_txts all_txts[split_idx:] for txt in val_txts: stem os.path.splitext(txt)[0] copy2(os.path.join(train_dir, stem .jpg), os.path.join(val_dir, stem .jpg)) copy2(os.path.join(label_dir, txt), os.path.join(val_lbl, txt))随机种子固定成 42保证每次划分结果一致。注意这里用了copy2而不是move保留原始文件万一划分比例不合适还能重新切。80/20 是常见比例也可以根据数据量调整如果总样本上万95/5 也够验证用。4.2 模型选型与输入分辨率小目标检测的核心矛盾是目标在原始图上占比太小下采样几次后特征图上的响应几乎消失。YOLO 系列默认输入分辨率一般是 640对这份数据集来说不够——我实际看样本时发现很多无人机在 1920×1080 原图里宽度只有 100 像素左右640 输入下缩到 30 像素检测难度陡增。建议优先尝试这几条路线提高输入分辨率训练时将imgsz设置到 1280让模型看到更多细节。代价是显存占用翻倍batch size 需要相应降低。选轻量级模型起步YOLOv8n 或 YOLOv11n 先跑通全流程再换 v8s/v8m 提精度。不要一上来就上最大的模型小目标数据集训练不稳定先确认流程没问题再堆算力。关闭或调整 mosaic 增强YOLOv8 默认开启 mosaic四张图拼一起会进一步缩小目标对这份数据集可能适得其反。可以设为 0.5 而不是 1.0保留一部分原始尺度样本。我实际跑的配置是 YOLOv8s 输入 1280batch 8单卡 24G 显存mosaic 0.5开启copy_paste增强。训练命令参考yolo detect train \ datadrone-dataset-3.yaml \ modelyolov8s.pt \ imgsz1280 \ batch8 \ epochs200 \ mosaic0.5 \ copy_paste0.3 \ patience30参数逐个说data指向数据集的 yaml 配置model用预训练权重做迁移学习比从零训练收敛快得多imgsz提到 1280mosaic从默认 1.0 降到 0.5避免小目标被拼图进一步缩小copy_paste是给小目标做复制粘贴增强在检测小目标的场景里效果比较明显patience是早停轮数连续 30 轮 mAP 不涨就自动停省显存也省时间。4.3 dataset.yaml 配置与常见参数在训练前需要准备好drone-dataset-3.yaml内容如下path: /data/drone-dataset-3 train: images/train val: images/val nc: 1 names: 0: dronepath是数据集根目录的绝对路径YOLO 会拿它去拼接train和val的相对路径。nc是类别数这份数据集只有 drone 一个类所以是 1。names的映射必须和 txt 标签里的类别序号对应——txt 里写 0 就对应这里的第 0 个名字写错类别名会导致训练时类别标签错乱而且很难排查。评估指标重点关注mAP50和mAP50-95。对小目标来说mAP50能到 0.7 以上就算不错mAP50-95因为 IoU 阈值更严格、小目标框稍微偏一点就判错掉到 0.3~0.4 是正常的不要一看数值低就怀疑模型跑挂了。5. 常见问题排查五个让人翻车的细节5.1 现象txt 和 xml 文件数量对不上解压后数了一下发现有的图片只有 txt 没有 xml或者反过来。原因是在采集和标注流程里xml 和 txt 可能由不同工序生成后补标签时只更新了一种格式。解决方法是写个脚本以文件名为 key 做三向校验找出不一致的样本。我一般会强制把两种格式都以“能对得上”为标准对不上的样本直接从训练集中剔除不然训练时一个 batch 里混入缺标签的图片模型会学到错误信息。import os txt_dir labels xml_dir annotations img_dir images txt_stems {os.path.splitext(f)[0] for f in os.listdir(txt_dir)} xml_stems {os.path.splitext(f)[0] for f in os.listdir(xml_dir)} img_stems {os.path.splitext(f)[0] for f in os.listdir(img_dir)} only_txt txt_stems - xml_stems - img_stems only_xml xml_stems - txt_stems - img_stems print(只有txt:, len(only_txt), 只有xml:, len(only_xml))5.2 现象txt 坐标出现 1.0 越界框画到图像外面检查标签时发现某些 txt 行的width或x_center超过 1.0画框后框体超出图片边界。原因是对 xml 做转换时没有做边界裁剪或者原标注时 xmax/xmin 写错了。解决方法是统一在转换逻辑加裁剪像我上面给的voc_to_yolo脚本里那样把所有坐标钳制到合法范围。裁剪后还要检查有没有变成“零面积框”——也就是width或height小于等于 0这种框直接删掉该行。5.3 现象训练时提示 “no labels found” 或 mAP 一直为 0数据集里所有标签文件的类别序号和 yaml 里的 names 对不上比如 txt 里写的是1而 yaml 里只有0: droneYOLO 会把序号 1 当作未知类别直接跳过导致整个训练过程没有正样本。解决方法是打开任意一个 txt 文件确认类别序号再看 yaml 里nc和names是否一致。这份数据集类别就是 drone序号固定 0yaml 里nc: 1就够了。5.4 现象验证集 mAP 虚高训练集 mAP 反而低原因基本是数据划分时图片和标签没对齐同一张图片的副本同时出现在 train 和 val 里模型在训练期见过验证图片评估失去意义。更隐蔽的情况是同一架无人机的连续帧被划到两边场景高度相似也会有轻微的数据泄漏。解决方法是切分时用文件名做去重校验并且在脚本里固定随机种子。我还会额外检查一下 train 和 val 的文件名集合是否有交集一行代码的事别省。train_names set(os.listdir(images/train)) val_names set(os.listdir(images/val)) overlap train_names val_names print(交集数量:, len(overlap))5.5 现象训练 loss 正常但检测框全集中在图像中心这种情况在新手场景里很容易遇到其实不是数据集的问题而是忘了关闭默认的 mosaic 增强导致目标被拼接图缩小到几乎不可见模型学不到有效特征。解决方法是把mosaic降到 0 到 0.5 之间同时打开copy_paste看看效果对比。6. 小目标检测的进阶技巧切片推理与跟踪联动小目标检测在验证阶段还有个很有效的 trick切片推理。思路很简单——把大图切成若干小块比如 640×640每块单独送进模型检测再把所有块的结果合并回原图坐标最后做一次全局 NMS。无人机这类目标在原图里占比小直接推理容易漏检切片后目标在块里的相对尺寸变大检测器更容易响应。def slice_detect(model, img, slice_size640, overlap0.2): h, w img.shape[:2] stride int(slice_size * (1 - overlap)) boxes [] for y in range(0, h, stride): for x in range(0, w, stride): x2 min(x slice_size, w) y2 min(y slice_size, h) patch img[y:y2, x:x2] results model(patch) for box in results[0].boxes.data: # 还原到原图坐标 box[0] x box[1] y box[2] x box[3] y boxes.append(box) return nms(boxes)代码里overlap0.2表示相邻切片保留 20% 重叠区域防止目标恰好在切片边界被截断还原坐标时要把切片的左上角偏移量加回去。切片尺寸根据目标大小调整——目标越小切片可以越小但太小会让推理次数膨胀一般 640 到 1024 之间折中。这个数据集还有一个适合做的方向是目标跟踪。因为目标小、场景是无人机集群飞行单帧检测结果容易闪烁接一个 ByteTrack 之类的跟踪器用帧间轨迹做平滑能明显提升视频序列上的稳定性。检测 跟踪两段串联一份数据两种用法比单纯跑 mAP 更能说明算法的工程价值。从那以后我每次拿到新的小目标数据集都会强制走一遍“可视化校验 → 切片推理 → 跟踪验证”的流程这三个环节能暴露的问题比训练 loss 曲线诚实得多。希望帮到你。本文还有配套的精品资源点击获取