资讯详情 无人机目标检测数据集落地:从PASCAL VOC XML到YOLO训练全流程
📅 2026/10/11 0:55:53
简介面向无人机目标检测与视觉研究的数据集包含一万张原始无人机图片可用于分析飞行特性、验证感知能力、优化导航控制算法也适合用来训练和评估目标检测、目标跟踪、地图构建等深度学习模型。数据采用Pascal VOC XML格式进行标注标签类别为drone并兼容YOLO与COCO JSON两种常见格式方便研究者直接接入业界主流训练框架。压缩包共两千个文件均为XML标注文件整体体积约七百三十五兆字节整个数据集划分了train、validation、test三个子集分别包含三千一百五十五、三千九百零六、二千九百三十九张图片便于复现实验并比较模型性能。标注内容包含图像尺寸、目标边界框及类别等关键信息模型测试正确识别率可达百分之九十五点七能够有效减少人工采集标注成本为算法调优提供可靠的数据支撑。目前已有二百三十七人学习下载适合计算机视觉研究者、无人机算法工程师以及高校相关专业学生作为基准数据集使用。1. 一万张原始无人机图片的标注数据集为什么 VOC XML 格式仍是落地的稳妥选择一万张原始无人机图片配上 PASCAL VOC XML 格式标注正确识别率可达到 95.7%这个数据集描述我在实际项目里见过不止一次。它解决的断层很具体没有标注再好的检测网络都在空转。PASCAL VOC XML 是目标检测领域最老牌的标注格式每张图片对应一个 XML 文件框住车辆、行人、建筑等目标训练框架可以直接读取。适合谁手头有无人机航拍数据、想快速验证检测方案的人以及刚入门目标检测却被标注格式卡住的新手。对新手它足够直白打开 XML 能看懂每个数字对老手它省掉了从零整理数据的功夫。后面按一条完整落地路径走先读 XML 字段再组织目录和转格式然后质检排坑最后把模型训到 95.7%。2. PASCAL VOC XML 格式拆解先读懂标注文件再谈训练PASCAL VOC 的 XML 不是给机器看的文档而是给检测框架吃的“菜单”。判断一个无人机数据集能不能直接用第一步不是跑模型而是抽查 XML 的字段是否齐全、是否良构。XML 解析出错、字段名写错后面所有脚本都会跟着翻车。2.1 一张图对应一个 XML文件结构和核心字段我习惯先随便打开一个Annotations目录里的 XML看它的骨架是否完整。一个标准 PASCAL VOC XML 长这样annotation folderUAV/folder filename000001.jpg/filename path/data/uav_dataset/JPEGImages/000001.jpg/path source databaseUAV Dataset/database /source size width1920/width height1080/height depth3/depth /size segmented0/segmented object namecar/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin100/xmin ymin200/ymin xmax300/xmax ymax400/ymax /bndbox /object /annotation这段 XML 的最小骨架就三个部分size告诉模型原图宽高object描述每个目标bndbox给出框的左上角和右下角像素坐标。VOC 约定 bndbox 用整数训练框架读到后会用 size 做归一化。filename要和JPEGImages里的文件名完全一致路径大小写也要对上否则数据加载器会直接跳过这张图。2.2 类别、目标数和 bndbox 的对应关系用表格核对解析 XML 时最容易漏掉的是字段间的隐含约束。比如一个annotation下可以挂多个object目标数量就等于findall(object)返回的节点数bndbox必须满足xmin xmax且ymin ymax。整理成表更方便核对节点含义注意事项/annotation/filename图片文件名必须和 JPEGImages 里的文件名一致/annotation/size/width图片宽度像素坐标归一化的分母/annotation/size/height图片高度像素坐标归一化的分母/object/name目标类别名大小写敏感要和训练配置完全一致/object/truncated目标是否被图像边界截断0 或 1/object/difficult目标是否难以辨认0 或 1difficult1 时很多框架默认忽略/object/bndbox目标框左上/右下坐标整数像素xmin xmaxymin ymax一个能跑到 95.7% 识别率的无人机数据集类别通常不会太多常见的是car、person、truck两三类的组合。类别多、类间重叠严重时同样的标注量识别率会明显下降。所以拿到 XML 后用脚本统计一下name的分布是判断这个数据集值不值得投入的第一步。2.3 打开和编辑 XML 的三种方式编辑器、浏览器渲染与脚本“XML 文件怎么打开和编辑”这个问题很多新手卡在第一步。三种方式我都用过文本编辑器VS Code 或 Notepad装 XML Tools 插件后可以格式化、校验良构性适合单文件查看和手改。浏览器Chrome 或 Edge 直接把 XML 文件拖进窗口会渲染成可折叠的树形结构适合快速确认单个文件有没有object节点。Python 脚本批量改字段时必须用比如把类别person统一改成pedestrian或者修正文件名。批量编辑时我用 lxmlfrom lxml import etree tree etree.parse(Annotations/000001.xml) root tree.getroot() # 批量把 person 改成 pedestrian顺手修正类别名不统一的问题 for obj in root.iter(object): name obj.find(name) if name.text person: name.text pedestrian # 写回新文件避免覆盖原始标注 tree.write(Annotations/000001_fixed.xml, encodingutf-8, xml_declarationTrue, pretty_printTrue)这个脚本的逻辑是iter(object)遍历所有 object 节点find(name)定位类别名改完写新文件。pretty_printTrue是 lxml 特有的格式化参数不传的话输出会挤成几行。注意一点如果 XML 里filename带了目录前缀比如images/000001.jpg直接写入训练配置前要先处理成纯文件名否则会形成双重路径。3. 从原始图像到可训练数据集目录组织与格式转换拿到一万张图和配套 XML 后先别急着训练。多数检测框架对数据集目录有约定尤其是 YOLO 系列目录不对时训练进程看似在跑实际读到的数据是 0。我一般先把数据整理成标准 VOC 布局再决定是否转成其他格式。3.1 标准目录结构JPEGImages、Annotations 与 ImageSets/Main一个能直接被 YOLOv5、YOLOv8 等框架兼容的 VOC 风格目录是这样uav_dataset/ ├── JPEGImages/ # 原始无人机图片jpg 或 png ├── Annotations/ # 同名 .xml 标注文件 └── ImageSets/ └── Main/ ├── train.txt # 训练集文件名不带扩展名 ├── val.txt # 验证集文件名 └── test.txt # 测试集文件名单行一个ImageSets/Main里的 txt 只写文件名不带扩展名每行一个。比如000001而不是000001.jpg。框架拿到这个名字后会分别去JPEGImages找图、去Annotations找标注。这个约定是 PASCAL VOC 时代定下来的后续很多工具都沿用了它。如果目录结构不一致后面转换脚本、训练脚本都要跟着改成本很高。3.2 用 Python 校验 XML 与图像是否一一对应最常见的数据集损坏是有 XML 没图片或者有图片没 XML。前者训练时直接报错后者图片被静默跳过导致实际训练样本比你以为的少。先用脚本扫一遍from pathlib import Path from xml.etree import ElementTree as ET ann_dir Path(Annotations) img_dir Path(JPEGImages) xml_stems {p.stem for p in ann_dir.glob(*.xml)} img_stems {p.stem for p in img_dir.glob(*.jpg)} # 反向有图片但没标注 without_ann img_stems - xml_stems # 正向有标注但图片缺失 without_img [] for xml_file in ann_dir.glob(*.xml): root ET.parse(xml_file).getroot() filename root.findtext(filename) if not (img_dir / filename).exists(): without_img.append((xml_file.name, filename)) print(f没有标注的图片数: {len(without_ann)}) print(f标注对应图片缺失数: {len(without_img)}) for name in list(without_ann)[:10]: print(name) for item in without_img[:10]: print(item)逻辑是先用文件名集合做差集找出缺标注的图片再解析每个 XML 里的filename检查对应图片是否存在。参数说明glob(*.jpg)只匹配 jpg如果你的数据是 png这里要相应改成*.png或用img_dir.iterdir()统一处理。另外如果 XML 里的filename是绝对路径脚本会判定全部缺失需要改成Path(filename).name只取末尾文件名再比对。3.3 把 VOC XML 转成 YOLO TXT脚本、归一化参数与四个边界坑如果用 YOLO 系列训练VOC XML 不能直接喂进去要转成每张图一个 txt 的格式。YOLO TXT 的每行是class_id center_x center_y width height全部归一化到 0 到 1。转换脚本如下import random from pathlib import Path from xml.etree import ElementTree as ET class_names [car, person, truck] # 必须和训练配置一致 def convert_one(xml_path: Path, out_dir: Path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.findtext(width)) img_h int(size.findtext(height)) lines [] for obj in root.findall(object): name obj.findtext(name) if name not in class_names: continue # 类别不在配置里直接跳过 b obj.find(bndbox) xmin float(b.findtext(xmin)) ymin float(b.findtext(ymin)) xmax float(b.findtext(xmax)) ymax float(b.findtext(ymax)) # 坐标越界时先裁剪到图像范围内 xmin max(0.0, min(xmin, img_w)) ymin max(0.0, min(ymin, img_h)) xmax max(0.0, min(xmax, img_w)) ymax max(0.0, min(ymax, img_h)) if xmax xmin or ymax ymin: continue # 空框直接丢弃 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 保留 6 位小数避免小目标精度丢失 lines.append(f{class_names.index(name)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_path out_dir / (xml_path.stem .txt) out_path.write_text(\n.join(lines)) out_dir Path(labels) out_dir.mkdir(exist_okTrue) for xml_path in Path(Annotations).glob(*.xml): convert_one(xml_path, out_dir)逻辑说明class_names的顺序就是训练时的类别编号顺序一旦定下来就不能随意改否则之前转好的 txt 全部要重转。findall(object)只取直接子节点VOC 的标准结构里 object 直接挂在 annotation 下所以这样写没问题。参数说明:.6f控制浮点位数对小目标尤其重要归一化后一个 20 像素的框宽度约 0.01如果只保留 4 位小数数值误差会被放大到几十个像素。这里集中说四个边界坑坐标越界标注框超出图像边界归一化后中心点大于 1训练时 loss 直接爆掉。脚本里我已经做了裁剪但最好还是找出这些样本回标注工具修正。空 XML没有object节点转换后生成空 txt训练时有些框架会报 “empty label”有些会直接忽略。统计后建议过滤掉。类别名拼写不一致Car和car是两类person和person多一个空格也是两类。转换时静默跳过但会少算目标数。小目标浮点溢出归一化后坐标非常接近 0打印出来可能显示成 0.000000实际框还在。保留 6 位小数是底线。提示转换前把 Annotations 完整备份一份改坏了还有后悔药。3.4 划分训练集和验证集按 UAV 场景随机抽样VOC 风格的数据集用train.txt与val.txt划分。最省事的做法是按文件名随机分import random from pathlib import Path random.seed(42) # 固定种子保证每次划分结果一致 anns list(Path(Annotations).glob(*.xml)) random.shuffle(anns) split int(len(anns) * 0.8) train_files anns[:split] val_files anns[split:] out Path(ImageSets/Main) out.mkdir(parentsTrue, exist_okTrue) (out / train.txt).write_text(\n.join(p.stem for p in train_files)) (out / val.txt).write_text(\n.join(p.stem for p in val_files))逻辑是先把所有 XML 文件打乱按 80/20 切分最后把不带扩展名的文件名写入 txt。参数说明random.seed(42)很关键不固定种子的话每次运行划分结果都不同前后实验没法对比。这里有个无人机数据特有的坑无人机照片通常按航线采集同一条航线的相邻帧高度相似。如果只做随机划分相近画面会同时出现在训练集和验证集里验证精度虚高。更稳的做法是先用聚类或按采集时间分组再把组作为最小单位划分。4. 数据质检与常见标注问题排查脚本和几个真实翻车现场带标注的数据集不等于可以直接用。一万张图里混几十张坏标注训练时不会报错但 mAP 就是上不去。这个章节是血泪经验汇总每一条我都按「现象 → 原因 → 解决」写清楚。4.1 没有 object 标签的 XML空标注文件怎么处理“XML 格式文件没有标签怎么办”是搜这个数据集的人问得最多的问题。现象训练能正常跑loss 也在降但验证集 mAP 一直是 0或者某个类别完全检测不到。原因部分 XML 文件里findall(object)返回空列表一张图没有任何目标数据加载器读到了空标签相当于给模型喂了一张没有正确答案的图。解决先统计再处理。from pathlib import Path from xml.etree import ElementTree as ET empty_xml [] for xml_file in Path(Annotations).glob(*.xml): root ET.parse(xml_file).getroot() objs root.findall(object) if not objs: empty_xml.append(xml_file.name) print(f空标注 XML 数量: {len(empty_xml)}) for name in empty_xml[:20]: print(name)逻辑说明findall(object)返回 object 节点列表数量为 0 说明没有目标。参数说明如果 XML 里 object 节点嵌在别的位置可以用root.iter(object)做全树搜索但 VOC 标准结构下findall足够。处理方式看项目阶段分类任务可以直接删掉这些空标注检测任务如果原图确实没有目标保留图但不要进训练列表否则等于强制模型学一个不存在的框。4.2 坐标越界和负值解析时报错的典型原因现象转换 YOLO TXT 时出现center_x 1.28这种明显不合理的值或者训练时报 “AssertionError: bbox … should be in [0, 1]”。原因标注框的 xmax、ymax 大于图片实际宽度或高度常见于标注工具导出时没有裁剪、或者图片被批量缩放后 XML 没同步更新。解决先定位这些坏样本。from pathlib import Path from xml.etree import ElementTree as ET for xml_file in Path(Annotations).glob(*.xml): root ET.parse(xml_file).getroot() size root.find(size) w int(size.findtext(width)) h int(size.findtext(height)) for obj in root.findall(object): b obj.find(bndbox) xmin float(b.findtext(xmin)) ymin float(b.findtext(ymin)) xmax float(b.findtext(xmax)) ymax float(b.findtext(ymax)) if xmin 0 or ymin 0 or xmax w or ymax h: print(f{xml_file.name} {obj.findtext(name)}: f({xmin}, {ymin}, {xmax}, {ymax}))逻辑说明逐个对比坐标和图像尺寸只要有一个越界就打印。参数说明float()转成浮点是必要的XML 里有些标注工具会写出100.0如果直接用int()会丢掉小数点后信息对边缘坐标造成误差。定位后两种处理轻微越界几个像素在转换脚本里裁剪严重越界回到标注工具修复。不要指望模型自己学“框其实在图外”这类样本只会把边界框回归头带偏。4.3 类别分布失衡小目标占比过高时怎么办现象验证集 mAP 看似有 90% 以上实际飞出去漏检大量行人或者对远处车辆基本失灵。原因类别不均衡且无人机视角下目标普遍偏小。解决统计类别和目标尺寸分布再决定要不要调整训练策略。from collections import Counter from pathlib import Path from xml.etree import ElementTree as ET counter Counter() size_ranges {32: 0, 32-96: 0, 96: 0} for xml_file in Path(Annotations).glob(*.xml): root ET.parse(xml_file).getroot() for obj in root.findall(object): counter[obj.findtext(name)] 1 b obj.find(bndbox) box_w float(b.findtext(xmax)) - float(b.findtext(xmin)) box_h float(b.findtext(ymax)) - float(b.findtext(ymin)) max_side max(box_w, box_h) if max_side 32: size_ranges[32] 1 elif max_side 96: size_ranges[32-96] 1 else: size_ranges[96] 1 print(counter.most_common()) print(size_ranges)逻辑说明max(box_w, box_h)是目标的最长边用这个值划分大小区间。参数说明32 和 96 是经验阈值分别对应 COCO 数据集里 small 和 medium 目标的定义边界。统计结果如果小目标占比超过一半直接做法是加大训练图像尺寸比如--img 1280让每个目标在特征图上的面积更大或者把大图切块成 patch 训练让目标占输入图像的比例接近正常检测场景。4.4 可视化抽查框和图像是否真的对齐现象训练集 loss 很低但检测结果整体偏移框落在目标旁边而不是目标上。原因标注框坐标和图不对齐常见于图片经过裁剪/缩放后 XML 没有重新计算或者标注工具使用的坐标系和读取脚本不一致。解决抽几十张图把框画出来肉眼检查。import cv2 from pathlib import Path from xml.etree import ElementTree as ET out_dir Path(check_vis) out_dir.mkdir(exist_okTrue) for xml_file in list(Path(Annotations).glob(*.xml))[:50]: root ET.parse(xml_file).getroot() img_path Path(JPEGImages) / root.findtext(filename) img cv2.imread(str(img_path)) if img is None: continue for obj in root.findall(object): b obj.find(bndbox) xmin int(float(b.findtext(xmin))) ymin int(float(b.findtext(ymin))) xmax int(float(b.findtext(xmax))) ymax int(float(b.findtext(ymax))) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, obj.findtext(name), (xmin, max(0, ymin - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) out_path out_dir / (xml_file.stem _check.jpg) cv2.imwrite(str(out_path), img) print(f已生成 {len(list(out_dir.glob(*.jpg)))} 张可视化检查图到 {out_dir})逻辑说明用 OpenCV 读取原图从 XML 取坐标画矩形和类别名输出到独立目录。参数说明list(...)[:50]是取前 50 个文件抽样数量看你的人力.glob(*.xml)的顺序不一定随机抽样会偏前部稳妥做法是先random.sample再遍历。画框时int(float(...))的写法看着啰嗦但能兼容 XML 里坐标是整数或浮点两种情况。可视化这一步最花时间但最值得我曾靠它一眼发现某段航拍图的 y 坐标整体错位了半张图这种系统性错误只靠数值统计很难察觉。5. 用这个数据集把识别率跑到 95.7%模型选型与训练参数处理好数据后进入训练阶段。95.7% 不是玄学一万张带标注的无人机图片类别少、场景相对单一用 YOLO 系列模型配合合理参数是完全可以触及的。关键是先把指标说清楚再复现。5.1 先把 95.7% 翻译成 mAP0.5指标对齐标题里的“正确识别率”在目标检测落地时通常对应的是 mAP0.5也就是 IoU 阈值取 0.5 时的平均精度。VOC 时代的评测标准一直沿用这个阈值所以看到一个数据集声称 95.7%先确认口径。如果单类别检测mAP 约等于该类的 AP如果多类别mAP 是所有类别 AP 的平均。实际项目里mAP0.5 到 95% 以上并不意味着不会漏检只看这个单一指标会被假象误导。5.2 最小可复现训练命令数据集配置文件与 YOLO 命令训练前先写一个数据配置文件把类别名和训练/验证列表指进去。以 YOLOv5 为例# uav.yaml train: /absolute/path/uav_dataset/ImageSets/Main/train.txt val: /absolute/path/uav_dataset/ImageSets/Main/val.txt nc: 3 names: [car, person, truck]然后启动训练python train.py --data uav.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --patience 20逻辑说明--data指向刚才写的配置文件--weights用预训练权重做迁移学习能显著缩短收敛时间。参数说明--img 640是输入图像短边尺寸无人机目标偏小时建议提到 1280但显存占用会翻几倍--batch 16在 16GB 显存左右可跑--patience 20是早停轮数100 个 epoch 里如果验证集连续 20 轮不涨就自动停。第一次跑不要改太多参数先把官方默认配置跑通拿到一个 baseline 再说。5.3 三个必调参数图像尺寸、batch size 与学习率这三个参数对最终识别率影响最大也是最容易反复试的参数推荐值影响与说明图像尺寸640 起步小目标多时 1280尺寸越大小目标在特征图上越清晰但训练速度下降batch size16 或 32按显存来太小时 BN 统计不稳定loss 波动大学习率0.01 默认数据噪声大时 0.001太高 loss 震荡太低收敛慢图像尺寸要结合数据集里目标大小决定。如果 4.3 节的统计结果显示大量目标最长边小于 32 像素直接上 1280。batch size 不要太极端我试过用 batch 2 硬跑loss 像心电图一样mAP 始终在 80% 附近晃。学习率方面YOLO 默认有 warmup 和余弦退火0.01 起步一般没问题但如果训练集里混了噪声标注建议降到 0.001稳一点。5.4 验证集评估与置信度阈值别被测试集精度骗了训练结束后用验证集重新评估一次指标才可信python val.py --data uav.yaml --weights runs/train/exp/weights/best.pt --img 640 --conf-thres 0.25 --iou-thres 0.5逻辑说明--weights换成训练输出的best.pt不是最后一步的last.pt。参数说明conf-thres是置信度阈值默认 0.25调低到 0.1 会提升召回但增加误检调高到 0.5 则相反。iou-thres 0.5就是 mAP0.5 的 IoU 判定阈值和标题里的 95.7% 口径对齐。评估打印出来的mAP0.5才是你对外宣称识别率时该引用的数字不要在测试集上反复调阈值刷分那是自欺欺人。6. 数据增强和边缘场景验证让 95.7% 在真实飞行中不缩水训练集上 mAP 再高飞出去光线一变、角度一变性能照样缩水。这一章讲两个我常用的补强手段。6.1 增强参数按场景开Mosaic、翻转与 HSV 扰动YOLO 默认把增强参数开得比较激进但无人机场景有自己的特点。我的习惯是先关掉一部分再逐个加增强项建议值场景说明mosaic0.5~1.0小目标占比高时降到 0.5避免目标被拼块切碎degrees10无人机视角稳定超过 30 会让框完全旋转失真flipud0.5航拍没有“上下”概念可以开hsv_h0.015光照变化大才调高否则颜色偏移会误伤小目标hsv_s / hsv_v0.5 / 0.5模拟逆光和阴影但幅度别拉满每改一次增强配置都在验证集上重跑一遍不要凭感觉全开。6.2 把增强后的图拉出来看一遍夜间、逆光和遮挡样本训练启动后YOLO 会在runs/train/exp下生成train_batch0.jpg、val_batch0.jpg这类预览图。我会专门打开它们看增强后的样本里标注框是否还紧贴目标。Mosaic 拼图偶尔会把一个目标切成两半框虽然还在但目标已被截断这种图学不到有效特征。另一个习惯是固定一个“现场测试集”——从原始一万张里挑出夜间、逆光、浓雾、目标被树冠遮挡的画面单独存一个目录每次训完模型都跑一遍。第一次调参时我的夜间场景漏检率高达 30%后来靠这批固定样本把无效改动都挡了回去。养成这个习惯后我再也不会只看 mAP 一个数字就上线希望帮到你。本文还有配套的精品资源点击获取