简介:面向无人机目标检测研究与入门实践的标注数据集资源包,适用于目标检测算法学习、模型训练与验证场景。数据包含1097张无人机图像及对应XML标注文件,标注由LabelImg工具完成,便于直接用于YOLO系列模型的格式转换与训练流程。资源共1894个文件,除JPG图像与XML标注外,还提供了将XML转换为TXT或JSON格式的脚本,可快速生成YOLO所需的训练标签,降低数据预处理门槛。压缩包约96.11MB,文件结构清晰,适合需要高质量小规模无人机数据集的开发者、研究者及计算机视觉初学者。目前已有515人学习使用,能够支撑从数据标注理解到模型训练评估的完整流程。借助这些格式化标签,读者可计算准确率、召回率与mAP等关键指标,系统掌握无人机目标检测的落地方法。
1. 无人机目标检测,说白了就是让模型认出一张图里有没有无人机、飞在哪、框在哪
做目标检测的人看到“无人机检测”第一反应往往是:这跟检测猫狗有什么区别?区别在数据集上——无人机在画面里通常占比小、姿态多变、背景杂乱(天空、树木、建筑物都有),加上类别单一只有“无人机”一种,很多通用目标检测的套路在这里会失灵。这份资源里是1097张已经用LabelImg标注好的无人机图片,标注格式是Pascal VOC标准的XML,并附带把XML转成YOLO所需txt/json格式的脚本。也就是说,你拿到的不是一堆裸图,而是一套可以直接喂给YOLO训练的数据管线。适合谁?准备跑YOLOv5/v8做无人机检测但苦于找不到干净数据的人,或者刚接触目标检测、想用一份真实标注数据走通全流程的新手。接下来我按“数据集结构 → 标注格式解读 → 格式转换 → 踩坑排查 → 训练验证”的顺序把它拆干净。
2. 拆开数据集看细节:XML标注结构、目录组织与LabelImg参数
2.1 目录与文件命名是一切的起点
拿到数据集先别急着训练,第一步是搞清楚目录长什么样。常见做法是主干下分images和Annotations两个目录,前者放JPG图片,后者放同名XML标注文件。这份数据里的图片文件名是pic_766.jpg这种带下划线加数字的格式,对应标注文件就是pic_766.xml。这里有个关键约定:图片和XML必须同名同前缀,YOLO训练时读标签是按图片名去labels目录里找对应txt的,名字对不上直接少一个样本。
目录组织我一般这么排:
datasets/ ├── images/ │ ├── pic_766.jpg │ ├── pic_878.JPG │ └── ... ├── Annotations/ │ ├── pic_766.xml │ ├── pic_878.xml │ └── ... ├── labels/ # 转换脚本生成,存放txt ├── train.txt # 训练集图片路径列表 ├── val.txt # 验证集图片路径列表 └── data.yaml # YOLO数据集配置文件注意文件名大小写。我见过不少数据集里图片是.JPG大写后缀、XML里记录的却是小写.jpg,这在Linux下会直接导致图片加载失败。做数据预处理第一步就是统一后缀名,pic_878.JPG这种建议批量改成小写.jpg,省得后面各种莫名其妙的报错。
2.2 XML标注文件的五个关键字段
用文本编辑器打开任意一个XML,整体结构是固定的,这是LabelImg按Pascal VOC格式输出的标准模板。你需要关注的字段就五个:filename、size/width、size/height、object/name、object/bndbox。bndbox里是xmin、ymin、xmax、ymax四个整数,表示目标框的左上角和右下角像素坐标。
一个典型XML的核心部分长这样:
<annotation> <filename>pic_766.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>uav</name> <bndbox> <xmin>642</xmin> <ymin>351</ymin> <xmax>731</xmax> <ymax>443</ymax> </bndbox> </object> </annotation>这里有个细节容易忽略:一张图里可能有多架无人机,所以会有多个<object>节点,转换时要用循环遍历,不能只取第一个。另外<name>是类别名,这份数据里只有uav一个类,如果后面你想自己标注扩展,命名统一是第一位的要求——别一会儿写uav一会儿写drone,YOLO是按类别名建立编号映射的,名字不一致会拆成两个类。
2.3 LabelImg标注时的参数设置与常见设置
既然原始数据是用LabelImg标注的,你自己扩充数据时也得守着同一套标准。LabelImg是开源免费的图像标注工具,安装后重点设置两个地方:一是默认标注目录指向数据集的Annotations文件夹,二是保存格式选PascalVOC(也就是XML)。软件左下角有PascalVOC和YOLO两种格式切换按钮,一定选PascalVOC,因为YOLO格式直接输出txt,但坐标是归一化后的浮点数,人工标注时不可视、容易标错。
标注操作本身不复杂:打开图片 → 框选目标 → 输入类别名uav→ 保存。但有两件事必须盯紧。第一是框的贴合度:无人机在画面里经常很小,手一抖框就比实际目标大一圈,训练出来的模型定位精度会很难看。我习惯把图像放大到200%再框,宁可多花两秒。第二是漏标:一张图里天上三架无人机,标了两架就保存,这等于给训练集喂了错误标签,模型学出来会把漏掉的那架也当背景。每次保存前扫一遍全图,确认没有遗漏的目标。
有一点值得提:LabelImg自动在XML末尾生成<folder>和<path>字段,这两个字段训练时用不到,转换脚本里直接跳过就行,别让它们干扰解析。
3. 把VOC格式转成YOLO格式:转换脚本与四个参数边界
3.1 XML里存的是什么坐标系
YOLO训练需要的标签格式和XML完全不同。XML存的是像素绝对坐标,YOLO要的是归一化相对坐标。具体来说,txt每一行是五个数字:class_id x_center y_center width height,其中class_id是类别编号(从0开始),后四个值的范围都在0到1之间,是相对于图片宽高的比例。
为什么要归一化?因为YOLO的输入图片尺寸是固定的(比如640x640),原始图可能是1920x1080也可能是800x600,如果标签存绝对像素,模型就必须同时学习“图片尺寸”这个无关变量。归一化之后,无论原图多大,标签都在同一个量纲下,模型只学目标在画面中的相对位置和大小。这也是YOLO能跨分辨率泛化的关键。
中心点和宽高的计算公式是:
x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height w = (xmax - xmin) / width h = (ymax - ymin) / height注意这里width和height是size节点里记录的图片实际像素宽高,不是你训练时的640x640。训练尺寸是YOLO在数据加载阶段做的resize,标签始终相对原始图,模型内部会同步缩放,所以写脚本时别把640写进去,这个错误很隐蔽。
3.2 转换脚本的完整实现
下面这份脚本是常规做法,可以一次性把整个Annotations目录里的所有XML转成YOLO格式的txt。用Python写,依赖xml.etree.ElementTree标准库,不需要额外安装任何包。
import os import xml.etree.ElementTree as ET # 类别映射表:XML里的类别名 -> YOLO类别编号 CLASS_MAPPING = {"uav": 0} def xml_to_txt(xml_path, output_path): """将单个XML标注文件转换为YOLO格式的txt标签文件""" tree = ET.parse(xml_path) root = tree.getroot() # 从size节点读取图片实际宽高 size = root.find("size") img_width = int(size.find("width").text) img_height = int(size.find("height").text) lines = [] # 遍历所有object节点,一张图可能多个目标 for obj in root.iter("object"): name = obj.find("name").text if name not in CLASS_MAPPING: print(f"警告: 未知类别 {name} 在 {xml_path},已跳过") continue class_id = CLASS_MAPPING[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) # 计算归一化后的中心点坐标和宽高 x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height box_width = (xmax - xmin) / img_width box_height = (ymax - ymin) / img_height # 格式: class_id x_center y_center width height lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}") # 写入txt,没有目标时生成空文件 with open(output_path, "w") as f: f.write("\n".join(lines)) def convert_all(annotations_dir, output_dir): """批量转换Annotations目录下所有XML文件""" os.makedirs(output_dir, exist_ok=True) xml_files = [f for f in os.listdir(annotations_dir) if f.endswith(".xml")] for xml_file in xml_files: xml_path = os.path.join(annotations_dir, xml_file) txt_name = os.path.splitext(xml_file)[0] + ".txt" txt_path = os.path.join(output_dir, txt_name) xml_to_txt(xml_path, txt_path) print(f"已转换: {xml_file} -> {txt_name}") if __name__ == "__main__": convert_all("datasets/Annotations", "datasets/labels")脚本逻辑分三层:先解析XML拿到图片宽高,再遍历每个object节点做坐标换算,最后按YOLO格式写txt。核心是坐标换算那四行公式,类型必须用float转成浮点数——如果你用int相除,Python 3里已经不会得到错误的整数结果了,但当xmin + xmax是奇数时,除以2后是.5结尾的浮点数,精度保留6位足够。
参数说明:CLASS_MAPPING是类别名到编号的映射表,这份数据只有uav一类映射到0。如果你扩充了数据加了新类别,按字母序或自定义顺序追加即可,但训练配置里的类别列表必须和这里保持一致。img_width和img_height取自XML的size节点而不是实际打开图片读取,两者应该一致,万一图片被resize过但XML没更新,以XML为准跑出来的训练也能对齐,因为YOLO加载时用的是图片实际尺寸。转换完记得抽样检查几个txt,把数字代回公式验证坐标是否在0到1之间。
3.3 txt内容与目录组织
转换完成后一个txt文件对应一张图片,里面每行是一个目标。比如pic_766.txt里面如果有一行0 0.357552 0.367593 0.046354 0.085185,意思是类别0(uav)、中心点在图像横向35.75%、纵向36.76%的位置,框宽占图幅4.6%、高占8.5%。这类检查看着基础,但能拦下一大批低级错误。
至此数据准备完成。之后在YOLO工程的datasets目录下建images和labels两个子目录,把原始图片和生成的txt按训练验证集分开,再写一个data.yaml指定路径和类别数:
train: datasets/images/train val: datasets/images/val nc: 1 names: ["uav"]nc是类别数量,这份数据是1。names列表里的名字顺序必须和CLASS_MAPPING里的编号一一对应,YOLO训练时直接读这个文件建立类别索引,顺序错了模型训练不会报错,但最后输出的检测框类别名全错。
4. 训练前的避坑排查:五条真实的踩坑记录
4.1 坑一:类别编号从1开始导致mAP直接归零
现象:训练正常跑完,验证时mAP@0.5就是0.0,一个目标都检不出来。模型没爆nan,loss也下降了,就是检测结果完全不对。
原因:手动写txt时习惯性把第一个类别编号写成1(因为人习惯数数从1开始),但YOLO的类别编号从0开始,写入类别0的位置实际存的是class_id=1,模型学到的是“类别1不存在”。数据加载阶段YOLO会过滤掉超出类别范围的标签,等于这批训练样本全部没有监督信息。
解决:检查每个txt首列数字是否都在[0, nc-1]区间内,写一条shell命令awk '{print $1}' labels/*.txt | sort -u看所有类别号,必须只有0。转换脚本里从CLASS_MAPPING取编号而不是直接写死,能从根本上避免这个问题。
4.2 坑二:Windows路径分隔符让训练直接崩
现象:数据集在Windows上转好的,放到Linux服务器训练,报错找不到图片。
原因:转换脚本输出的train.txt里存的是datasets\images\train\pic_766.jpg,反斜杠在Linux下是普通字符而不是路径分隔符。YOLO读列表文件时按行解析,整条路径根本不存在。
解决:所有路径统一用/分隔。转换脚本里用os.path.join生成路径后,最后做一次replace("\\", "/")。或者干脆在Linux上跑一遍数据集重命名脚本,把train.txt里所有反斜杠批量替换成正斜杠。这个坑不涉及模型,但卡住就是完全跑不动。
4.3 坑三:边界框越界导致训练loss变成nan
现象:训练到某个epoch后loss突然变成nan,从头再跑一遍还是在同一个位置炸。
原因:有张图的标注框超出了图像边界,比如xmax等于图片宽度还多了几个像素,xmax - xmin除以宽度后大于1。YOLO对标签坐标有限制,越界数据在计算损失时产生负数或异常值。
解决:转换脚本里加一个越界修正逻辑,把xmin、ymin小于0的钳到0,xmax、ymax大于宽高的钳到宽高。同时打印警告日志,把异常样本挑出来人工复核。这个坑在自动标注或半自动标注产生的数据集里特别常见。
4.4 坑四:空标注文件被当成无目标图
现象:训练结束后发现训练集里大量图片没有参与loss计算,验证集mAP偏低。
原因:某些图片确实没有目标,XML里没有<object>节点,转换后生成了空txt。YOLO按常规会跳过空标签文件,但如果数据加载逻辑把空文件也算一个样本,这张图就以“无目标”的身份参与了训练——模型被反复教“这张图什么都没有”。
解决:转换脚本统计空txt数量,确认是真实无目标还是标注漏了。真实无目标的图建议直接移出训练集,因为单类检测里负样本比例太高会让模型倾向于什么都不检。这个坑的深层教训是:数据质量检查必须在转换阶段做,不能等训练完看指标再回头。
4.5 坑五:图片与XML文件名大小写不一致
现象:训练集图片能加载,验证集图片加载失败,报错FileNotFoundError。
原因:XML里的filename字段记录的是pic_878.JPG(大写后缀),实际目录里文件叫pic_878.jpg(小写后缀)。在大小写敏感的Linux系统上直接找不到文件。Windows上开发时不报错,一上服务器就翻车。
解决:写一个统一命名脚本,把图片后缀全部改成小写,同时同步修改XML里的filename字段。这类体力活必须自动化,手工改1000多个文件不现实。
5. 验证模型能不能用:可视化、mAP计算与数据增强技巧
5.1 训练前先做一轮标签可视化
训练启动之前,我强烈建议先把标签画在图上过目一遍。代码逻辑很简单:读入图片、读入对应txt、把归一化坐标换算回像素、用OpenCV画矩形框。这一步能同时验证格式转换、文件匹配、坐标范围三个环节,全部通过后再进训练,能省掉大量排错时间。我自己做过一次没可视化直接训练的事,结果三天后才发现坐标系的宽高顺序写反了,血泪教训。
验证时重点看两类图:一类是目标贴边的,确认框没有溢出图像边界;一类是小目标密集的,确认每架无人机都被框住了。抽样看30张就够,不需要全部画出来。
5.2 用mAP和PR曲线量化模型
模型训练完后,用验证集跑一遍推理,算mAP(mean Average Precision)。这个指标是目标检测领域公认的“及格线”,mAP@0.5是IoU阈值0.5下的平均精度,mAP@0.5:0.95是IoU从0.5到0.95步长0.05共10个阈值下的平均值。前者看基础检测能力,后者更严格、更考察定位精度。单类检测任务里,AP(Average Precision)就等于mAP,因为只有一种类别,别看到报告里只有AP没有mAP就觉得不对劲——正常现象。
1097张图训练出的模型,在合理超参下mAP@0.5应该能到0.9以上,mAP@0.5:0.95低一点是正常的,毕竟无人机小目标占比高,边界框稍微偏移几个像素,IoU就掉得厉害。如果mAP@0.5连0.8都没到,先优化数据质量而不是调模型结构。
5.3 数据量不够时的增强策略
1097张图对单类检测来说不算充裕,但也不是不能用的量级。常用的增强手段有Mosaic(把4张图拼接成1张训练)、随机旋转、亮度扰动、水平翻转。YOLOv5和v8的配置文件里hsv_h、hsv_s、hsv_v这些超参就是控制颜色增强力度的。对无人机这种可能出现在逆光、晨昏场景的目标,把hsv_v(亮度扰动)从默认的0.4调到0.6,往往能显著提升模型的鲁棒性。但旋转角度要克制,无人机本身有明确的上方视角特征,超过30度的旋转会破坏语义信息,模型可能学到奇怪的东西。
从那以后我每次跑新数据集,都强制自己先走一遍“标注检查 → 格式转换 → 可视化验证 → 小规模试训”的流程,不再直接拿全量数据开跑。这套流程看起来多花半天时间,但能拦住七成以上的低级错误。希望帮到你。
本文还有配套的精品资源,点击获取