简介目标检测学习者、算法工程师与航空视觉研究者可选用这份飞机结构检测数据集其以7281张真实飞机图像为基础提供Pascal VOC与YOLO两种主流格式的标注文件覆盖机头、垂直稳定器、机翼、轮子4类结构总计31455个目标框可支撑YOLO、Faster R-CNN、SSD等目标检测模型的训练、验证与算法调优。压缩包共2000个文件核心为1999个XML标注文件与1个说明文档整体351.37MB配合符合VOC/YOLO规范的目录信息可快速拆分训练集与测试集。目前已有253人学习/下载。标注由labelImg完成四类框数统计明确机翼11752框、轮子9004框、机头7271框、垂直稳定器3428框适合分析类别不均衡、设计采样策略同时双格式一一对应免去自行转换标注的额外工作拿到即可体验完整的目标检测数据预处理流程。1. 目标检测数据集怎么选7280张飞机结构图解决的是什么问题做飞机部件检测时最缺的往往不是模型结构而是带精细部位标注的目标检测数据集。常见开源数据集大多标整架飞机或机场场景很少单独把机头、垂直稳定器、机翼、轮子这四个结构件切出来标框。这个7280张、4类、VOCYOLO双格式的飞机结构检测数据集正好补上这个空档。它解决的是“从零开始标注成本太高、找通用数据集又对不上部件”的尴尬直接解压就能进YOLOv8训练也可以转成其他框架能读的格式。适合两类人一是刚接触目标检测、想用真实场景数据跑通训练和评估的新手二是做航空器外观检查、损伤识别预研的工程师拿它验证算法在细结构上的上限。2. 读取飞机结构数据集VOC与YOLO双格式的目录和标注解析拿到zip压缩包之后我最建议先做两件事检查压缩包能不能完整解压检查标注和图片是不是一一对应。很多翻车不是模型问题而是数据格式没对齐。这个数据集虽然给了双格式但如果目录结构和类别顺序没看明白后面训练阶段会反复返工。2.1 zip解压后的标准目录长什么样先解压再看目录结构unzip aircraft_structure_dataset.zip -d ./aircraft_dataset cd ./aircraft_dataset find . -maxdepth 2 -type d | sortunzip命令的作用是把压缩包解压到指定目录-d参数指定目标路径。find列出两层目录是为了快速看清根目录下VOC和YOLO两套文件是怎么组织的避免直接ls -R刷屏。VOC格式一侧通常有JPEGImages、Annotations、ImageSets/Main分别放原图、xml标注和训练/验证划分文件YOLO格式一侧通常是images、labels和classes.txt。这两套目录里的图片内容是一样的只是标注表达方式不同。不要急着删除其中任何一套。VOC的XML方便人工检查和可视化YOLO的txt方便直接进入训练框架。双格式的价值在于你可以在不同工具链之间切换而不需要重标数据。代价则是两边容易出现不同步比如某张图在JPEGImages里有但labels里没有对应的txt这种情况必须提前查出来。2.2 VOC的XML标注读字段比读代码重要不管当初用什么目标检测常用标注工具导出VOC格式的XML字段基本固定。先看一段示例字段结构和真实数据一致但类别名可能不同annotation folderJPEGImages/folder filenameaircraft_001.jpg/filename sourcedatabaseaircraft_structure/database/source size width1280/width height720/height depth3/depth /size object namenose/name bndbox xmin112/xmin ymin80/ymin xmax220/xmax ymax145/ymax /bndbox /object /annotation这里最需要注意的是name字段。它不一定是英文全称可能是v_stab、wheel或者拼音缩写一切以数据集内的classes.txt为准。我见过有人直接照搬网上的类别顺序去训练结果整批标签错位。第二要注意bndbox的四个值是像素坐标不是归一化坐标x轴向右、y轴向下xmin/ymin是左上角xmax/ymax是右下角。2.3 把VOC转成YOLO归一化坐标转换脚本与四个边界坑既然数据给了YOLO格式很多人会跳过转换这一步。但我会建议你自己跑一遍转换因为转换过程就是一次数据体检。下面是通用脚本import xml.etree.ElementTree as ET from pathlib import Path # 类别顺序必须和数据集内的 classes.txt 保持一致 class_names [nose, vertical_stabilizer, wing, wheel] def convert_voc_xml_to_yolo(xml_file: Path, label_dir: Path): tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) if img_w 0 or img_h 0: print(f[skip] zero size: {xml_file}) return False lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: print(f[warn] unknown class {name} in {xml_file}) continue cls_id class_names.index(name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 归一化到 [0,1]并做边界裁剪 x1 min(max(x1 / img_w, 0.0), 1.0) y1 min(max(y1 / img_h, 0.0), 1.0) x2 min(max(x2 / img_w, 0.0), 1.0) y2 min(max(y2 / img_h, 0.0), 1.0) cx (x1 x2) / 2.0 cy (y1 y2) / 2.0 w x2 - x1 h y2 - y1 if w 0 or h 0: print(f[skip] bad box {name} in {xml_file}) continue lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_path label_dir / f{xml_file.stem}.txt out_path.write_text(\n.join(lines)) return True # 用法示例 xml_dir Path(VOC/Annotations) label_dir Path(YOLO/labels) label_dir.mkdir(exist_okTrue) for xml_file in sorted(xml_dir.glob(*.xml)): convert_voc_xml_to_yolo(xml_file, label_dir)脚本逻辑并不复杂从size读取宽高从每个object里读类别名和框坐标除以宽高得到归一化中心点和宽高最后写入txt。代码里class_names顺序决定txt每一行第一个数字一定不能错。边界裁剪是为了处理某些标注框超出图像宽高的情况不裁剪会让训练时anchor匹配异常甚至loss变NaN。过滤w 0 or h 0是为了防止坐标顺序颠倒产生的负宽高。实际转换中还有四个边界坑需要留意。第一个坑是图像尺寸读出0常见原因是jpg伪装成png或webp头信息不标准建议先把所有图片统一转成RGB的jpg。第二个坑是xmin/ymin和xmax/ymax顺序颠倒导致w或h为负值脚本里过滤了但更建议抽样人工看。第三个坑是类别错位VOC的xml里写的是英文名而YOLO的classes.txt可能是发布者手动乱序的一旦class_names顺序不一致所有框都会张冠李戴。第四个坑是xml和jpg数量对不上有些图片没有xml或xml没有对应图片直接放进训练集训练日志会出现no labels found或者空跑。3. 用YOLOv8训练自己的数据集数据清洗、数据划分和训练命令数据格式确认后下一步就是把它变成YOLOv8能吃的结构。这个环节看似简单但大多数失败发生在训练启动之前。下面按我习惯的顺序来四步做完再敲训练命令。3.1 数据卫生检查损坏图片、重复图片和缺失标注怎么查先写一个快速体检脚本from PIL import Image from pathlib import Path from collections import defaultdict import hashlib img_dir Path(YOLO/images) label_dir Path(YOLO/labels) # 1. 检查能否被PIL正确解码 broken [] for img_path in sorted(img_dir.glob(*.jpg)): try: with Image.open(img_path) as im: im.verify() except Exception: broken.append(str(img_path)) # 2. 用MD5找重复图片 md5_to_paths defaultdict(list) for img_path in sorted(img_dir.glob(*.jpg)): md5 hashlib.md5(img_path.read_bytes()).hexdigest() md5_to_paths[md5].append(str(img_path)) duplicates {md5: paths for md5, paths in md5_to_paths.items() if len(paths) 1} # 3. 对比img和label文件名 imgs {p.stem for p in img_dir.glob(*.jpg)} labels {p.stem for p in label_dir.glob(*.txt)} missing_label imgs - labels orphan_label labels - imgsim.verify()只读图数据块并校验损坏情况不会完整解码所以速度很快。如果这一步有报错训练时大概率会在加载图片时崩。MD5查重复能抓完全相同的复制图这类复制图如果同时出现在训练集和验证集会直接造成指标虚高属于典型的数据泄漏。文件名对比是校验双格式同步的底线操作missing_label和orphan_label都应该为空否则要回到2.3的转换流程补齐或剔除。3.2 按飞机分组划分训练/验证集避免同机重复泄漏这个数据集的图片很可能来自同一架飞机的多个视角。如果随机按单张图划分训练集和验证集里会出现同一架飞机不同角度模型在验证时相当于已经看过同一套纹理mAP会虚高不少。处理办法是先按文件名里的飞机编号分组。常见命名是aircraft_001_side.jpg这类按第一段分组即可import random from pathlib import Path from collections import defaultdict img_dir Path(YOLO/images) groups defaultdict(list) for img_path in sorted(img_dir.glob(*.jpg)): # 按下划线拆分取第一段作为飞机ID按实际命名调整 group_key img_path.stem.split(_)[0] groups[group_key].append(img_path.stem) group_ids list(groups.keys()) random.seed(2024) random.shuffle(group_ids) val_ratio 0.2 num_val_groups max(1, int(len(group_ids) * val_ratio)) val_groups set(group_ids[:num_val_groups]) train_groups set(group_ids[num_val_groups:]) with open(train.txt, w) as ft, open(val.txt, w) as fv: for group in groups: for stem in groups[group]: line fimages/{stem}.jpg\n (fv if group in val_groups else ft).write(line)random.seed(2024)固定随机种子保证每次划分结果一致训练可复现。这里划分粒度是组而不是张就是为了防止同机泄漏。如果数据集命名规则里没有编号可用至少要做感知哈希去重再随机划分否则后续所有指标都不可信。写完train.txt和val.txt后注意检查两个文件里图片数量比例是否接近8:2差距过大说明分组本身不均衡。3.3 写data.yaml并启动训练关键参数按这个基准调YOLOv8使用YAML文件描述数据集位置和类别。配置如下path: /absolute/path/to/aircraft_dataset train: train.txt val: val.txt nc: 4 names: 0: nose 1: vertical_stabilizer 2: wing 3: wheelnames的顺序必须和classes.txt完全一致。如果你打开classes.txt发现顺序是wing, wheel, nose, vertical_stabilizer这里就要改成那个顺序否则训练时第0类会被当成wing推理结果整个错位。接下来启动训练yolo detect train \ dataaircraft.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch16 \ patience50 \ project./runs \ nameaircraft_detmodelyolov8n.pt是选择nano版本的预训练权重参数最小先拿它验证数据链路是否正常。7280张的规模n模型跑到80轮左右mAP50一般能到0.8以上。如果你的机器是V100这类大显存可以把模型换成yolov8s.pt或yolov8m.ptbatch从16加到32但学习率保持默认不要上来就改。patience50表示50轮验证指标没有提升就停止能省时间。第一次跑建议不加任何自定义增强默认配置的结果才是合理基线。3.4 看训练结果mAP50、mAP50-95和混淆矩阵怎么看训练结束后去runs/detect/aircraft_det/目录重点看results.png和confusion_matrix.png。results.png里有train/box_loss、train/cls_loss、val/box_loss、mAP50、mAP50-95几条曲线。判断标准是mAP50到0.7以上说明基础结构检测可用再看逐类AP机头和机翼如果相差超过15个点先不要急着换模型很可能是类别不均衡。confusion_matrix.png的横轴是预测类别纵轴是真实类别对角线越亮越好。注意矩阵的行列和不一定相等这分别代表真实框数和预测框数和模型好坏没有直接关系。真正要警惕的是“垂直稳定器”和“机翼”互相串这种通常不是模型问题而是标注边界不齐回头检查第2章做的转换结果。4. 飞机结构检测实战避坑五个必须记录的现象和处理以下五条是从这个数据集的实际使用场景里总结出来的每条都按现象、原因、解决三步讲。遇到类似问题按这个顺序排查不用把训练流程推倒重来。4.1 训练loss在降但mAP不涨问题多半出在类别不均衡现象训练到第80轮时loss曲线还在下降但mAP50在0.7附近横盘尤其垂直稳定器的AP明显低于其他三类。原因四个类别的样本数差异大。机头和轮子出现频率高垂直稳定器只在特定角度出现模型把大部分学习能力用在了多数类上。解决先统计每个类别的框数如果差异大于5:1不要急着删图对少类做复制粘贴增强。最简单的做法是在训练命令后追加copy_paste0.5让垂直稳定器这类目标被复制到其他图中参与训练。如果效果还不够再考虑按类别加权损失但第一次改进用增强就够了。4.2 zip伪加密导致解压要密码不是数据坏了是文件头标记问题现象在Windows里双击zip文件弹出密码框试什么密码都不对但压缩包里的文件能预览。原因部分数据集发布者打包时使用的工具在zip头部写入了加密标志位文件数据本身没有真正加密这就是伪加密。它不是数据损坏也不是真的有密码纯粹是文件头标记问题。解决换用7-Zip直接解压7-Zip遇到伪加密通常不会要求输密码。如果7-Zip也要密码那说明文件确实加密了只能联系发布者要密码。不要用网上那些“zip密码移除”类工具它们很容易把文件头改坏。解压后用unzip -t校验CRC能通过就放心使用。4.3 轮子一直漏检低对比度小目标不能用默认增强策略现象训练完成后在测试图上推理机头、机翼都框得准轮子在机腹阴影区域几乎框不到偶尔框出来IoU也很差。原因轮子相对图像面积小而且和机身、地面阴影的对比度低。YOLO默认的Mosaic增强把四张图拼在一起小目标在拼接后细节被进一步压缩模型学不到稳定的纹理特征。解决降低Mosaic概率比如mosaic0.3让原图参与训练的比例拉高同时加hsv_v0.4增加亮度扰动让阴影下的轮子有更多亮度变化。如果阴影特别重可以在预处理阶段用CLAHE做局部对比度增强但这个改动比较大先试前两个参数。4.4 验证mAP和loss趋势相反先检查数据划分是否按飞机分组现象训练日志里val/box_loss上升但mAP50在120轮以后还在涨最终测试效果反而不如验证集。原因八成是数据划分泄漏。同一架飞机的多张图同时出现在训练集和验证集模型在验证时看过相似上下文mAP会虚高但loss暴露了真实情况。解决回到3.2确认同一飞机编号的所有图片都落在同一个集合里。如果文件名看不到编号用感知哈希聚类后再划分。这一步费时间但它是指标可信的前提。4.5 机头被标成机翼类别映射错位的排查现象模型在预测图上把机头区域标成wing轮子标成nose整体类别全部错位。原因VOC的xml里name是nose而YOLO的classes.txt顺序是wing, wheel, nose, vertical_stabilizer训练时class id从0开始每一位都错开了。解决在转换脚本里打印xml中所有出现过的name和classes.txt列表人工比对。然后用可视化脚本把模型预测和真实标签同时画到图上看第0类到底是机头还是机翼。这一步不做模型训完基本白费。5. 让机头、垂直稳定器、机翼、轮子各自收敛尺度分析与调参实战四类部件在尺度、纹理和光照条件下差异很大一套默认参数很难让它们同时收敛。我一般会从尺度统计入手再调整增强策略必要时用切片推理补召回。5.1 统计四类目标的面积分布先知道哪些是小目标把YOLO格式的txt读出来统计每类标注框的面积占比import pandas as pd from pathlib import Path label_dir Path(YOLO/labels) rows [] for txt_path in sorted(label_dir.glob(*.txt)): img_name txt_path.stem for line in txt_path.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: continue cls_id int(parts[0]) w float(parts[3]) h float(parts[4]) rows.append({img: img_name, cls: cls_id, area: w * h, aspect: w / max(h, 1e-6)}) df pd.DataFrame(rows) print(df.groupby(cls)[area].describe())这里的w和h是归一化比例所以area代表标注框占整张图面积的比例。打印结果后通常能看到机头和轮子的中位数面积只有机翼的1/10到1/20垂直稳定器则明显细长长宽比可能是1:5以上。这个分布直接决定数据增强策略小目标多的类别不能靠大尺度Mosaic去增强否则会变得更难学。5.2 增强策略不能一套打天下Mosaic、HSV和复制的组合根据上面的尺度分布重新组合增强参数yolo detect train \ dataaircraft.yaml \ modelyolov8s.pt \ epochs200 imgsz640 batch16 \ mosaic0.3 \ hsv_h0.015 hsv_s0.4 hsv_v0.5 \ copy_paste0.4 \ scale0.5 \ fliplr0.5mosaic0.3是降低拼接概率防止小目标在拼接图中被压缩太多。copy_paste0.4对少类有明显帮助但贴图位置要合理不能把轮子贴到机身上方。scale0.5表示允许缩放范围在0.5到1.5倍之间给模型更多尺度变化。fliplr0.5水平翻转在飞机结构检测里可以用因为飞机左右对称但如果后续要检测机头朝向翻转会改变语义那时要关掉它。5.3 大图上用小目标切片推理补齐召回率如果训练时imgsz640实际推理输入图是4K分辨率模型等于把目标缩得很小去检测漏检很正常。常见做法是用切片推理把大图切成640的小块重叠区域用NMS合并。python -m sahi predict \ --model_type yolov8 \ --model_path runs/detect/aircraft_det/weights/best.pt \ --source ./test_big \ --slice_width 640 \ --slice_height 640 \ --overlap_ratio 0.2 \ --postprocess_type NMS \ --project ./sahi_outslice_width和slice_height建议和训练尺寸一致或略小。切块太大会把目标截断太小则小目标跨块严重重叠率overlap_ratio0.2能保证中间目标至少有一部分落在相邻块的重叠区。切片推理的代价是速度下降但召回率改善明显尤其适合机头和轮子这类小目标检测。5.4 损失函数和超参调整按观察到的故障模式再动手不要一开始就换损失函数。YOLO系列默认的box_loss和cls_loss权重在多数场景够用先看训练曲线如果cls_loss偏高说明分类边界不清可以适度调高cls权重如果box_loss抖得厉害说明框回归不稳定先确认标注坐标没有超界。飞机结构部件之间视觉差异大但类别数量少真正的问题通常不是分类难而是小目标召回所以优先做增强和切片推理而不是换损失。如果确实要做对比实验就固定其他参数只改某一项跑完用mAP50-95和难例集结果一起对比不要只看一个数。6. 验证与部署用“难例集”代替反复刷mAP6.1 把“难例集”写进验收流程训练完成不要只报mAP。从测试图或现场拍照中挑20到30张难例包含侧光、阴影、多机叠加、远距离小目标、轻微模糊等。固定跑一次预测把每张结果保存下来。看两个点一是有没有把垂直稳定器漏成机翼二是轮子框是否贴合轮毂边缘。定性检查比单看数字更能暴露数据问题。6.2 部署前再验证一次TTA开关用难例集跑一次带TTA的预测yolo predict modelbest.pt source./hard_set augmentTrue saveTrueaugmentTrue会在推理时做多尺度翻转能小幅提升召回率但速度明显下降。如果开启TTA后很多漏检被找回来说明训练时的小目标增强还不够回到第5章调数据而不是依赖TTA撑场子。部署阶段记得把augment关掉保持可重复和可接受的推理延迟。我早先做结构件检测时只盯着mAP50结果一到傍晚光线下的实拍图就漏轮子。后来把“先跑难例集再谈mAP”作为固定习惯模型迭代才回到正轨。希望帮到你。本文还有配套的精品资源点击获取