简介:面向无人机目标检测这一细分方向,这份数据集围绕四千余张业余无人机拍摄的实景照片构建,素材常见于大疆精灵等消费级无人机,并特意纳入非无人机及外观相似的干扰目标,适合训练能够区分真假无人机的检测模型。压缩包内共两千个文件,包含一千九百九十九个txt标注文件和一个Python处理脚本,其中txt文件以YOLO格式记录目标位置与类别,可直接配合YOLOv3、YOLOv2-tiny等Darknet系列模型使用;Python脚本用于辅助数据集划分与标签处理,整体资源包约一百五十七点四二兆字节,结构清晰,便于快速开展实验。目前已有八百零二人下载学习,适合刚接触无人机目标检测的研究人员、学生和算法工程师,为低空安防、电力巡检、智慧城市等场景下的模型调试提供现成标注数据,省去大量采集与标注成本,同时干扰样本的加入也增强了模型对真实复杂环境的适应能力。
1. 4000+无人机图像数据集到底够干什么:从视角差异看模型训练
一个做无人机视觉感知的工程师,第一次拿到4000+张航拍图,最常见的疑问是:这和用COCO训练出来的YOLO差在哪。差在视角、尺度和背景复杂度。俯视视角下目标大多只有几十像素,道路、屋顶、树冠混成一块,地面视角学到的特征先验大部分不适用。4000+张在通用检测里不算巨量,但对某个具体场景的无人机目标检测来说,刚好跨过从demo到可上线的线:类别覆盖够、场景变化够,也足够暴露标注不干净的问题。下面不碰飞控和编队,只围绕数据集本身,讲清楚格式怎么处理、训练参数怎么定、小目标怎么调、验证怎么看。
2. 无人机图像数据集的结构与标注格式:从VOC XML转换到YOLO格式
许多打包好的无人机图像数据集会同时给两套东西:原始航拍图和VOC格式的XML标注。原因很实际,LabelImg这类工具默认导出XML,XML可读性好,出错了容易人工检查。拿到手第一步不是直接喂给YOLO,而是先把目录理清楚、把标注格式统一掉。这一步做慢了后面改起来代价极高。
2.1 一个典型4000+无人机图像数据集的目录布局
uav4000/ ├── raw_images/ # 原始航拍图,1920×1080为主 │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... ├── annotations/ # VOC XML标注,与raw_images同名 │ ├── 000001.xml │ └── ... └── classes.txt # 类别清单,一行一个名称这个结构里,annotations/000001.xml和raw_images/000001.jpg一一对应。classes.txt的顺序是最关键的约束:它决定了之后YOLO格式里类别索引的数字,转换脚本会按这个文件生成映射。常见类别包括 pedestrian、bicycle、car、truck、bus、motorcycle,数量上行人往往占一半以上,这个不平衡会在后面的训练阶段直接影响mAP。如果数据包只给了XML而漏了 classes.txt,先用一条命令把类别名和出现次数统计出来:
grep -h "<name>" annotations/*.xml | sed 's/.*<name>\(.*\)<\/name>.*/\1/' | sort | uniq -c | sort -rn这条命令把每个XML里的<name>标签内容抽出来,去重并排序,输出结果就是类别清单和大致分布。先确认类别顺序再写 classes.txt,否则后面YOLO格式的索引全部错位。
2.2 VOC、YOLO、COCO三种标注格式怎么选
格式选型不需要纠结,直接按使用阶段决定。中转和人工检查用VOC XML,训练和部署用YOLO txt,评估和跨模型迁移时才考虑COCO JSON。
| 格式 | 记录方式 | 坐标含义 | 适合阶段 |
|---|---|---|---|
| VOC XML | xmin, ymin, xmax, ymax | 像素绝对坐标 | 标注导出、人工检查 |
| YOLO txt | class xc yc w h | 归一化中心点+宽高 | YOLO系列直接训练 |
| COCO JSON | [x, y, w, h] | 左上角+宽高 | mmdetection、评估脚本 |
VOC转YOLO最容易算错的点是中心点坐标。XML里是左上角和右下角,YOLO需要的是中心点相对整张图的归一化坐标,分母是图像宽高而不是目标宽高,两者混用会导致框整体漂移。转换脚本里把分母和坐标顺序写清楚,能省掉后面排查NaN loss的大量时间。
2.3 把VOC XML转换成YOLO txt的预处理脚本
import xml.etree.ElementTree as ET from pathlib import Path # 从classes.txt读取类别顺序,保证索引稳定 CLASSES = [c.strip() for c in open("classes.txt", encoding="utf-8").readlines() if c.strip()] def convert_xml_to_yolo(xml_path, out_txt): tree = ET.parse(xml_path) root = tree.getroot() w = float(root.find("size/width").text) h = float(root.find("size/height").text) lines = [] for obj in root.iter("object"): cls = obj.find("name").text if cls not in CLASSES: continue bbox = obj.find("bndbox") x1 = float(bbox.find("xmin").text) y1 = float(bbox.find("ymin").text) x2 = float(bbox.find("xmax").text) y2 = float(bbox.find("ymax").text) # 越界框裁剪到图像范围内,避免训练崩溃 x1 = max(0, min(x1, w)) x2 = max(0, min(x2, w)) y1 = max(0, min(y1, h)) y2 = max(0, min(y2, h)) # 左上右下转中心点+宽高,并除以图像宽高做归一化 xc = (x1 + x2) / 2 / w yc = (y1 + y2) / 2 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{CLASSES.index(cls)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") Path(out_txt).write_text("\n".join(lines), encoding="utf-8")这段脚本把XML里的像素坐标转成归一化坐标,顺序是class, xc, yc, bw, bh。max(0, min(x, w))这一行专门处理标注框越界:航拍数据里偶尔会出现目标在画面边缘、标注框超出图像范围的情况,不裁剪的话部分训练代码会直接报错或产生负数宽高。CLASSES.index(cls)根据 classes.txt 的顺序转成数字索引,这样只要 classes.txt 不变,索引就不会漂移。
2.4 数据划分与类别统计
标注转换完之后,按9:1划分训练集和验证集。航拍数据集中同一个架次的图像相关性很强,划分前要做随机打乱,否则前几个架次全在训练集、后几个架次全在验证集,验证结果会虚高。
import random from pathlib import Path root = Path("uav4000") xml_list = sorted(root.glob("annotations/*.xml")) random.seed(42) random.shuffle(xml_list) split = int(len(xml_list) * 0.9) for idx, xml_path in enumerate(xml_list): part = "train" if idx < split else "val" img_out = root / "images" / part lbl_out = root / "labels" / part img_out.mkdir(parents=True, exist_ok=True) lbl_out.mkdir(parents=True, exist_ok=True) convert_xml_to_yolo(xml_path, lbl_out / (xml_path.stem + ".txt")) src_img = root / "raw_images" / (xml_path.stem + ".jpg") if src_img.exists(): src_img.replace(img_out / src_img.name)划分完成后,用下面的命令检查每个类别的样本数量。grep -oP '^\d+'提取每行开头的类别索引,cut -d: -f2把grep输出的文件名去掉,剩下纯索引,再sort | uniq -c统计:
grep -oP '^\d+' labels/train/*.txt | cut -d: -f2 | sort | uniq -c | sort -rn如果某个类别的目标数量比其他类别少一个数量级,先别急着调损失函数权重,优先确认是不是标注漏标了。航拍数据集中小目标漏标极为常见,类别不平衡往往不是真实分布,是标注质量问题。
3. 用YOLOv8把4000+无人机图像数据集跑成训练工程
数据准备好之后,选择YOLOv8作为训练框架。原因只有一个:在中等数据规模下,它的默认参数已经足够稳定,CLI支持从训练到验证的完整流程,改配置不需要重写代码。4000+张的数据量不大不小,模型选yolov8s起步,先跑通baseline再考虑变大或变小。
3.1 环境准备与模型选型
pip install -U ultralytics nvidia-sminvidia-smi用来确认显存。训练之前先明确自己的显存上限,因为无人机图像往往需要较大的imgsz,显存很快就成为瓶颈。如果显存小于8GB,建议直接选yolov8n;16GB左右可以跑yolov8s加 imgsz 1280 的小batch;显存充足再考虑yolov8m或v8l。
3.2 写数据配置文件
在项目根目录建uav4000.yaml,路径用绝对路径,减少换机器后的踩坑:
path: /data/uav4000 train: images/train val: images/val nc: 6 names: 0: pedestrian 1: bicycle 2: car 3: truck 4: bus 5: motorcyclepath指向数据集根目录,train和val是相对根目录的图片文件夹路径。names的顺序必须与之前 classes.txt 完全一致,这是整个配置里最容易出错的地方。如果训练时发现某个类别的框错位,优先检查这里。
3.3 训练命令与关键参数表
cd /data/uav4000 yolo detect train data=uav4000.yaml model=yolov8s.pt \ epochs=200 imgsz=1280 batch=8 device=0imgsz=1280是航拍场景的关键调整。默认的640会把大量只有一二十像素的目标直接压没,1280在模型性能和检测效果之间更平衡。model=yolov8s.pt表示加载COCO预训练权重继续训练,而不是从随机初始化开始,收敛速度和最终精度都会更好。
| 参数 | 建议值 | 说明 |
|---|---|---|
| imgsz | 1280 | 小目标保留更多像素,通常比640明显提升 |
| batch | 8-16 | 受显存限制,但不宜过小,过小难收敛 |
| epochs | 150-300 | 航拍场景收敛慢,100轮后仍可能继续上涨 |
| optimizer | AdamW | 中等数据量下比SGD更稳 |
| close_mosaic | 10 | 最后10轮关闭mosaic,避免训练与验证分布不一致 |
batch的选择逻辑很简单:显存能撑住16就先用16,撑不住就降到8。epochs不要死守100,航拍场景中模型要学细粒度纹理,往往到150轮之后mAP还在缓慢上升。如果时间成本高,先跑100轮看曲线趋势,再决定是否继续。
3.4 训练过程中看什么
训练启动后不要只盯终端日志,直接用Ultralytics自动生成的指标文件:
tail -n 5 runs/detect/train/results.csvresults.csv里逐行记录了每轮的box_loss、cls_loss、mAP50、mAP50-95。重点看两个信号:loss是否在前20轮快速下降并随后平滑收敛,mAP50是否在稳定上涨但还有抖动。航拍小目标场景里,mAP50的抖动幅度比地面数据集大很多,前几十轮忽高忽低是正常的,只要整体趋势向上就不要随手中断。
显存不足时报错通常是CUDA out of memory,先把batch降到4,还不行就把imgsz降到960。断点续训用resume=True命令,它会自动读取runs目录下最新的训练状态,不需要手动指定chkpt。
4. 无人机图像数据集的小目标调优:切图、mosaic与P2检测头
航拍场景的目标检测精度上不去,绝大多数问题出在目标太小。无人机在常见巡检高度下,一辆汽车只占画面的几十个像素,车辆边缘和地面阴影几乎没有可区分纹理。这类目标不能只靠加大epoch硬扛,需要从数据组织、增强策略和网络结构三个方向一起调。
4.1 航拍小目标问题在哪
小目标的定义通常看绝对像素,小于32×32就算小目标。YOLOv8默认特征金字塔从P3开始,对应原图下采样8倍的层级,一个32像素的目标在这个层级里只剩4×4像素,信息几乎全部丢失。更小的目标经过5次下采样后只剩不到2×2像素,检测头能用的特征极其有限。
解决路径有三条:提高输入分辨率、降低下采样倍数、把大图切成小图让目标相对变大。三条路径侧重点不同,实际项目里往往组合使用。
4.2 离线切图:1024×1024滑窗与标签同步
切图是航拍数据最有效的预处理方式。把1920×1080的原图切成多个1024×1024的tile,目标在tile中的像素占比立刻翻倍。切图的核心陷阱是标签必须跟着图片同步位移,并且对被切到边缘的目标做取舍。
import cv2 from pathlib import Path TILE = 1024 OVERLAP = 256 KEEP_IOU = 0.3 STEP = TILE - OVERLAP def range_with_edge(start, end, step): """生成滑窗位置,最后一刀贴到边缘,避免丢失右/下边界目标""" if end <= start: return [start] pos = list(range(start, end + 1, step)) if pos[-1] != end: pos.append(end) return pos def tile_one(image_path, label_path, out_dir): img = cv2.imread(str(image_path)) h, w = img.shape[:2] labels = [] for line in label_path.read_text().strip().splitlines(): cls, cx, cy, bw, bh = map(float, line.split()) labels.append((int(cls), (cx - bw / 2) * w, (cy - bh / 2) * h, (cx + bw / 2) * w, (cy + bh / 2) * h)) for y0 in range_with_edge(0, h - TILE, STEP): for x0 in range_with_edge(0, w - TILE, STEP): tile = img[y0:y0 + TILE, x0:x0 + TILE].copy() new_lines = [] for cls, x1, y1, x2, y2 in labels: # 计算目标框与当前tile的相交面积 ix1, iy1 = max(x1, x0), max(y1, y0) ix2, iy2 = min(x2, x0 + TILE), min(y2, y0 + TILE) inter = max(0, ix2 - ix1) * max(0, iy2 - iy1) if inter <= 0: continue # 只有相交占比足够才保留,避免边缘残框造成噪声 if inter / ((x2 - x1) * (y2 - y1)) < KEEP_IOU: continue nx1 = max(0, ix1 - x0) / TILE ny1 = max(0, iy1 - y0) / TILE nx2 = min(TILE, ix2 - x0) / TILE ny2 = min(TILE, iy2 - y0) / TILE new_lines.append(f"{cls} {(nx1 + nx2) / 2:.6f} {(ny1 + ny2) / 2:.6f} " f"{(nx2 - nx1):.6f} {(ny2 - ny1):.6f}") name = image_path.stem + f"_{y0 // STEP}_{x0 // STEP}" cv2.imwrite(str(out_dir / f"{name}.jpg"), tile) (out_dir / f"{name}.txt").write_text("\n".join(new_lines), encoding="utf-8")TILE=1024和训练时的imgsz=1280相互配合,切出来的tile可以直接送入训练。OVERLAP=256保证被切到边缘的目标在相邻tile中仍完整出现至少一次。KEEP_IOU=0.3的含义是:目标只有30%以上区域落在当前tile内才保留这个标签,避免只保留目标的一小条边缘,因为这种残框会让模型学到错误的框位置。
切图会显著增加样本量,4000+张原图通常会切成一万张以上的tile。训练时直接指向切图后的目录,epochs可以适当缩减,因为同一目标被多个tile重复覆盖,训练信号本身已经翻倍。
4.3 在线增强参数:mosaic、close_mosaic与翻转
切图解决的是目标绝对大小,在线增强解决的是模型泛化。航拍场景中,mosaic效果非常明显:四张图拼接之后,模型被迫学习跨图像的上下文,对小目标的背景混淆抗性更强。
yolo detect train data=uav4000.yaml model=yolov8s.pt imgsz=1280 \ mosaic=0.8 close_mosaic=10 fliplr=0.5 translate=0.1 scale=0.4mosaic=0.8表示80%的样本使用拼接增强,没必要设成1.0,留出一定比例的原始图可以让模型保持对真实分布的感知。close_mosaic=10在最后10轮自动关闭mosaic,避免训练集分布和验证集分布不一致,这是mAP最后几个点能不能稳住的关键。translate=0.1和scale=0.4模拟飞行高度和水平漂移的扰动,这类增强更贴近实际数据分布。
航拍图像的翻转需要看场景而定。巡检类数据里的车辆、行人没有方向语义,fliplr可以放心开;如果数据集中包含地面文字、交通信号灯这类有方向性的目标,翻转会造成方向信息错乱,建议关掉。
4.4 P2检测头与类别均衡
如果切图之后mAP仍然卡住,下一步调整网络结构。YOLOv8默认从P3检测头开始,对应下采样8倍;P2检测头对应下采样4倍,能直接利用更高分辨率特征图中的细节纹理。
yolo detect train data=uav4000.yaml model=yolov8-p2.yaml pretrained=yolov8s.pt \ imgsz=1280 batch=8换成P2检测头的代价很直接:显存占用显著增加,推理速度下降,但面对十几像素的目标,收益通常值得付出。显存不足时优先把imgsz降到960,P2头带来的特征分辨率提升仍然存在。
类别不均衡方面,如果某个类别在标注检查中确实数量偏少,最稳妥的做法是在数据层重采样。把包含该类的图片整体复制一份放进训练目录,比修改损失函数权重更直观,也不会影响验证集分布:
for f in labels/train/*.txt; do if grep -q '^0 ' "$f"; then base=$(basename "$f" .txt) cp "images/train/$base.jpg" images_balance/ cp "$f" labels_balance/ fi done这里的^0匹配第一个类别索引为0的目标,也就是上一节看到的行人。复制后把新目录当作训练集路径即可。重采样之后需要重新关注验证集的表现,如果验证集里该类样本本身就少,mAP会忽高忽低,这就是过拟合信号。
5. 无人机图像数据集的验证与落地:mAP、混淆矩阵和推理技巧
5.1 用val命令重新评估best.pt
训练结束后,Ultralytics会自动保存best.pt和last.pt。不要只用训练过程中的验证指标下结论,单独跑一遍验证,排除训练中断、数据加载异常等因素:
yolo detect val model=runs/detect/train/weights/best.pt data=uav4000.yaml imgsz=1280输出里的核心指标是mAP50和mAP50-95。下面是一份示例输出,不同数据划分跑出来的数值有差异,看的是相对关系:
| Class | Images | Instances | P | R | mAP50 | mAP50-95 |
|---|---|---|---|---|---|---|
| all | 400 | 8200 | 0.721 | 0.638 | 0.684 | 0.486 |
| pedestrian | 400 | 4200 | 0.655 | 0.502 | 0.570 | 0.350 |
| car | 400 | 1900 | 0.832 | 0.791 | 0.846 | 0.641 |
| truck | 400 | 700 | 0.710 | 0.668 | 0.702 | 0.489 |
行人mAP远低于车辆,是因为行人在航拍视角下尺寸更小、彼此遮挡更重。如果行人是业务重点,随后要优先做针对性的切图尺寸调整或补充数据。
5.2 从PR曲线和混淆矩阵找问题
验证结束后,runs/detect/val目录下会生成PR_curve.png和confusion_matrix.png:
ls runs/detect/val/PR曲线越靠近右上角越好,曲线出现明显直角拐弯说明置信度阈值选择对精度和召回的影响很大。混淆矩阵是找问题的最快路径:看类别之间的错分集中在哪。航拍场景里最典型的是小汽车被误检为卡车,或者行人被漏检到背景列。这类错误靠调阈值解决不了,需要回到标注阶段补边界框质量,或者用4.2的切图重新组织训练数据。
5.3 推理阶段的三个落地技巧
第一个技巧是推理时开启增强式测试:
yolo detect predict model=best.pt source=test/ imgsz=1280 augment=Trueaugment=True会做多尺度翻转测试并合并结果,mAP能提升一点,但推理耗时翻倍,适合离线批处理,不适合实时视频流。
第二个技巧是切图推理。飞行高度较低、画面中没有大量小目标时不需要切;一旦目标密度大或目标小,把输入图像切块推理再把框坐标加回原图坐标,效果比单纯加大imgsz更可控。实现时用与训练阶段相同的TILE和OVERLAP参数,tile坐标偏移在合并时逐级加回,最后用NMS合并重叠框。重叠区域的同一目标会出现两个框,置信度取两个框的更高值即可。
第三个技巧是收集失败样本回注训练集。把推理错误的图片按“漏检”“误检”两类归档,隔一段重新标注并混入下一轮训练。无人机图像数据集最难的不是模型结构,而是新场景的分布漂移:光线角度、季节背景、飞行高度稍微一变,之前的高mAP可能直接崩溃。遇到这种情况,优先对比新场景和已有训练集的背景差异,用失败样本补充负样本,比继续堆epoch和调学习率有用得多。
本文还有配套的精品资源,点击获取