简介面向无人机目标检测任务的完整数据集与配套工具包适合正在学习YOLO系列算法、需要真实场景图片训练模型的开发者与课程学员。资源包含约5000张高质量无人机视角图片采用LabelImg标注并提供VOCxml、COCOjson和YOLOtxt三种格式标签分别存放可直接接入主流YOLO训练流程。压缩包内共2000个文件以1986个xml标签文件为主另含6个HTML环境搭建与训练教程、5个txt说明文件及3个Python数据集划分脚本整体约301.59MB。教程覆盖Linux与Windows双系统的YOLO环境配置、案例修改与训练方法划分脚本支持按需生成训练集、验证集和测试集。目前已有433人学习浏览资源结构清晰从数据准备到模型训练均有配套说明能帮助使用者避开标注格式转换和环境配置等常见坑点。1. 无人机目标检测数据集为什么值得你花一个晚上搞定它做无人机视角的目标检测第一道坎从来不是模型而是数据。拿航拍图像来说同样一辆车在地面数据集里可能占了几百个像素到了无人机图像里就变成十几个像素的一小团标注标准、类别定义、图像分辨率全都不一样。如果你从零开始收集和标注一套无人机数据集光是框出几千个目标按熟练工一小时标两三百个的速度没个把星期根本下不来更别说中间标错、漏标、返工的时间。这也是为什么大家对现成的YOLO无人机目标检测数据集这么在意——直接把有人替你打过包的5000张图和相关标签拿过来当天就能进入环境搭建和训练省掉的其实是整个流程里最枯燥、最容易被低估的那段时间。这篇笔记就把这套数据集从解压到训出模型的全过程讲透三种标签格式各自怎么读、怎么验证它们没损坏、划分脚本怎么落地以及无人机小目标训练里那些让你翻车的坑。内容适合两类人想快速上手YOLO系列训练、拿现成数据走通全流程的新手以及手里有自己的数据、想借鉴一套干净处理流程的熟手。2. 三种标签格式怎么选VOC、COCO 和 YOLO 各自解决什么问题2.1 三种格式的本质差异与适用场景一个数据集包同时给了VOC、COCO、YOLO三种格式标签很多刚接触的人会疑惑这到底是重复还是必要其实这是同一批标注对象的三种不同“表达方式”对应的是不同训练工具链。VOC格式源自PASCAL VOC竞赛每张图片对应一个同名XML文件框的坐标记录的是左上角和右下角的像素值。COCO格式把所有信息塞进一个JSON文件图片、类别、标注各自用id关联。YOLO格式最精简每个目标一行txt记录的是类别id和归一化后的中心点坐标、宽高。格式存储方式坐标表达典型消费方VOC每图一个XMLxmin, ymin, xmax, ymax像素labelimg 默认、老代码COCO单JSONx, y, width, height像素左上角为原点Detectron2、MMDetectionYOLO每图一个txtclass_id, x_center, y_center, w, h归一化YOLOv5/v8/v9 系列训练我一般建议训练直接选YOLO格式因为从加载效率上讲txt文件读取最轻量不需要解析XML树或者翻JSON的嵌套字典。但VOC和COCO格式的存在价值在于兼容性——你后续想换检测框架、做数据增强、跑开源评估代码很多工具只认其中某一种三种格式齐全意味着你不需要临时去网上找转换脚本。2.2 拿到格式后的第一步核对标签有没有损坏格式齐全不等于标签就能直接用。我拿到这类数据集包时第一件事不是急着配环境而是先写个可视化脚本把YOLO标签直接画回原图上肉眼确认坐标有没有错位、类别id是否合理。这一步能在半小时内帮你避免之后训练到一半才发现数据有问题的尴尬。python import cv2 import numpy as npimg_path images/train/000001.jpg label_path labels/train/000001.txt class_names [car, truck, person, bicycle] # 按你数据集的类别顺序写img cv2.imread(img_path) h, w img.shape[:2]with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) x_center, y_center, box_w, box_h map(float, parts[1:])# YOLO存的是归一化坐标必须乘回图片真实宽高 x1 int((x_center - box_w / 2) * w) y1 int((y_center - box_h / 2) * h) x2 int((x_center box_w / 2) * w) y2 int((y_center box_h / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)cv2.imwrite(check.jpg, img)逻辑说明YOLO标签里的五个数值分别是类别id、中心点x、中心点y、宽、高全部是相对图片宽高的比例值取值在0到1之间。所以画框时必须先乘以图片实际宽高还原成像素坐标再算左上角和右下角。很多新手在这一步直接拿归一化数值当像素用画出来的框全挤在图片左上角约等于瞎了。参数说明class_names的顺序必须严格对应txt里类别id的定义id 0 就取列表第一个元素。如果画出来框的位置和物体明显对不上或者类别名错位说明标签本身有问题建议直接放弃这张图而不是训练时让模型去学错误标注。2.3 标签质量检查的三个实用技巧可视化抽样只抽查几张速度慢不适合全量检测。我再给你几个纯代码层面的检查方法。检查坐标是否越界YOLO是归一化坐标理论上不可能出现大于1或小于0的情况。一旦出现多半是转换脚本在合并数据集时写错了分母。下面这段代码可以快速排查python label_dir labels/train bad_files []for name in os.listdir(label_dir): if not name.endswith(.txt): continue with open(os.path.join(label_dir, name), r) as f: for line in f: vals line.strip().split() if len(vals) ! 5: bad_files.append((name, 字段数不对)) break try: coords [float(v) for v in vals[1:]] except ValueError: bad_files.append((name, 坐标不是数字)) break if not all(0.0 c 1.0 for c in coords): bad_files.append((name, 归一化坐标越界)) breakprint(f共发现 {len(bad_files)} 个异常文件) for f, reason in bad_files[:20]: print(f.name, reason)检查图片与标签是否一一对应即每张jpg都该有同名txt反过来也要成立。这个检查极其重要因为训练框架是按文件名去找标签的少一个txt不会报错只会让这张图变成无目标图参与loss计算时梯度方向是错误的。bash cd images/train for f in *.jpg; do base${f%.jpg} if [ ! -f ../../labels/train/${base}.txt ]; then echo 缺少标签: ${f} fi done3. 标签格式转换与数据划分两个脚本打通到 YOLO 训练的前置准备3.1 COCO 转 YOLO 的转换逻辑与代码虽然数据集包标题写的是三种格式齐全但实际使用中你大概率还是需要自己写转换脚本——比如你后续补充了自己的标注数据或者从其他数据集源拿到的只有COCO格式。这里给出一个最常用的COCO转YOLO脚本核心逻辑是先建立图片文件名到id的映射再按图片id聚合所有标注框最后把坐标从像素值换算成归一化值。python import json import osdef coco_to_yolo(coco_json, output_dir, class_names): with open(coco_json, r, encodingutf-8) as f: coco json.load(f)# COCO的类别id从1开始转YOLO时统一减1从0开始 cat_id_map {cat[id]: idx for idx, cat in enumerate(coco[categories])} # 建立 image_id - 图片信息的映射 img_id_map {img[id]: img for img in coco[images]} # 按image_id聚合所有标注 anns_by_img {} for ann in coco[annotations]: img_id ann[image_id] anns_by_img.setdefault(img_id, []).append(ann) os.makedirs(output_dir, exist_okTrue) for img_id, anns in anns_by_img.items(): img_info img_id_map[img_id] img_w img_info[width] img_h img_info[height] base_name os.path.splitext(os.path.basename(img_info[file_name]))[0] with open(os.path.join(output_dir, base_name .txt), w) as f_out: for ann in anns: # COCO的bbox是[x, y, width, height]左上角原点 x, y, w, h ann[bbox] cls_id cat_id_map[ann[category_id]] # 归一化并转换成中心点坐标 x_center (x w / 2) / img_w y_center (y h / 2) / img_h norm_w w / img_w norm_h h / img_h f_out.write(f{cls_id} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}\n)coco_to_yolo(instances.json, labels/train, class_namesNone)逻辑说明COCO的坐标系原点是图片左上角bbox给出的是左上角坐标加宽高转YOLO时要先换算成中心点再除以图片宽高。这里最容易出的错是类别id偏移——COCO的categories id从1开始且有时不是连号的直接拿原始id当YOLO类别id会导致类别错乱。参数说明class_names参数在脚本里其实没用到只用于让你打印时核对顺序保留它是提醒你YOLO的类别顺序完全由txt文件里写的id决定训练配置里的类别名单必须严格对应。coco_json路径如果数据集包内COCO标签是拆成多个json的需要先合并再转换。3.2 数据划分脚本比例、随机种子与文件配对的避坑数据集包带的划分脚本一般能直接跑但如果你想自己重新划分或者补充了自定义数据之后需要重新分那必须自己掌握划分逻辑。划分的核心不只是把图片随机分成三份还要保证图片和标签始终配对移动否则训练时一半样本没有标签。python import os import shutil import randomrandom.seed(42)split_ratio {train: 0.7, val: 0.2, test: 0.1}src_images images/all src_labels labels/alldst_base dataset_split目标结构dataset_split/train/images, dataset_split/train/labels, ...all_images [f for f in os.listdir(src_images) if f.endswith(.jpg)] all_labels [f for f in os.listdir(src_labels) if f.endswith(.txt)] image_names [os.path.splitext(f)[0] for f in all_images]random.shuffle(image_names)def get_split_index(i): if i round(len(image_names) * split_ratio[train]): return train elif i round(len(image_names) * (split_ratio[train] split_ratio[val])): return val else: return testfor idx, name in enumerate(image_names): split get_split_index(idx) img_src os.path.join(src_images, name .jpg) lbl_src os.path.join(src_labels, name .txt)img_dst_dir os.path.join(dst_base, split, images) lbl_dst_dir os.path.join(dst_base, split, labels) os.makedirs(img_dst_dir, exist_okTrue) os.makedirs(lbl_dst_dir, exist_okTrue) # 注意先检查标签是否存在避免把无标签图拷进训练集 if os.path.exists(img_src) and os.path.exists(lbl_src): shutil.copy2(img_src, os.path.join(img_dst_dir, name .jpg)) shutil.copy2(lbl_src, os.path.join(lbl_dst_dir, name .txt)) else: print(f跳过样本 {name}图片或标签缺失)print(划分完成) for split in split_ratio: img_count len(os.listdir(os.path.join(dst_base, split, images))) lbl_count len(os.listdir(os.path.join(dst_base, split, labels))) print(f{split}: {img_count} 张图片, {lbl_count} 个标签)逻辑说明脚本用固定随机种子让划分结果可复现train、val、test的比例是7:2:1这是中小型数据集最常用的默认比例。移动文件统一用copy2而不是move原因很实际——如果你划分完发现某张图损坏需要重标原图还在原始目录不用翻备份。参数说明split_ratio可以用8:1:1或6:2:2取决于你的数据量和用途。数据集规模在5000张以上、类别均衡时7:2:1是稳妥选择如果只有一两千张建议把val和test压小给train留更多数据做训练但test一定不要为0否则最终评估没有说服力。3.3 划分完成后必须跑的验证命令很多人划分完就直接开训结果跑到一半发现val集里全是某单类别的图或者train集和test集有重叠。这里给两条我常用的快速验证命令。bash检查train和test的图片文件名是否有交集防止数据泄露comm -12(ls dataset_split/train/images | sort)(ls dataset_split/test/images | sort) | head统计每个split的类别分布确认划分没有引入分布偏差for split in train val test; do echo $split cat dataset_split/$split/labels/*.txt | awk {print $1} | sort | uniq -c done记住一个原则数据划分不是图省事而是为了让val集能真实反映模型的泛化能力。如果划分完你发现val集的类别分布和train明显不一致比如train里60%是car而val里car只占10%那这个val集的评估结果就不要信。4. YOLOv8 训练无人机数据集环境配置、训练命令与关键参数4.1 环境搭建从零到能跑训练命令标题里写的是YOLO系列目前社区活跃度最高、教程最全的就是YOLOv8。我建议直接用ultralytics框架它把数据加载、增强、训练、导出打包得比较干净命令式交互也方便新手理解整体流程。环境配置这块主要卡在GPU版本匹配上CPU环境也能跑但速度会让你怀疑人生。bash创建虚拟环境Python版本建议3.9-3.11conda create -n yolo python3.10 -y conda activate yolo安装CPU版或GPU版pytorch二选一CPU版兜底用能跑但慢pip install torch torchvision --index-url https://download.pytorch.org/whl/cpuGPU版需要先确认你的CUDA版本一般11.8/12.1比较常见pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118安装ultralyticspip install ultralytics安装完之后跑一句最简单的验证能出结果就说明环境没问题。注意第一次运行ultralytics会自动下载预训练权重对网络环境有点要求如果下载卡住可以手动下载权重文件放到项目根目录下。bash yolo detect predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg4.2 数据配置dataset.yaml 的写法与路径陷阱环境装好只是第一步真正让人翻车的通常是data配置文件的路径和类别顺序。我在第3章已经强调过类别顺序要和标签严格一致这里再看一遍实际写法。yamldataset.yamlpath: /home/user/dataset_split # 数据集的绝对路径 train: train/images val: val/images test: test/imagesnc: 4 names: [car, truck, person, bicycle]注意path字段建议写成绝对路径。YOLO框架解析相对路径时是相对于当前工作目录的如果你在项目根目录执行命令偶尔能碰上相对路径写对但运行时换了工作目录导致找不到数据的情况。绝对路径虽然不好迁移但能帮你少折腾半小时。4.3 训练命令与参数选择的实战经验训练命令本身不复杂难的是参数怎么针对无人机场景调整。无人机图像里的目标普遍偏小这就需要在图像尺寸和batch size之间找平衡。GPU显存不够时优先保imgsz而不是保batch因为YOLO对小目标的检测能力跟输入分辨率直接相关把图缩得太小小目标就直接变成了几个像素。bash cd /home/user训练命令这里用yolov8s做示例yolo detect trainmodelyolov8s.ptdatadataset.yamlepochs100imgsz1280batch8device0workers4patience15参数说明model用yolov8s.pt是预训练权重框架会做迁移学习比从零训练收敛快得多无人机场景目标虽然和COCO差异大但底层特征仍然复用价值很高。epochs默认100实际跑的时候结合patience看如果连续15个epoch val集mAP没提升就会早停不用干等。imgsz1280是我对无人机数据集的一个常用设置因为原始航拍图一般都挺大2000×1500甚至更大直接缩到640会把整个目标区域抹成噪点。batch8对应的是1280分辨率下的显存消耗如果你的卡是8G显存建议减到424G显存可以设16。4.4 训练过程中看什么日志文件里的关键指标训练日志里最容易误导新手的是train_loss一直下降就以为万事大吉。train_loss下降只能说明模型在训练集上拟合了真正要盯的是val侧的指标也就是mAP50和mAP50-95的变化趋势。train结束后框架会输出最佳权重位置和对应指标。我习惯看训练完的results.png和confusion_matrix.png这两个图。results.png里如果val/box_loss曲线在后半段开始抬头上翘说明已经过拟合了这时把epochs调回去或者加大数据增强比换更大模型更管用。confusion_matrix.png则能直接看出哪些类别互相混淆比如无人机视角下车辆和阴影经常被认混。跑测试集验证时用如下命令bash用best.pt在测试集上评估yolo detect valmodelruns/detect/train/weights/best.ptdatadataset.yamlsplittestimgsz12805. 无人机数据集训练的避坑记录从报错到玄学问题5.1 训练卡在“Dataset error”但路径明明是对的现象执行训练命令后数据集加载阶段直接报Dataset xxx error但反复看dataset.yaml里的路径都没问题。原因最常见的是路径指向了一个没有正确组织结构的目录yolo要求images和labels目录在同一级且标签目录下必须对应子目录。另外一个隐蔽原因是标签文件里混入了DS_Store或隐藏文件yolo在读取label文件列表时遇到非txt文件会报错。解决先确认目录结构是images/train labels/train这种配对层级然后清理掉labels目录里的所有非txt文件。bash找出labels目录下所有非txt文件find dataset_split -name .txt -o -name .jpg | wc -l find dataset_split -path /labels/ ! -name *.txt -type f5.2 训练loss正常下降但预测框的位置完全对不上现象loss降到相对稳定可拿训练好的权重去预测检测框确实框到了目标附近但位置偏了一大截框的大小也不对。原因九成是标签坐标转换出了问题特别是COCO转成YOLO时忘记除以图片宽高就直接当成归一化坐标。这样的框训练时模型也在拟合但因为标签本身就是错的最终学出来的映射关系就偏移了。解决遇到这种情况先回第2章做标签可视化如果某几张图框明显偏左或偏上统一偏移那基本就是坐标归一化漏算了。重点检查转换脚本里乘除的顺序x_center是除以img_wy_center是除以img_h两者别弄反。5.3 验证集mAP高但实际航拍测试效果差现象val集mAP50能到0.8以上自己拿一段真实航拍视频去测检测结果稀烂漏检严重。原因数据集划分出了偏差。航拍视频是连续帧如果划分前没有按场景去重那train和val里很可能包含同一个飞行架次拍的相似画面val评估的数字水分很大。解决这种情况我在划分时按视频场景分帧而不是全量随机划分。如果原始数据就是单帧图片可以先对图片做特征聚类保证同一个场景的帧只进train或val其中一边。对于此类数据集用单独的一段新视频做最终测试更有说服力val指标只能当参考。5.4 小目标密集区域漏检率高调整conf阈值却越调越怪现象无人机俯视停车场或农田场景远处的小目标漏检一大堆把conf阈值从0.25调低到0.1漏检少了但误检大量增加。原因模型的置信度对尺度差异敏感。同样一辆车近处大目标置信度0.7远处小目标可能只有0.2。单靠调阈值无法同时兼顾两端。更本质的问题是输入分辨率不够小目标在特征图上只剩一两个像素模型根本没有足够特征去判断它是什么。解决这一步我一般把推理时的imgsz调到和训练一致如果训练用的1280推理就别用640。如果显存不够可以尝试tiling思路把原图切成若干有重叠的瓦片分别检测再合并结果。5.5 同一份代码别人能训我这边就OOM现象跑网上的同行配好的训练命令别人同样的参数能训我一跑就CUDA out of memory。原因显卡不一样是表面原因更深层的是很多机型在batch size和图像尺寸不相同以及数据读取时pin_memory和workers的配置对显存峰值都有影响。解决先确认显存大小然后按4.3节的建议调batch和imgsz。还有一个隐蔽因素:训练过程中开了TensorBoard或数据可视化界面也会吃显存训练时建议把这些都关掉。6. 把训练出的权重用到真实场景实测调优的完整闭环一个容易被忽略的事实训练完成不代表模型能直接交付。从best.pt到真正能用于实时推理中间还需要做推理配置的调优。我自己习惯的流程是找几段有代表性的航拍视频跑批处理预测然后分析badcase这个过程通常能暴露训练时看不到的问题。python from ultralytics import YOLOmodel YOLO(runs/detect/train/weights/best.pt)推理参数里最重要的两个conf和iouresults model.predict( sourcetest_video.mp4, conf0.25, # 置信度门槛 iou0.5, # NMS的IoU阈值 imgsz1280, # 与训练保持一致 device0, saveTrue, vid_stride1 # 每隔几帧做一次检测 )conf和iou这两个参数要配合起来调。无人机小目标密集区域NMS的iou阈值设太高会把相邻目标的主要框滤掉设太低又会保留大量重叠框。我的一般经验是conf先设0.25看基线结果如果误检太多往0.35-0.4调如果漏检太多往0.15-0.2降每次只调一个参数。iou保持0.5不动。另外一个经验如果画面中有大量目标被截断或者互相遮挡我会把iou往下调到0.4效果往往比调conf更明显。video处理这块还有一个容易卡住的问题就是视频解码速度跟不上检测速度。cpu解码容易成为瓶颈这时可以用vid_stride跳帧处理先看整体效果确认流程没问题后再逐帧精调。我自己的一个习惯是跑完批量预测后把漏检和误检的帧单独抽出来人工看一遍归好类再决定下一步是调参数还是补数据。你会很容易发现模型把阴影当成了车那说明这类错误在训练数据里样本不足并不是参数能解决的这时回到数据集去增强阴影场景的负样本比继续调阈值更有价值。这套流程从拿到数据集到最终交付模型如果你一次都没踩坑大概需要两个晚上如果踩坑多半是困在标签和路径问题上那也是每个做目标检测的人都要经历的阶段。希望这篇笔记能帮你把时间压缩到一个晚上。本文还有配套的精品资源点击获取
