☰
无人机检测数据集从VOC格式入门:构建、转换与避坑指南
2026/10/22 3:57:41 网站建设 项目流程

简介:这份无人机检测数据集面向计算机视觉研究者、目标检测算法开发者及高校相关专业学生,用于训练和评估无人机与鸟类目标的识别与定位模型,适合具备一定深度学习基础、正在做目标检测实验或课程项目的读者。资源遵循PASCAL VOC标准组织,共862个文件,包含427个xml标注文件、427张jpg图像、7个txt列表文件及1个md说明文档,压缩包约29.49MB。其中xml文件记录每张图像中无人机或鸟类的边界框与类别信息,jpg为对应原始图像,txt文件则按训练、验证、测试等用途划分图像集合,便于直接接入常见检测框架。目录结构清晰,涵盖ImageSets与Annotations等模块,可省去自行标注与划分数据集的成本,快速复现或对比检测算法效果。目前已有59人学习关注,适合作为无人机检测方向的入门与验证数据来源。

1. 无人机检测数据集为什么值得从 VOC 格式切入

手里有一批无人机航拍图,想训一个能框出无人机的小模型,第一步往往不是选 YOLO 还是 Faster R-CNN,而是被数据格式卡住。VOC 格式的无人机检测数据集,本质就是一套用 XML 描述每张图里目标类别与边界框的标注体系,配套 JPEGImages、Annotations、ImageSets 三个目录。它解决的是「标注信息怎么被训练框架稳定读进去」这件事,适合刚拿到原始航拍素材、准备做检测基线的人,也适合需要把旧标注迁移到新框架的熟手。很多人一上来就转 YOLO 的 txt,结果类别对不齐、坐标越界,回头返工,血泪经验就是先把 VOC 这套结构吃透再谈转换。

2. VOC 目录结构与无人机标注的对应关系

2.1 三个核心目录各自装什么

VOC 格式的骨架非常固定,拿到一个无人机检测数据集,先看根目录下有没有这三个文件夹:

  • JPEGImages:存放原始图像,命名通常是六位数字加扩展名,比如000123.jpg。无人机航拍图分辨率差异大,常见 1920×1080 或 4000×3000,命名不要带中文和空格,否则后续脚本读路径容易翻车。
  • Annotations:每张图对应一个同名 XML,比如000123.xml。里面记录图像尺寸、目标类别、边界框的xmin/ymin/xmax/ymax。
  • ImageSets/Main:存放划分文件,如train.txt、val.txt、test.txt,每行一个图像 ID,不带扩展名。

还有一个容易被忽略的SegmentationClass和SegmentationObject,做检测用不到,可以没有。判断一个数据集是不是标准 VOC,就看Annotations里的 XML 能否和JPEGImages一一对应。

2.2 一个无人机 XML 标注长什么样

下面是一个典型的无人机标注文件,类别名假设为drone:

<annotation> <folder>drone_voc</folder> <filename>000123.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>drone</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>820</xmin> <ymin>310</ymin> <xmax>960</xmax> <ymax>420</ymax> </bndbox> </object> </annotation>

size里的宽高必须和真实图像一致,否则训练时坐标缩放会错位。truncated表示目标是否被截断,difficult表示是否难以识别,这两个字段在无人机小目标场景里很关键——远距离无人机常常只有十几个像素,标difficult=1能让评估更合理。bndbox四个值必须是整数,且满足xmin < xmax、ymin < ymax,越界框是后续报错的高频来源。

2.3 为什么无人机检测偏爱 VOC 而不是直接上 COCO

COCO 的 json 标注更紧凑,但对小目标密集的无人机场景,VOC 的 XML 可读性更好,改一个框直接编辑文本就行,不用写脚本解析整个 json。另一个现实原因是很多开源无人机检测代码库的默认接口就是 VOC,比如早期的一些航拍检测项目,直接读Annotations目录。选 VOC 不是因为它先进,而是因为它省事、容错高、迁移成本低。常见做法是先用 VOC 跑通基线,再考虑转 COCO 或 YOLO 做加速。

3. 从零构建无人机 VOC 数据集的完整操作

3.1 目录初始化与图像重命名

假设原始素材是一堆命名混乱的航拍图,先统一重命名并建目录:

# 建立 VOC 标准目录 mkdir -p drone_voc/JPEGImages mkdir -p drone_voc/Annotations mkdir -p drone_voc/ImageSets/Main # 批量重命名为六位数字,保留顺序 cd raw_images i=1 for f in *.jpg; do newname=$(printf "%06d.jpg" $i) cp "$f" "../drone_voc/JPEGImages/$newname" i=$((i+1)) done

这段脚本做两件事:建目录、按顺序重命名。printf "%06d"保证 ID 是六位,和 VOC 惯例一致。注意cp而不是mv,保留原始素材做后悔药用。如果图像本身有子目录分类,重命名前先打平,否则 ID 会断号。

3.2 用脚本生成 XML 标注骨架

手工写 XML 不现实,通常用标注工具导出,但如果没有工具,可以用脚本先生成空骨架再填:

import os from xml.etree.ElementTree import Element, SubElement, ElementTree from PIL import Image img_dir = "drone_voc/JPEGImages" ann_dir = "drone_voc/Annotations" for fname in os.listdir(img_dir): if not fname.endswith(".jpg"): continue img_id = os.path.splitext(fname)[0] img_path = os.path.join(img_dir, fname) w, h = Image.open(img_path).size ann = Element("annotation") SubElement(ann, "folder").text = "drone_voc" SubElement(ann, "filename").text = fname size = SubElement(ann, "size") SubElement(size, "width").text = str(w) SubElement(size, "height").text = str(h) SubElement(size, "depth").text = "3" tree = ElementTree(ann) tree.write(os.path.join(ann_dir, img_id + ".xml"), encoding="utf-8", xml_declaration=True)

逻辑说明:遍历JPEGImages,用 PIL 读真实宽高写入size,保证后续坐标缩放不出错。depth固定为 3,灰度图要改成 1。生成的 XML 还没有object节点,需要标注工具或后续脚本补。参数上,encoding="utf-8"和xml_declaration=True是必须的,否则中文路径或特殊字符会解析失败。

3.3 划分 train/val/test 并写入 ImageSets

import os, random img_dir = "drone_voc/JPEGImages" out_dir = "drone_voc/ImageSets/Main" ids = [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")] random.seed(42) random.shuffle(ids) n = len(ids) train = ids[:int(n*0.7)] val = ids[int(n*0.7):int(n*0.85)] test = ids[int(n*0.85):] for name, subset in [("train", train), ("val", val), ("test", test)]: with open(os.path.join(out_dir, name + ".txt"), "w") as f: f.write("\n".join(subset))

random.seed(42)保证每次划分一致,方便复现。比例 7:1.5:1.5 是无人机检测的常见做法,数据量少时可以只分 train/val。写入的是纯 ID,不带扩展名,这是 VOC 的约定,带扩展名会导致部分框架读不到。

3.4 校验数据集完整性

import os from xml.etree import ElementTree as ET img_dir = "drone_voc/JPEGImages" ann_dir = "drone_voc/Annotations" missing, bad_box = [], [] for f in os.listdir(img_dir): img_id = os.path.splitext(f)[0] xml_path = os.path.join(ann_dir, img_id + ".xml") if not os.path.exists(xml_path): missing.append(img_id) continue tree = ET.parse(xml_path) for obj in tree.findall("object"): box = obj.find("bndbox") xmin = int(box.find("xmin").text) xmax = int(box.find("xmax").text) if xmin >= xmax: bad_box.append((img_id, xmin, xmax)) print("缺失标注:", missing) print("异常框:", bad_box)

这个校验脚本是上线训练前的最后一道关。missing说明有图没标注,bad_box说明坐标逻辑错误。实际跑下来,无人机数据集最常见的异常是xmin == xmax,通常是标注时手抖点了一下没拖动。发现后回标注工具修,不要试图在训练脚本里自动修正,那会掩盖数据问题。

4. 无人机 VOC 转 YOLO 格式的脚本与参数

4.1 转换脚本逐行拆解

YOLO 的 txt 格式是class_id cx cy w h,全部归一化到 0~1。转换脚本如下:

import os from xml.etree import ElementTree as ET from PIL import Image classes = ["drone"] # 类别列表,顺序决定 class_id img_dir = "drone_voc/JPEGImages" ann_dir = "drone_voc/Annotations" out_dir = "drone_yolo/labels" os.makedirs(out_dir, exist_ok=True) for f in os.listdir(ann_dir): if not f.endswith(".xml"): continue img_id = os.path.splitext(f)[0] img_path = os.path.join(img_dir, img_id + ".jpg") w, h = Image.open(img_path).size tree = ET.parse(os.path.join(ann_dir, f)) lines = [] for obj in tree.findall("object"): name = obj.find("name").text if name not in classes: continue cls_id = classes.index(name) 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) cx = (xmin + xmax) / 2 / w cy = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, img_id + ".txt"), "w") as out: out.write("\n".join(lines))

关键点:classes列表的顺序就是class_id,一旦定了不要改,改了要重训。归一化用真实图像宽高,不是 XML 里的size,因为有些数据集size字段是错的。保留六位小数足够,再多没意义。如果某张图没有目标,txt 写空文件,不要跳过,YOLO 需要空标签表示负样本。

4.2 类别名不一致时的映射处理

实际数据集里类别名可能是UAV、drone、quadcopter混用,转换前先统一:

name_map = { "UAV": "drone", "quadcopter": "drone", "Drone": "drone" } name = name_map.get(obj.find("name").text, obj.find("name").text)

映射表要显式写出来,不要用大小写转换糊弄,UAV和uav在有些标注里是两类。映射完再进classes.index,否则会抛ValueError。这一步是很多转换脚本翻车的地方,报错信息往往只显示not in list,不告诉你是哪个类别。

4.3 转换后的抽样验证

转完不要直接开训,抽几张图可视化验证:

import cv2 img = cv2.imread("drone_voc/JPEGImages/000123.jpg") h, w = img.shape[:2] with open("drone_yolo/labels/000123.txt") as f: for line in f: cls, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw/2) * w) y1 = int((cy - bh/2) * h) x2 = int((cx + bw/2) * w) y2 = int((cy + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("check.jpg", img)

画出来的框如果和原图目标对不上,优先查归一化用的宽高是不是反了(w/h 写反是经典错误),再查xmin/xmax有没有被交换。抽样至少看 10 张,覆盖有目标和无目标的图。

5. 无人机 VOC 数据集避坑与排查清单

5.1 图像与 XML 数量对不上

现象:训练时报FileNotFoundError,或者评估时 mAP 异常低。原因:JPEGImages里有图但Annotations里没有对应 XML,或者反过来。解决:跑第 3.4 节的校验脚本,把missing列表打出来,要么补标注,要么把无标注图从ImageSets里剔除。注意有些数据集故意留无目标图做负样本,这种图也要有 XML,只是object节点为空。

5.2 边界框坐标越界

现象:转换后的 YOLO 标签出现负数或大于 1 的值。原因:标注时框拖到了图像外面,或者 XML 里的size和真实图像不一致。解决:在转换脚本里加裁剪,xmin = max(0, xmin)、xmax = min(w, xmax),同时打印越界记录。如果越界比例超过 5%,说明标注质量有问题,建议回标注环节重做,不要靠裁剪硬扛。

5.3 类别名大小写与空格

现象:classes.index(name)报ValueError。原因:XML 里写的是drone(带尾空格)或Drone。解决:读出来先name.strip(),再进映射表。尾空格在文本编辑器里看不见,用repr()打印一下就能现形。这个坑我踩过不止一次,后来养成习惯,所有类别名先 strip 再比较。

5.4 ImageSets 里 ID 带扩展名

现象:框架读train.txt时找不到图。原因:写入时带了.jpg。解决:划分脚本里用os.path.splitext(f)[0]去掉扩展名。有些框架容错好,带扩展名也能读,但换一个框架就翻车,统一不带扩展名最稳。

5.5 小目标被过滤

现象:训练后模型对远距离无人机完全没反应。原因:标注时把小于某尺寸的框标成了difficult=1,而训练脚本默认跳过difficult目标。解决:检查 XML 里difficult字段的分布,如果小目标全被标成 difficult,要么改训练配置保留 difficult,要么重新标注。无人机检测的核心难点就是小目标,把小的全过滤掉等于没训。

6. 用 VOC 数据集跑通基线的验证技巧

6.1 先过拟合 10 张图验证流程

拿到整理好的 VOC 数据集,不要一上来就训全量。先挑 10 张图,用极小学习率训 100 个 epoch,看 loss 能不能降到接近 0。这一步是验证数据管道通不通,不是验证模型效果。如果 10 张图都过拟合不了,问题一定在数据格式或路径,不在模型。我一般会把这个步骤固定成习惯,能省掉大量「训了半天发现标签是错的」的时间。

6.2 用 mAP 和召回率定位数据问题

跑完基线后,看两个指标:mAP@0.5 和 recall。如果 mAP 低但 recall 高,说明框的位置不准,回去查归一化;如果 recall 低,说明漏检多,查小目标标注和difficult字段。无人机数据集常见的情况是 recall 尚可但 mAP 上不去,多半是框的宽高比和真实目标差太多,标注时框太松。

6.3 数据增强与 VOC 的配合

VOC 格式本身不限制增强,但增强后要同步更新 XML 里的框。常见做法是在 dataloader 里做在线增强,框跟着变换,不落盘。如果做离线增强,记得每张增强图配一个新 XML,否则框和图对不上。无人机场景里,随机缩放和 mosaic 对小目标帮助明显,但缩放后小于 8 像素的框建议直接丢弃,留着也是噪声。

6.4 一个可复用的检查习惯

每次拿到新数据集,先跑三件事:数量校验、坐标校验、抽样可视化。这三步加起来不到五分钟,但能挡掉八成以上的低级错误。我现在的习惯是,校验脚本和转换脚本放在同一个目录,改完数据先跑校验,再跑转换,最后抽样看一眼。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询