拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

飞机数据集7931张VOC+YOLO双格式:目标检测训练与避坑指南

飞机数据集7931张VOC+YOLO双格式:目标检测训练与避坑指南

简介:本资源为面向目标检测初学者与算法工程师的飞机单类数据集,采用Pascal VOC与YOLO双格式标注,可直接用于训练与验证飞机检测模型,适合课程设计、算法复现及小样本实验等场景。压缩包共2000个文件,以1999个xml标注文件和1个说明txt为主,整体约107.37MB,图片与标注一一对应,省去格式转换的繁琐步骤。数据集包含7931张jpg图片,对应7931个VOC格式xml与7931个YOLO格式txt文件,标注类别仅airplane一类,共23568个矩形框,全部由labelImg工具人工绘制,标注准确合理。目前已有201人学习下载,读者可快速获得一份结构清晰、开箱即用的单类检测数据,用于模型训练、精度对比与数据增强实验,也可作为自建数据集的标注参考范例。

1. 飞机数据集落地:7931 张图、23568 个框,VOC 与 YOLO 双格式到底怎么用

手上有个目标检测项目,类别就一个:airplane。找了一圈公开数据,要么类别杂、要么标注质量参差,最后翻到这份飞机数据集——7931 张 jpg,配 7931 个 VOC 格式 xml 和 7931 个 YOLO 格式 txt,单类别 airplane,总框数 23568,标注工具是 labelImg,规则就是画矩形框。这个量级对单类别检测来说够用了,平均每张图约 2.97 个框,说明不少图里有多架飞机,不是那种一张图一个目标的“玩具集”。

它解决的核心问题是:你不用自己从零标数据,也不用写脚本在 VOC 和 YOLO 之间来回转。压缩包里两套标注同时给齐,xml 给需要解析原始标注、做统计分析或转其他格式的人,txt 给直接喂 YOLO 系列训练的人。适合谁?做遥感飞机检测、机场场面监控、航拍目标识别的从业者,以及想拿一个干净单类别集跑通 YOLOv8/v11 训练流程的新手。下面按“先看清结构 → 再动手转换和训练 → 最后避坑”的顺序拆。

2. 拆包先看目录:VOC 与 YOLO 双格式的文件对应关系

2.1 压缩包里到底有什么

从项目正文的文件列表能看到,xml 命名是xyxr_plane_数字.xml这种形式,比如xyxr_plane_7336.xml、xyxr_plane_6759.xml、xyxr_plane_362.xml。数字部分就是图片的唯一标识,对应的 jpg 和 txt 应该同名。这是很典型的“一套图、两套标注”组织方式。先别急着训练,第一步是把压缩包解开,确认三件事:jpg 数量、xml 数量、txt 数量是否都是 7931,以及三者文件名(去掉扩展名)能不能一一对上。

# 解压后进入目录,分别统计三类文件数量 unzip "【目标检测数据集】飞机数据集7930张VOC+YOLO格式.zip" -d plane_dataset cd plane_dataset echo "jpg 数量:"; ls *.jpg 2>/dev/null | wc -l echo "xml 数量:"; ls *.xml 2>/dev/null | wc -l echo "txt 数量:"; ls *.txt 2>/dev/null | wc -l # 找出只有图片没有标注的“孤儿文件” for f in *.jpg; do base="${f%.jpg}" [ -f "$base.xml" ] || echo "缺 xml: $base" [ -f "$base.txt" ] || echo "缺 txt: $base" done

逻辑说明:ls | wc -l是最快的计数方式,比打开文件管理器数靠谱。第三个循环是血泪经验——数据集号称 7931 对,实际交付时经常有个别图片漏标或标注文件丢失,训练前不查,训练时 dataloader 报错或者静默跳过,你都不知道少训了多少张。参数上没什么可调的,就是把*.jpg换成你实际的扩展名(有些集是.jpeg或.png)。

2.2 VOC xml 里存了什么

VOC 格式的 xml 是“人类可读”的标注,打开一个看结构:

<annotation> <folder>plane</folder> <filename>xyxr_plane_7336.jpg</filename> <size> <width>1024</width> <height>768</height> <depth>3</depth> </size> <object> <name>airplane</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>212</xmin> <ymin>98</ymin> <xmax>540</xmax> <ymax>330</ymax> </bndbox> </object> </annotation>

关键字段:size里的宽高是原图尺寸,bndbox是左上角和右下角的绝对像素坐标。name全是airplane,单类别所以不用做类别映射表。truncated和difficult这两个标志位很多转换脚本会忽略,但如果你后面要做困难样本挖掘或者评估时排除截断目标,就得把它们读出来。这份集标注规则是“对类别画矩形框”,没有多边形、没有分割路径,所以 xml 里不会出现segmentation节点,转换时也不用处理掩码。

2.3 YOLO txt 的归一化坐标

YOLO 格式的 txt 每行一个目标,格式是class_id x_center y_center width height,全部归一化到 0~1。单类别时 class_id 恒为 0。拿上面那个框举例,假设图是 1024×768:

0 0.3672 0.2786 0.3203 0.3021

计算方式:x_center = (212+540)/2/1024 ≈ 0.3672,y_center = (98+330)/2/768 ≈ 0.2786,width = (540-212)/1024 ≈ 0.3203,height = (330-98)/768 ≈ 0.3021。这份集已经帮你转好了 txt,所以正常情况直接用就行。但如果你要自己校验或重转,就得注意归一化用的是每张图各自的宽高,不是统一尺寸——这是新手最容易翻车的地方,后面避坑章会细说。

3. 从 VOC 到 YOLO:转换脚本、目录划分与 data.yaml 配置

3.1 自己写一遍转换脚本(校验用)

虽然集里给了 txt,但强烈建议自己写一遍转换脚本做交叉校验,确认给的 txt 和 xml 一致。常见做法是用xml.etree.ElementTree解析,逐框算归一化坐标:

import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, img_w, img_h, class_map): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): name = obj.find('name').text.strip() if name not in class_map: continue bnd = obj.find('bndbox') xmin = float(bnd.find('xmin').text) ymin = float(bnd.find('ymin').text) xmax = float(bnd.find('xmax').text) ymax = float(bnd.find('ymax').text) # 归一化,注意用每张图自己的宽高 xc = (xmin + xmax) / 2.0 / img_w yc = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 裁剪到 [0,1],防止标注越界 xc, yc = min(max(xc, 0), 1), min(max(yc, 0), 1) w, h = min(max(w, 0), 1), min(max(h, 0), 1) lines.append(f"{class_map[name]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}") return lines class_map = {"airplane": 0}

逻辑说明:class_map把类别名映射成 id,单类别就是{"airplane": 0}。归一化前先取size里的宽高,不要用固定值。最后那两行裁剪是后悔药——有些标注框会超出图片边界(xmax 大于 width),不裁的话 YOLO 训练时可能报坐标越界或产生异常梯度。参数上:.6f保留六位小数足够,YOLO 官方也是这个精度。

3.2 划分 train/val 并生成 data.yaml

YOLO 训练需要按images/train、images/val、labels/train、labels/val组织。7931 张按 8:2 分,验证集约 1586 张:

import os, random, shutil random.seed(42) # 固定种子,保证可复现 src_img, src_lbl = "plane_dataset", "plane_dataset" dst = "plane_yolo" for sub in ["images/train","images/val","labels/train","labels/val"]: os.makedirs(os.path.join(dst, sub), exist_ok=True) files = [f[:-4] for f in os.listdir(src_img) if f.endswith(".jpg")] random.shuffle(files) split = int(len(files) * 0.8) for i, base in enumerate(files): phase = "train" if i < split else "val" shutil.copy(f"{src_img}/{base}.jpg", f"{dst}/images/{phase}/{base}.jpg") shutil.copy(f"{src_lbl}/{base}.txt", f"{dst}/labels/{phase}/{base}.txt")

逻辑说明:random.seed(42)是必须的,否则每次划分不同,实验没法对比。split取 80% 做训练。复制而不是移动,保留原始文件,方便后面重新划分。对应的data.yaml:

path: ./plane_yolo train: images/train val: images/val nc: 1 names: 0: airplane

nc是类别数,单类别填 1;names用字典或列表都行,但 id 必须从 0 开始且和 txt 里的 class_id 对上。这份集只有 airplane 一类,所以nc: 1,别写成 0。

3.3 起训命令与关键参数

yolo detect train \ data=plane_yolo/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ workers=4 \ project=runs/plane \ name=exp1

逻辑说明:model用预训练权重起步,单类别小数据从零训容易不收敛。imgsz=640是通用起点,如果原图分辨率普遍在 1024 以上且飞机目标偏小,可以提到 960 或 1280,但显存和速度要权衡。batch按显存调,16 是 8G 显存的稳妥值。workers是 dataloader 进程数,Windows 下设太高容易卡死,4 比较稳。训练完看runs/plane/exp1下的results.csv和confusion_matrix.png,单类别重点看 mAP50 和召回,别只盯 loss。

4. 避坑与排查:标注越界、类别错位、划分泄漏这几件事

4.1 现象:训练报 “normalized coordinates outside [0,1]”

原因:xml 里存在 xmax 大于图片宽度、或 ymax 大于高度的框,转换时没裁剪,归一化后超过 1。解决:在转换脚本里对 xc/yc/w/h 做min(max(v,0),1)裁剪,或者直接过滤掉越界比例过大的框(比如宽高超过 1.5 的)。我一般会先跑一遍统计,把越界框数量打出来,超过几十个就说明标注源头有问题,得回看原图。

4.2 现象:训练不报错但 mAP 一直很低

原因:类别 id 错位。比如 data.yaml 里names: {1: airplane},但 txt 里写的是 0,模型学的是“背景”。解决:确认nc和names的 id 从 0 开始,且和 txt 第一列完全一致。单类别集里这个错特别隐蔽,因为只有一个类,错了也不报错,就是学不动。

4.3 现象:验证集指标虚高,实际推理一塌糊涂

原因:划分时同一张图的不同副本或高度相似帧被分到了 train 和 val,造成数据泄漏。解决:如果图片来自视频抽帧,按视频段划分而不是随机按图划分;随机划分时至少固定种子并检查 train/val 里有没有文件名高度相似的。这份集是独立图片,随机划分问题不大,但如果你自己扩充了数据,这条要盯紧。

4.4 现象:dataloader 报找不到 label 文件

原因:图片和 txt 文件名大小写不一致,或者扩展名不统一(.JPGvs.jpg)。解决:用 2.1 的孤儿文件检查脚本先扫一遍,统一重命名。Linux 下大小写敏感,Windows 下不敏感,跨平台迁移时这个坑必踩。

4.5 现象:显存够但训练速度异常慢

原因:workers设太大导致进程争抢,或者图片分辨率远大于imgsz且没开缓存。解决:先把workers降到 2~4 试,再考虑cache=ram(内存够的话)或cache=disk。另外确认图片不是超大尺寸,必要时预处理缩放到接近imgsz再存一份。

5. 进阶技巧:用 23568 个框做分布分析,反推标注质量与训练策略

拿到一个数据集,别急着开训,先花十分钟做框分布分析,能提前发现很多问题。这份集总框数 23568,图 7931 张,平均 2.97 框/图,但平均值会骗人——得看分布。下面这段脚本统计每张图的框数、框的宽高比和面积占比:

import os, glob import numpy as np box_per_img, ratios, areas = [], [], [] for txt in glob.glob("plane_dataset/*.txt"): with open(txt) as f: lines = [l for l in f if l.strip()] box_per_img.append(len(lines)) for l in lines: _, xc, yc, w, h = map(float, l.split()) ratios.append(w / h if h > 0 else 0) areas.append(w * h) box_per_img = np.array(box_per_img) ratios = np.array(ratios) areas = np.array(areas) print(f"总框数: {len(ratios)}") print(f"每图框数: min={box_per_img.min()} max={box_per_img.max()} " f"mean={box_per_img.mean():.2f} 中位数={np.median(box_per_img)}") print(f"宽高比: 中位数={np.median(ratios):.2f} " f"5%={np.percentile(ratios,5):.2f} 95%={np.percentile(ratios,95):.2f}") print(f"面积占比: 中位数={np.median(areas):.4f} " f"小于0.01的比例={np.mean(areas<0.01):.2%}")

逻辑说明:box_per_img看每图目标数,如果中位数远小于均值,说明少数图目标极多、多数图目标很少,训练时 batch 内正样本不均衡。ratios看飞机框的宽高比,飞机在不同视角下比例差异大,如果 5% 和 95% 分位跨度很宽,说明视角多样,anchor 或模型要能覆盖。areas看小目标占比,areas<0.01就是面积不到图 1% 的框,如果比例超过 30%,imgsz=640可能不够,得往上提。

我一般会拿这几个数做决策:小目标多就提分辨率或加 P2 检测层;每图框数中位数低就适当增大 batch 里的图数保证正样本量;宽高比跨度大就确认用的是 anchor-free 头(YOLOv8 之后默认是)。这套分析跑下来,比盲目调参有用得多。

从那以后我每次拿到新数据集,都强制先跑一遍数量核对、孤儿文件检查和框分布统计,再动训练脚本。这三步花不了十五分钟,但能省掉后面几小时的玄学排查。希望帮到你。

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

返回列表