简介:一套基于PASCAL VOC 2012定制筛选的专用飞机检测数据集,只保留aeroplane这一目标类别,面向需要训练YOLO等实时目标检测模型的开发者和算法学习者,可用于航空监控、无人机巡检、机场安检等场景中的飞机识别定位。整个压缩包共2149个文件,含716张jpg原图、717个txt标签和716个xml标签,总大小56.85MB;txt标签记录边界框坐标,xml标签补充类别与更详细的区域注解,图像与标签一一对应,解压后可直接配合Darknet等YOLO训练框架使用。已有1256人学习下载。数据集覆盖不同角度、光照和遮挡条件的真实图像,训练时可自行划分训练集与验证集,也能在此基础上做数据增强和超参数调优,帮助快速上手YOLO数据组织方式与目标检测流程。
1. 先看清这份 aeroplane_VOCtrainval2012.zip 到底值不值的下
YOLO飞机检测数据集 aeroplane_VOCtrainval2012.zip 不是那种「又大又全」的通用目标检测包,它是从 PASCAL VOC 2012 的 trainval 里专门筛出来的单类子集,一共 716 张带飞机的图片,外加每张图对应的 txt 和 xml 两种标签文件。对刚入门 YOLO 或者想快速跑通一个完整训练流程的人来说,这份资源的价值在于省掉你从二十个类别的 VOC 全集里挑图、筛框的时间——解压、划分、配好 data.yaml,就能直接进训练。它的适用场景很聚焦:单类飞机检测的训练与验证,适合正在学 YOLO 结构、想看清 label 格式怎么影响 loss,或者需要一份干净的航空器数据集做预实验的从业者。不适合想要很多类别的通用检测模型、也不适合要海量数据跑大模型的人。
2. 标签格式拆解:txt 和 xml 里各写了什么
这份数据集里每张 jpg 都有两个同名的标签文件。你在解压后看到的典型文件结构是这样的:
aeroplane_VOCtrainval2012/ ├── 2010_001426.jpg ├── 2010_001426.txt ├── 2010_001426.xml ├── 2008_008086.jpg ├── 2008_008086.txt ├── 2008_008086.xml ...图片是原始照片,txt 是给 YOLO 训练直接用的归一化坐标,xml 是 PASCAL VOC 原始标注。两个格式记录的是同一个框,但表达方式完全不同,转换不当会在训练时埋下大坑。
2.1 txt 标签格式:一行一个目标
打开任意一个 txt 文件,每一行对应一张图中的一个飞机实例,格式是五个数字:
0 0.431603 0.288750 0.346690 0.050729第一个数字是类别 ID,后面四个是归一化后的边界框参数。YOLO 系列从 v3 到 v8 都用这种格式,它的定义如下:
| 参数 | 含义 | 取值范围 |
|---|---|---|
| class | 目标类别 ID | 非负整数,本数据集只有 0 |
| x_center | 边界框中心点的横坐标,除以图片宽度 | 0 到 1 |
| y_center | 边界框中心点的纵坐标,除以图片高度 | 0 到 1 |
| width | 边界框宽度,除以图片宽度 | 0 到 1 |
| height | 边界框高度,除以图片高度 | 0 到 1 |
注意这里的坐标全部是归一化后的比例值,不是像素坐标。YOLO 在训练时会先对图片做 resize,再用这些归一化坐标在特征图的各个尺度上做匹配,所以用比例值能让标注在不同分辨率的输入图片上保持语义一致。如果你看到某个 txt 里出现负数、大于 1 的值或宽高为 0,那这份标签基本可以判死刑,训练时轻则 loss 不降,重则直接崩。
2.2 xml 标签格式:PASCAL VOC 的原始标注长什么样
xml 文件是 VOC 原始的标注格式,记录的信息比 txt 多很多。以 2010_001426.xml 为例,核心内容是这样的:
<annotation> <filename>2010_001426.jpg</filename> <size> <width>800</width> <height>600</height> <depth>3</depth> </size> <object> <name>aeroplane</name> <pose>Left</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>345</xmin> <ymin>173</ymin> <xmax>622</xmax> <ymax>203</ymax> </bndbox> </object> </annotation>xml 里的 bndbox 给出的是绝对像素坐标,同时附带目标类别名、是否截断、是否难例等额外信息。YOLO 训练时不会直接用 xml,而是需要先把 bndbox 转成 2.1 里的归一化五元组。常见的做法是写一个 Python 转换脚本,读 xml、算中心点坐标、再做归一化写入 txt。
# xml_to_yolo.py import xml.etree.ElementTree as ET import os def xml_to_txt(xml_path, txt_path): # 解析PASCAL VOC格式的xml tree = ET.parse(xml_path) root = tree.getroot() # 读取图片尺寸,供坐标归一化使用 size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) lines = [] for obj in root.iter('object'): name = obj.find('name').text # 在本数据集中只保留 aeroplane 类别 if name != 'aeroplane': continue # 关键点:单类数据集的类别ID固定写0 class_id = 0 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 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 组装成YOLO格式的txt行 lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(txt_path, 'w') as f: f.write('\n'.join(lines)) # 批量转换 xml_dir = 'aeroplane_VOCtrainval2012' for f in os.listdir(xml_dir): if f.endswith('.xml'): xml_path = os.path.join(xml_dir, f) txt_path = os.path.join(xml_dir, f.replace('.xml', '.txt')) xml_to_txt(xml_path, txt_path)这段脚本里有几处需要说明。class_id = 0 是强制写死的,不是从 xml 里读的,因为 aeroplane 在 PASCAL VOC 全集类别顺序里排第二类(person 是第 0 类,aeroplane 是第 1 类),但这份数据集只保留飞机一个类别,data.yaml 里只有一个名字叫 aeroplane 的类,所以 YOLO 的 class 索引必须从 0 开始。x_center 和 y_center 是中心像素坐标除以图片宽高,全部变成 0~1 区间的小数。归一化时如果你图片尺寸读错了,比如把 height 当成 width,生成的框就会被拉成竖条或扁条,训练时 loss 虽然能降但检测框完全不对。
2.3 图片分辨率与场景特征:直接影响训练参数
VOC2012 的图片不是统一尺寸的,有的 800×600,有的 1024×768。训练时模型会把输入 resize 成一个固定正方形,通常是 640×640。图片里的飞机有停在停机坪上的、有低空飞行的、有远处带仰角的,部分图里目标很小,只有几十个像素。
# 统计图片尺寸分布 python -c " from PIL import Image import os sizes = set() for f in os.listdir('aeroplane_VOCtrainval2012'): if f.endswith('.jpg'): img = Image.open(os.path.join('aeroplane_VOCtrainval2012', f)) sizes.add(img.size) print(sorted(sizes))看输出你会得到一张尺寸列表,比如 [(500, 375), (800, 600), (1024, 768)]。这意味着 imgsz 参数不能设太小,否则小尺寸原图被压缩后飞机细节全丢。分辨率跨度大也说明训练时的数据增强里应该打开 mosaic 和 random_perspective,让模型对不同缩放尺度更鲁棒。
3. 整理目录结构:数据划分与 data.yaml 配置
拿到解压后的图片和标签,不能直接丢给 YOLO 训练。Ultralytics YOLO 默认的目录结构要求图片和标签分开放,并且训练集、验证集要单独建目录。这一章直接给出可复制的目录搭建脚本。
3.1 为什么要按 images 和 labels 分目录
YOLO 训练时通过图片路径推断标签路径,默认规则是找同目录下的同名 txt 文件,但如果图片和标签混在一起,文件多了之后很容易错配。标准的目录结构是:
datasets/ └── aeroplane/ ├── images/ │ ├── train/ │ │ ├── 2010_001426.jpg │ │ └── ... │ └── val/ │ ├── 2008_008086.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 2010_001426.txt │ │ └── ... │ └── val/ │ ├── 2008_008086.txt │ └── ... └── data.yaml图片在 images/train/xxx.jpg,标签在 labels/train/xxx.txt,两者文件名完全相同,只有扩展名不一样。validation 同理。这样设计的好处是你只需要在 data.yaml 里分别指定 train 和 val 的图片目录,YOLO 会自动去找对应的 labels 目录。
3.2 数据划分脚本:随机分配加完整性校验
我一般不会手动拖文件,而是写一个划分脚本,一次性完成创建目录、打乱、拷贝、校验四件事。
# split_data.py import os import random import shutil src_dir = 'aeroplane_VOCtrainval2012' dst_dir = 'datasets/aeroplane' train_ratio = 0.8 random.seed(42) # 收集所有图片文件 images = [f for f in os.listdir(src_dir) if f.endswith('.jpg')] random.shuffle(images) split_idx = int(len(images) * train_ratio) train_files = images[:split_idx] val_files = images[split_idx:] # 创建目标目录 for split in ['train', 'val']: os.makedirs(os.path.join(dst_dir, 'images', split), exist_ok=True) os.makedirs(os.path.join(dst_dir, 'labels', split), exist_ok=True) def copy_paired(split_files, split_name): missing_txt = [] for img_name in split_files: base = os.path.splitext(img_name)[0] src_img = os.path.join(src_dir, img_name) src_txt = os.path.join(src_dir, base + '.txt') # 关键校验:标签文件必须存在 if not os.path.exists(src_txt): missing_txt.append(base) continue dst_img = os.path.join(dst_dir, 'images', split_name, img_name) dst_txt = os.path.join(dst_dir, 'labels', split_name, base + '.txt') shutil.copy2(src_img, dst_img) shutil.copy2(src_txt, dst_txt) return missing_txt print('train missing txt:', copy_paired(train_files, 'train')) print('val missing txt:', copy_paired(val_files, 'val')) print('train count:', len(train_files), 'val count:', len(val_files))脚本里最关键的是 copy_paired 函数里的 exists 判断。训练集里如果混入一张没有 txt 的图,YOLO 会跳过它并打印 warning,但如果缺失比例过高,模型能学到的样本变少,直观表现就是 mAP 上不去。random.seed(42) 保证每次运行得到相同划分,便于复现你自己的训练结果。train_ratio 设成 0.8 在 716 张图的总量下大约能得到 572 张训练、144 张验证,对单类任务足够。
3.3 写 data.yaml:路径和类别 ID 别搞错
完成目录划分后,在 datasets/aeroplane/ 下新建 data.yaml:
# data.yaml path: /absolute/path/to/datasets/aeroplane train: images/train val: images/val names: 0: aeroplanepath 建议写绝对路径,因为 YOLO 在运行时会以当前工作目录为基准解析相对路径,如果你在项目根目录跑训练,写相对路径很容易踩坑。names 的索引必须和 txt 里的第一个数字对应,这份数据集里全是 0,所以 names 只有 0 这一个键。类别名 aeroplane 仅用于显示,不影响训练效果,但你将来想改成 airplane 也完全可以,只要保持索引 0 不变。
4. 用 YOLOv8 训练飞机检测模型
目录准备好了,接下来进入训练环节。这一章讲模型选型、训练命令和训练日志的判读方法,按这套参数配置可以直接跑通,不需要额外调参。
4.1 模型选型:为什么从 yolov8n 开始
716 张图属于小规模数据集,模型选太大容易过拟合,选太小的又怕精度不够用。Ultralytics YOLOv8 系列里 yolov8n 是最轻量的版本,参数量大约 320 万,适合在单卡小显存环境下快速迭代。有人会问怎么不用 yolov5s,其实两者都能做,但 yolo 命令在 v8 里默认集成了更多数据增强策略,对 VOC 这种小数据集更友好。如果你用的也是 ultralytics 框架,首次执行训练命令时会自动下载 yolov8n.pt 预训练权重,不需要手动去网页找。网络条件一般的情况下,可以先把 pt 文件放到项目根目录,命令行指定 model 参数时用本地文件名,避免运行时报下载超时。
| 模型 | 参数量 | 适用场景 |
|---|---|---|
| yolov8n | 约 320 万 | 小数据集、边缘设备、快速验证 |
| yolov8s | 约 1110 万 | 精度要求更高、显存充足 |
| yolov8m | 约 2590 万 | 大模型,本数据集不建议 |
4.2 训练命令与关键参数说明
从项目根目录执行:
yolo detect train \ data=datasets/aeroplane/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ pretrained=True各参数的作用如下表:
| 参数 | 作用 | 建议值 |
|---|---|---|
| data | 指向 data.yaml 的路径 | 绝对路径最稳 |
| model | 模型结构或预训练权重 | 首次用 yolov8n.pt |
| epochs | 训练轮数 | 100 起步,看验证集 mAP 是否继续涨 |
| imgsz | 训练输入分辨率 | 640 兼顾速度和精度 |
| batch | 每批图片数 | 按显存调整,6G 显存建议 8 |
| patience | 早停阈值,验证 mAP 连续多少轮不涨就停止 | 20 |
| pretrained | 是否加载 COCO 预训练权重 | True |
显存不够时的表现是执行到一半报 CUDA out of memory。我一般先跑 batch=8 试一轮,看显存占用率,再决定要不要往上加。patience=20 的意思是验证集 mAP 连续 20 轮没有提升就提前终止,能省不少时间,因为你不知道 100 轮里哪一轮是真正的拐点。
4.3 训练日志怎么看:loss 曲线和 mAP
训练开始时输出面板会长这样:
Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 1/100 2.1G 1.482 1.135 1.208 25 640 2/100 2.1G 1.201 0.9875 1.032 18 640判断训练是否健康的几个观察点。box_loss 是边界框回归损失,持续下降说明模型在学框的位置;cls_loss 是分类损失,单类数据集里它应该降得很快,因为只有两类(有飞机和没飞机),相当于二分类;dfl_loss 是分布焦点损失,负责框的边界精细度,波动正常,但整体趋势得向下。如果 box_loss 在第 20 轮以后还在明显下降,说明 100 轮不够,可以加 epochs。训练结束后在 runs/detect/train/ 下会生成 confusion_matrix.png、results.png 等文件,results.png 里能看到每个 epoch 的 mAP50 和 mAP50-95。VOC 时代的传统指标是 mAP50,即 IoU 阈值取 0.5 的平均精度,评估这份数据集看 mAP50 即可。mAP50-95 是多个 IoU 阈值的平均,数值通常比 mAP50 低不少,不要拿它当主线指标。
5. 避坑指南与常见问题:亲手踩过的五个数据坑
从拿到原始 zip 到训练结束,最容易翻车的不是模型结构,而是数据和标签层面的各种小毛病。以下五个问题是我在实测过程中遇到过的典型场景,按「现象 → 原因 → 解决」的方式记录。
5.1 训练时提示 no labels 或图片标签对不上
现象:训练启动时打印大量 WARNING,或者执行到一半提示 no labels found,loss 一直不降。
原因:解压后的文件名大小写不一致,或者部分图片本身缺标签文件。VOC 原始文件名全是小写 jpg,但有人手动改名时会把扩展名改成大写 JPG,YOLO 匹配标签只认 .txt 不认扩展名大小写关系,找不到同名 txt 就跳过这张图。
解决:在划分脚本里加入 exists 检查(见 3.2 的 copy_paired),把缺失标签的图片单独输出到一个目录自己检查。我后来还养成了个习惯:解压完先跑一个全量检查脚本统计图片数和标签数,数值对得上再往下走。
5.2 class ID 写错导致类别混乱
现象:训练时 loss 下降正常,但验证集 mAP 很低,可视化检测结果发现所有框都偏向图片某一边缘。
原因:aeroplane 在 PASCAL VOC 全局类别列表里是第 1 类(person 是第 0 类),如果转换脚本直接用了 VOC 的全局索引,txt 里的 class 就变成 1,但 data.yaml 里只有一个 0 类,于是所有带 1 的标签在训练时被当成无效框,模型学不到正确信息。
解决:单类数据集的 txt 里 class 一律写 0。写转换脚本时直接硬编码 class_id = 0,不要从 xml 里读取 name 映射,这是最稳妥的做法。
5.3 训练中期 bn 崩溃、loss 变成 NaN
现象:某个 epoch 时 loss 突然变 NaN,或者报 RuntimeError: CUDA error: device-side assert triggered。
原因:标签里有坐标越界的框。比如某个 xml 里 xmax 比 xmin 还小,或者某些手工标注的框宽高算出来是负数、坐标为 1.5 之类的越界值。归一化坐标必须严格落在 0~1 区间内,宽高必须大于 0。
解决:训练前写一个数据校验脚本扫描所有 txt:
# validate_labels.py import os label_dir = 'datasets/aeroplane/labels' bad_files = [] for split in ['train', 'val']: for f in os.listdir(os.path.join(label_dir, split)): path = os.path.join(label_dir, split, f) with open(path) as fh: for line in fh: parts = line.strip().split() if len(parts) != 5: bad_files.append(path) break x_c, y_c, w, h = map(float, parts[1:]) if x_c < 0 or x_c > 1 or y_c < 0 or y_c > 1 or w <= 0 or h <= 0: bad_files.append(path) break print('failed files:', bad_files)运行后如果有输出,就逐个检查对应图片,修正或删除异常框。老版本 ultralytics 在遇到越界标签时不会预处理过滤,直接进 CUDA 算子导致 crash,所以越早扫出来越好。
5.4 验证 mAP 高但实际检测不行
现象:VOC 测试集上 mAP50 很高,但拿手机拍的飞机照片或无人机俯视图来测,完全检测不到。
原因:VOC2012 中的 aeroplane 图片场景集中在中低空视角、目标主体比较完整,训练样本里缺少小目标、遮挡、逆光和密集停机场景。模型学到的特征偏向 VOC 场景。
解决:把 conf 阈值调低,比如 predict 时 conf=0.15,并且单独留一撮业务相关的图片做真实测试集,不能只用 VOC 里的 val 图评估模型生产可用性。数据增强里的 mosaic、copy_paste 能缓解一部分,但治本方法是补充自己场景的数据。
5.5 解压后目录嵌套多了一层
现象:data.yaml 里 path 指向解压目录,结果训练时找不到任何图片。
原因:压缩包在 Windows 上用默认解压工具解压时,经常生成 aeroplane_VOCtrainval2012/aeroplane_VOCtrainval2012/xxx.jpg 这样的嵌套结构。
解决:解压后先养成习惯看一层目录结构,确认 jpg 在最外层。路径配好后再跑yolo detect train,如果还是报图片不存在,打开 data.yaml 用绝对路径重新写一遍 path,不要用相对路径猜。
6. 模型验证与进阶:从跑通到能落地
6.1 对验证结果做一次置信度阈值调优
训练完成后,runs/detect/train/weights/ 下会有 best.pt 和 last.pt。best.pt 是根据验证集 mAP 选出来的最优权重,推理时默认用它:
yolo predict \ model=runs/detect/train/weights/best.pt \ source=datasets/aeroplane/images/val \ conf=0.25 \ iou=0.45conf 是置信度阈值,iou 是 NMS 的 IoU 阈值。conf 设高会漏掉小目标,设低会多出误检框,实际做法是先跑一遍 val 看混淆矩阵,找到「飞机被分成背景」和「背景被分成飞机」的交叉点,再决定把 conf 调到 0.15 还是 0.4。无人机或高空俯视的小飞机通常得调到 0.15 左右才不漏检,iou 同时降到 0.3 防止密集飞机框被合并吞掉。
6.2 导出 ONNX 和复用训练流程
如果要把模型部署到 Jetson 或服务端,导出成 ONNX 比直接跑 PyTorch 模型更快:
yolo export model=runs/detect/train/weights/best.pt format=onnx dynamic=True导出后再用 ONNX Runtime 推理,延迟能压到几十毫秒级。另外这套从 xml 转 txt、划分数据集、写 data.yaml、训练、看混淆矩阵的流程可以完整复用到你自己的单类项目,只要把源数据换成自己的标注格式,class_id 写 0 这一个关键点不变。我第一次拿这个数据集练手时没做标签校验,训练到一半突然 device-side assert triggered,翻遍训练日志才发现是有个 txt 里宽高算成了负数。从那以后我每转一次数据都强制走一遍越界扫描和文件名配对检查,训练前花五分钟,省下排查一整天的时间。希望帮到你。
本文还有配套的精品资源,点击获取