无人机目标检测数据集处理全攻略:从ZIP解压到YOLOv8训练
发布时间:2026/9/28 20:24:17来源:尧图网络
简介面向无人机小目标检测与跟踪场景的数据集第三批共包含1万多张空中拍摄的飞行图片目标类别统一为drone且目标在画面中占比较小能有效检验算法在低分辨率、小尺寸目标上的表现。数据主要来自DJI Phantom等无人机平台视觉角度和光照条件丰富适合目标检测、目标跟踪及相关对比实验。本批与已发布的前两批数据集做了去重处理可放心与其他批次联合使用。压缩包约920MB共3万余个文件内含10146张jpg图片以及一一对应的10146个txt、10146个xml标注文件分别提供YOLO与VOC两种标准格式免去自行转换标签的麻烦可直接接入YOLO系列、mmdetection或VOC工具链。目前已有2286人学习下载适合具备一定目标检测基础、需要扩充无人机样本或专门研究小目标场景的开发者。1. 一个 zip 背后是整个无人机视角检测任务的起点做目标检测的人早晚会遇到这样一层尴尬模型结构、训练命令、调参玄学聊得头头是道但真正卡住你三天进度、让你怀疑人生的往往不是网络结构而是那份来历不明的数据集。drone-dataset-3.zip这个标题就是典型的“数据集系列第三包”名字里只带了一个 drone它对应的是无人机视角下的目标检测任务——车辆、行人、船只、建筑工地这类俯视目标和日常手机相册里的平视照片完全是两个世界。这个 zip 解决的是从零开始做无人机视觉方案时的“第一公里”问题有没有数据、标注格式是什么、能不能直接丢进 YOLO 或 mmrotate 训练。适合正在做遥感、低空巡检、交通监控、园区安防的算法工程师或者说任何一个被缺数据逼到想自己标数据的人。2. 拆开 drone-dataset-3.zip 之前先搞清无人机数据集的三种常见形态2.1 三种标注格式的识别顺序VOC / YOLO / COCO拿到一个 zip第一件事不是解压而是先猜里面是什么格式。无人机目标检测数据集最常见的三种标注格式按出现频率排序大概是这样的YOLO 的 txt每行一个目标内容是 class x_center y_center width heightPascal VOC 的 xml每个目标是一个 object 节点带 bndbox 坐标COCO 的 json整个数据集一个 annotations 数组。drone 类数据集里VOC 和 YOLO 格式出现的概率最高因为无人机目标检测的主流下游工具是 YOLO 系很多人会把原始标注统一转成 YOLO 格式再发布。判断顺序很简单解压后先看有没有annotations目录里面有 xml 就是 VOC如果有labels目录里面是 txt再看有没有同名的 classes.txt那就是 YOLO如果只有一个 json 文件摆在根目录那大概率是 COCO。还有一个比较隐蔽的情况——数据集可能同时提供 images 和 labels但 labels 是空的或者只有一小部分图片有标注。这是系列数据集的常见坑后面避坑章节会细说。2.2 解压、清点、抽检拿到 zip 的十分钟体检流程我拿到任何数据集压缩包都会先跑一遍固定的体检流程不急着解压到项目里# 1. 先用 unzip -l 看压缩包内部结构不实际解压 unzip -l drone-dataset-3.zip | head -50 # 2. 统计图片和标注文件数量 unzip -l drone-dataset-3.zip | awk {print $4} | grep -E \.(jpg|jpeg|png)$ | wc -l unzip -l drone-dataset-3.zip | awk {print $4} | grep -E \.(txt|xml|json)$ | wc -l # 3. 确认压缩包完整性 unzip -t drone-dataset-3.zip这段命令的前两步是只看不拆先摸清目录结构和文件数量避免解压完发现是“空包 一堆损坏文件”的尴尬局面。第三步的unzip -t是测试压缩包完整性特别适合从网盘或别人转发拿到手的包能提前把传输损坏的文件筛出来。注意第二步用了grep做文件扩展名过滤实际使用时要根据上一步看到的真实目录结构调整路径层级别直接抄。提示如果解压后发现图片和标注文件数量对不上比如 5000 张图只有 3000 个标注文件先别删数据用命令行把有标注和无标注的图片分别列出来后面训练时做个子集筛选比补标要划算得多。2.3 用 pandas 快速盘点类别分布解压之后我最先看的不是图片长什么样而是标注的类别分布。类别分布直接决定了这个数据集能不能用、需要补什么、训练时要不要调权重。写一个快速统计脚本import glob import os from collections import Counter # 假设标注是 YOLO 格式每行: class_id x_center y_center width height label_files glob.glob(labels/*.txt) class_counter Counter() empty_files [] total_objects 0 for label_path in label_files: with open(label_path, r, encodingutf-8) as f: lines f.readlines() if not lines: empty_files.append(label_path) continue for line in lines: parts line.strip().split() if len(parts) 1: class_id int(parts[0]) class_counter[class_id] 1 total_objects 1 print(类别 - 目标数量:, dict(class_counter)) print(空标注文件数:, len(empty_files), 占总标注文件比例:, round(len(empty_files)/len(label_files), 4)) print(平均每张图目标数:, round(total_objects / max(len(label_files), 1), 2))这段脚本跑完数据集的健康状况基本就有数了类别是不是严重倾斜、有多少空标注、平均每张图几个目标。无人机视角的数据集有个典型特征——平均每张图的目标数普遍偏少特别是高空拍摄的图一张图里可能只有两三个小目标。如果你统计出来平均每张图 0.5 个目标那这个数据集直接训练是没意义的得先做切片裁剪或者过滤背景占比过高的图片。如果类别 id 是不连续的比如只有 0 和 4那大概率是发布者裁剪过子集或者合并过类别必须回到类别定义文件里核对。3. 处理数据集用于 yolov8 训练格式归一和路径修正3.1 把 VOC 转 YOLO 格式转换脚本与四个边界坑如果解压出来的是 VOC xml而你的训练管线是 yolov8现在 yolov11 也出了接口基本一致那就必须转成 YOLO 的 txt 格式。转换本身不复杂网上脚本一堆但我见过的翻车案例几乎都集中在坐标归一化和边界框处理上。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, output_dir, class_list): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) yolo_lines [] for obj in root.iter(object): class_name obj.find(name).text if class_name not in class_list: continue class_id class_list.index(class_name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 边界修正防止越界或宽高为 0 xmin max(0, min(xmin, img_w - 1)) xmax max(xmin 1, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(ymin 1, min(ymax, img_h - 1)) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h yolo_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) out_path os.path.join(output_dir, os.path.basename(xml_path).replace(.xml, .txt)) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(yolo_lines)) return len(yolo_lines) # class_list 必须和训练时的 data.yaml 保持一致 class_list [car, person, boat, building]这段代码的关键不在 XML 解析而在四个容易被忽略的边界点第一xml 里可能有部分目标类别不在你的 class_list 里continue跳过了但没统计后续要核对丢失数量第二有些 xml 的 bndbox 坐标会超出图片尺寸训练时会导致 opencv 读图越界报错或 loss 变成 nan所以要做 clamp第三xmax 和 ymax 做了xmin 1的下限保护防止标注退化成一个点第四归一化保存时保留 6 位小数精度不够会导致小目标框偏移几个像素在无人机小目标场景下影响明显。3.2 按场景拆分 train/val而不是随机拆大多数人拿到数据集后直接random_split(0.8, 0.2)就把训练集和验证集拆了。这在通用目标检测上问题不大但在无人机数据集上经常导致验证集指标虚高。原因在于同一架无人机、同一条航线、同一个时间段拍出来的连续帧画面高度相似。随机拆分会把相似帧同时分进训练集和验证集模型等于“见过”验证集了。对于drone 视角数据更靠谱的拆分维度是场景按拍摄地点、按飞行架次、按光照条件去分。比如压缩包内目录如果是按site_01/site_02/组织的就按目录拆如果文件名带序号且连续帧就按帧区间拆。一个实用做法是把图片按拍摄时间或文件名前缀分组再对组做拆分import os import random import shutil # 按文件名前缀分组假设前缀如 site1_0001.jpg 中的 site1 是场景标识 image_files os.listdir(images) scene_groups {} for img in image_files: scene img.split(_)[0] scene_groups.setdefault(scene, []).append(img) train_imgs, val_imgs [], [] for scene, imgs in scene_groups.items(): random.shuffle(imgs) split_idx int(len(imgs) * 0.8) train_imgs.extend(imgs[:split_idx]) val_imgs.extend(imgs[split_idx:]) print(ftrain: {len(train_imgs)}, val: {len(val_imgs)})这个脚本的原则是同一个场景前缀的所有帧要么大部分进训练集要么大部分进验证集避免同一场景的连续帧横跨两个集合。如果数据集的目录结构不是按场景划分的退一步至少按文件名排序后做等间隔采样作为验证集也比纯随机拆分可靠。拆分后还要注意检查验证集中是否有和训练集完全相同的图片——有些系列数据集会在不同分卷里重复收录同一批图。3.3 写 data.yaml 的三个注意点把数据塞给 yolov8 训练前要写一个 data.yaml。绝大多数人在这个文件上踩的坑不是格式而是路径和类别顺序# data.yaml path: /data/drone_dataset # 改成你存放数据集的绝对路径不要用相对路径 train: images/train val: images/val names: 0: car 1: person 2: boat 3: building三个注意点第一path字段用绝对路径不要依赖相对路径——yolov8 在 Windows 和 Linux 下的路径解析行为不一致相对路径在不同工作目录下启动训练会直接报找不到图片第二names的索引必须和标注 txt 里的 class_id 严格一致如果标注里出现过 id4 而 names 只写到 3训练时索引越界不会报错而是悄悄把 id4 的目标忽略掉mAP 会莫名上不去第三train和val指向的是图片目录而非标注目录yolov8 会自己找同名的 txt 文件如果你的图片和标注目录层级不是标准结构最好用软链接把 images 和 labels 并排放好而不是硬改代码。4. 高质量子集和类别均衡小样本类别怎么补4.1 先看类别直方图再决定要不要重新组织无人机目标检测数据集一个非常普遍的问题是类别极度不平衡。典型场景车占 70%行人占 20%船占 8%剩下的骑行者和动物加起来 2%。直接用原始分布训练模型会对大类过拟合小类几乎学不出来。这跟通用数据集里的不平衡不一样无人机俯视视角下目标尺寸差异极大——一辆车可能只有 20x40 像素而一个建筑占了大半个画面。这个双重不均匀让“直接训练”基本等于白做。我的处理顺序是先根据 2.3 节的统计结果画一个类别直方图看看每个类别的目标数量级。如果最小类目标数不足最大类的 1/10先想两个办法一是查一下 zip 里是否还有分卷没下完系列数据集经常把某一类单独打包在另一个卷里二是用小目标切片的方式扩充小类。对于 drone 数据把原图裁剪成 640x640 的 tiles 再训练是比任何高级增强都更直接有效的办法。4.2 切片裁剪和基础增强的取舍把大图切成训练块是无人机目标检测的标准操作。常见做法是设定滑动窗口和重叠率切完后只保留包含至少一个标注目标的块否则训练集里全是纯背景图模型收敛极慢而且 mAP 虚低。import cv2 import os import numpy as np def slice_image_with_labels(img_path, label_path, out_img_dir, out_label_dir, tile640, overlap0.3): img cv2.imread(img_path) h, w img.shape[:2] labels [] if os.path.exists(label_path): with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) 5: cls_id, cx, cy, bw, bh map(float, parts) # YOLO 归一化坐标转像素坐标 x1 int((cx - bw/2) * w) y1 int((cy - bh/2) * h) x2 int((cx bw/2) * w) y2 int((cy bh/2) * h) labels.append((cls_id, x1, y1, x2, y2)) valid_tiles 0 stride int(tile * (1 - overlap)) for y0 in range(0, max(h - tile 1, 1), stride): for x0 in range(0, max(w - tile 1, 1), stride): tile_img img[y0:y0tile, x0:x0tile] tile_labels [] for cls_id, x1, y1, x2, y2 in labels: # 只保留在新瓦片内有足够面积的标注 nx1, ny1 max(x1, x0), max(y1, y0) nx2, ny2 min(x2, x0tile), min(y2, y0tile) if nx2 nx1 or ny2 ny1: continue area_ratio (nx2 - nx1) * (ny2 - ny1) / ((x2 - x1) * (y2 - y1)) if area_ratio 0.3: continue # 目标被切掉太多丢弃该标注 # 转回归一化坐标 tile_labels.append( f{cls_id} {(nx1 - x0 nx2 - x0) / 2 / tile:.6f} {(ny1 - y0 ny2 - y0) / 2 / tile:.6f} f{(nx2 - nx1) / tile:.6f} {(ny2 - ny1) / tile:.6f} ) if tile_labels: out_img os.path.join(out_img_dir, f{os.path.basename(img_path)[:-4]}_{y0}_{x0}.jpg) out_lbl os.path.join(out_label_dir, f{os.path.basename(img_path)[:-4]}_{y0}_{x0}.txt) cv2.imwrite(out_img, tile_img) with open(out_lbl, w) as f: f.write(\n.join(tile_labels)) valid_tiles 1 return valid_tiles这个切片脚本里几个参数值得单独说。tile640是主流检测网络的输入尺寸和 yolov8 默认训练尺寸一致切出来的块不用再做复杂缩放。overlap0.3控制相邻瓦片的重叠率太小会切到目标正中间太大则膨胀训练集。最关键的是area_ratio 0.3这个阈值——一个目标被切掉 70% 以上还保留标注等于给模型喂了错误的监督信号识别结果会很差。无人机大图里经常出现一个目标横跨四块瓦片的情况0.3 的阈值能保证只有主要落在某一块里的目标才会在该块中参与训练。关于数据增强无人机目标检测里我反而不推荐一上来就上 mosaic、旋转这类强增强。原因是俯视目标里面的“小目标”占比高强增强会让本来就十几个像素的目标变得更模糊。先把基线跑起来再根据漏检情况逐步加增强这条路更稳。5. 无人机目标检测数据集避坑5 条血泪记录5.1 zip 解压后图片全 0 字节现象解压完unzip -t没报错但训练时 opencv 读图一片黑检查发现大量 jpg 文件大小为 0KB。原因压缩包是在传输过程中中断后续传打包的或者上传者打包时磁盘已满。unzip -t只能验证 zip 结构完整不能验证内部文件字节码有效。解决写脚本用cv2.imread遍历所有图片读不出来的直接列文件名清单删除。删除后同步删掉同名的标注 txt否则训练时 label 还在但图片缺失yolov8 会跳过这些样本但 mAP 统计已经污染了。5.2 标注类别 id 不连续0, 3, 7...训练直接报错或 loss 异常现象训练开始时类别数对不上data.yaml 里写了 5 类但标注文件里最大 class_id 是 9。原因这是系列数据集的通病——上一卷定义了完整类别表这一卷只挑了其中一部分类别打包但标注文件没有重新编号保留着原始全局类别 id。解决不要手动改 data.yaml 的 names 数量去凑而是写脚本读取所有标注文件的 class_id生成实际出现的类别映射表按新映射重写所有 txt。这个映射表后续所有训练和评估都要复用。5.3 图片尺寸不一致归一化标注在 resize 后全错位现象训练和验证 mAP 都很高但部署到实际无人机视频流上完全不能用框的位置偏移大半张图。原因压缩包内图片尺寸混乱有 1920x1080、有 4000x3000还有少量 800x600。标注归一化是基于各自原始尺寸算的训练时 dataloader 会统一 resize 到 640这一步没问题但如果你自己写推理脚本时假设了固定输入尺寸而没按原图比例还原坐标框就全歪了。解决训练前写一个检查脚本把尺寸不一致的图片单独分目录要么统一 resize 到 1024 保存要么干脆在训练时把该图片的 batch 设为 1避免 dataloader 因为尺寸不一致报错。最笨但有效的办法是压缩包里如果有带 EXIF 旋转信息的 jpg先用 PIL 批量转正再训练。5.4 背景图占比 50% 以上模型收敛到 mAP 0.5 就上不去现象loss 一直在降val mAP0.5 停在 0.5 左右不再变化预测时大量误检把树冠当成目标。原因数据集中大量纯背景帧没有标注任何目标。模型学到的是“画面纹理复杂的地方就是目标”而不是目标的语义特征。解决把所有空标注 txt 对应的图片剔除或者在 sampler 里给有目标和没目标的样本按 4:1 加权。如果剔除后样本量不够就把纯背景图切成瓦片保留一部分当负样本当作难负例挖掘用。5.5 标注框原点在左上角还是中心点弄反了直接 loss 爆炸现象训练 loss 前几个 epoch 直接 nan或者 mAP 永远为 0。原因YOLO 格式的x_center y_center width height是归一化中心点坐标而 VOC 转过来的数据如果转换脚本里把 xmin 当成了 x_center整框全错。更隐蔽的是某些数据集标注是“左上角 右下角”的归一化坐标写成了 4 列但语义完全不同。解决转换完抽样 3~5 张图写一个可视化脚本把 bbox 画回原图上肉眼看。不要相信统计值只看图——二十秒的目检能省下三小时的排错。这一步是我的固定动作每转完一个数据集都跑一遍。6. 进阶技巧吃透 drone 数据的剩余价值校验整个数据管线6.1 判断是不是需要旋转框如果你手里的 drone-dataset-3.zip 标注里出现了长宽比极大的目标——比如停着的飞机、细长的船、塔吊——先用水平框跑一个基线观察 mAP 是否在目标角度接近 45 度时系统性偏低。如果是这个数据集值得用旋转框检测器吃。常见做法是转成 DOTA 格式喂给 mmrotate和水平框 YOLO 的区别主要在标注表示DOTA 的四个点坐标 类别转训练格式时要注意四点必须按顺时针排列且第一点没有约定mmrotate 的poly2rbox会自动处理但这个转换步骤里最常见的坑是四点顺序混乱导致 rbox 角度算错。6.2 用完整性校验和可视化锚定数据可信度数据管线的最后一公里不是训练是验证数据本身有没有毒。我每次换新数据集都会跑两个东西全量图片完整性校验 随机抽 50 张图可视化标注。前者用cv2.imread批量检查后者用一个十行脚本把标注画回图上看坐标是否贴合目标轮廓。# 图片完整性校验 可视化标注 python check_images.py --img_dir images --label_dir labels \ --sample 50 --out_dir vis_check/这组参数的含义--img_dir和--label_dir是图片和标注目录--sample 50表示随机抽取 50 张图做可视化--out_dir指定可视化结果输出目录。完整校验是不抽样的全量扫描可视化是抽样目检。我个人的习惯是每转换一次格式、每修改一次类别映射都重新跑一遍哪怕只是改了 classes 的顺序。6.3 别急着删数据分卷冗余是常态drone 数据集分卷发布有很强的现实逻辑拍摄一个新场景、整理一批标注、补一卷压缩包成本远低于重新发一个大包。所以 drone-dataset-3.zip 里面很可能混有前两卷中出现过的图片或标注。直接删掉重复文件看起来没问题但要先确认标注是否一致。如果同一张图片在两卷里标注不同保留版本较新的那一个并单独记录差异文件清单——这类不一致往往暗示标注规范变了排查时这就是最重要的线索。整个处理流程走完我自己的习惯是数据集的体检报告、格式转换脚本、类别映射表、拆分记录全部留在项目目录下统一命名一行代码都不删。模型效果崩了先看数据报告而不是先调参数这套工作流救过我太多次。希望今天的这些记录对你有用。本文还有配套的精品资源点击获取
网站建设高端定制企业官网