机场飞机小目标检测实战:YOLOv26结合数据增强与P2检测头优化
1. 项目思路与整体方案选型1.1 机场飞机目标检测到底难在哪先说结论机场场景下的飞机检测不是普通目标检测套个预训练模型就能做好的事。你打开任意一张高空俯拍的机场图就会发现飞机目标在整张图里通常只有几十到几百个像素宽尤其当飞机停在远机位、跑道尽头或者低分辨率监控画面里时一不小心就和停机坪上的白色车辆、航站楼屋顶、引导车混成一团。模型要做的不是“看到像飞机的亮色块”而是把飞机的结构特征机翼、机身、尾翼从一堆相似纹理里精确拎出来。这个场景里有几个特别让人头疼的地方。第一目标尺度跨度极大。同一张图中近机位的A320可能占一两百像素远机位的通勤飞机可能只有三十像素一个检测头很难同时兼顾。第二背景极其杂乱。机场地面有滑行线、廊桥、各种地面保障车辆还有跑道标线和不同材质反射光照很多纹理和飞机边缘特别像。第三飞机本身姿态和外观不统一客机、货机、螺旋桨飞机机翼位置、机身比例都不同而且不同航空公司涂装差异很大深色涂装和浅色涂装的光影特征完全不同。我用过传统图像处理的方法比如基于边缘检测、形状匹配去抠飞机在小样本、单一背景情况下能出效果但一旦机位变化、光线变化马上失效。后来转向深度学习目标检测路线把这个问题交给数据驱动模型来解决。1.2 为什么我最终选择了YOLOv26项目对检测实时性和部署友好度的要求比较高所以当时没有考虑两阶段检测器而是在单阶段的YOLO系列里选型。对比过几个版本的差异后我最终用了YOLOv26作为基线。选择YOLOv26不是因为它版本号“看起来新”而是实际测试下来它在三个关键点上更贴合机场飞机检测需求。一是特征融合结构比较完善浅层细节能有效传达到检测头这是小目标检测的基础二是模型在保持较高精度的同时参数量和推理耗时控制得不错便于后续用TensorRT做推理加速三是它的训练策略和超参默认值更省心数据增强逻辑内置了Mosaic、MixUp等直接修改配置文件就能用。当然整个项目不是把YOLOv26拿过来直接train就结束而是围绕飞机检测这个特殊场景做了数据层面和结构层面的适配。后面会详细说。有一点我要强调选型阶段一定要拿自己的数据做小规模消融测试不要只看公开榜单的mAP。我在自己的几百张机场图上对比过YOLOv26和另一个轻量级版本YOLOv26的mAP0.5高了大概4个百分点在远机位小目标上的召回优势更明显所以最终确定它作为主线模型。1.3 数据准备与标注策略不要急着标几千张数据是整个项目的地基。我一开始踩过坑以为数据越多越好就到处收集机场俯拍图但很多图要么分辨率太高导致显存爆炸要么场景单一导致模型严重过拟合。后来我总结了一套比较实用的数据收集标准。覆盖不同机场布局单跑道机场、双跑道、大型枢纽机位区至少要有3~5种摆布形态。覆盖不同光照白天顺光、逆光、阴天、近黄昏这些光照下飞机反射差异很大。覆盖不同拍摄高度塔台视角、卫星视角、高杆监控视角不要只有一种俯视角度。覆盖不同机型窄体客机、宽体客机、支线机、小型公务机如果没有公务机样本模型会对小飞机非常不敏感。标注方面类别只设置一个“plane”但每个目标的框必须贴边不要为了省事把飞机外圈多留几个像素。因为对接下来的小目标检测来说框稍微松一点模型学到的定位信号就会模糊尤其是在飞机只有三四十像素的时候。我习惯在标注工具里开启自动吸附边缘再人工过一遍离群点。最终项目的数据集规模大致是训练集一千八百张图验证集三百张测试集两百张每张图里飞机实例数从1架到20多架不等。这个规模不算大所以后面数据增强做得比较重。2. 核心细节数据增强与模型改进2.1 数据增强让一张图顶十张图对于小目标检测数据增强的作用有时候比换模型结构还明显。我在YOLOv26配置里重点调了几组增强参数。第一组是Mosaic把四张图拼接成一张。这个对模型学习不同场景的拼接自然很有效但需要注意一个坑拼接后的图里单架飞机会被切到边缘甚至跨图截断因此我把Mosaic的随机裁剪比例调高了一点避免飞机实例被裁得太碎。第二组是Copy-Paste增强也就是把某一架飞机的像素抠出来贴到另一张背景图上。YOLOv26较新的训练流程里支持类似语义级数据合成我显式开启了它飞机数量在训练时被等效放大这个操作对提升小目标召回特别明显。实践中我固定从每张训练图中随机抽取1~3架飞机做粘贴粘贴位置随机避开原图中心。第三组是尺度扰动我用的是随机放缩范围0.5~1.5。机场飞机尺寸跨度太大通过这种扰动强迫模型对不同大小的飞机都保持敏感。但注意如果放缩比例过大会破坏飞机显示比例导致训练样本失真所以0.5到1.5是我试过比较稳的区间。2.2 小目标检测给模型加一层眼睛YOLOv26的原始检测头通常在三个尺度的特征图上做预测高层特征层负责大目标中层负责中等目标低层负责一般小目标。但机场图中的远机位飞机实在太小默认的最低层特征图依然不够细。我在项目里做了两个主要改动。第一个改动是增加一个更高分辨率的检测分支也就是常说的P2层。原理很简单特征图的分辨率越高保留的细节越多检测小目标的能力越强。YOLOv26的颈部网络通过上采样把浅层特征和深层特征融合我再额外引出一个步长为4的检测头把原始输入尺寸下采样四倍的特征接入检测输出。这样做的代价是计算量增加显存占用也变多但对三十像素级别的小飞机提升非常明显。第二个改动是引入注意力机制。我用的是轻量级注意力模块加在颈部特征融合之后让模型更关注飞机机翼和机身的边缘区域而不是整张背景。这个模块的参数很少但实测能提升2个点左右的召回率。不过我也提醒自己注意力度要适度加太多反而会让训练收敛变慢尤其在这种中小规模数据集上容易过拟合。还有一个后处理层面的改动把原来的NMS改成Soft-NMS。机场飞机经常密集停靠两个飞机挨得很近普通NMS很容易把相邻的检测框当作重复框误删。Soft-NMS根据IoU衰减相邻框的置信度而不是直接扔掉能在密集机位场景多找回10%左右的检测框。2.3 参数不是拍脑袋是挖出来的YOLOv26本身自带的默认超参偏向通用目标检测但针对飞机检测还需要手动调几项。我的最终训练配置如下表参数设置值选择思路输入尺寸1280×1280太大显存吃紧太小远机位飞机看不清Batch Size16卡显存约24G勉强够用初始学习率0.001用公开预训练权重不宜过大学习率策略warmup 余弦退火前3个epoch从0.0001线性升到0.001训练轮数300 epoch配合早停最优模型出现在230~270之间优化器AdamW比SGD在数据增强较重的场景下更稳EMAon平滑权重避免最后阶段震荡有一个重要细节预训练权重的选择。我是直接用公开的目标检测预训练权重而不是随机初始化。这相当于让模型先具备通用物体特征理解能力再迁移到飞机检测上。实测从零训练的话同样的epoch下mAP至少低8~10个点而且前期loss很难降下去。还有一个值得说的点是loss曲线的观察节奏。训练早期我应该关注train loss是否有持续下降到了后程更该关注验证集的mAP是否稳步上升。如果train loss降但validation mAP不动那基本是过拟合前兆要立刻调整数据增强强度或者提前早停。3. 实操过程从环境配置到跑通推理3.1 环境准备版本选不好全程折腾我习惯用conda做环境隔离这能避免不同项目之间的依赖冲突。推荐版本搭配如下Python3.9PyTorch2.0.1CUDA11.8cuDNN8.6.0依赖库OpenCV-Python、numpy、matplotlib、pandas需要注意一个坑PyTorch和CUDA版本必须匹配如果CUDA装成12.x但PyTorch编的是11.8训练时虽然偶尔能跑但会出现奇怪的显存报错或者算子执行异常。我一般都用pip安装对应CUDA版本的PyTorch而不是用conda默认源。训练阶段我用的是单卡24G显存的GPU。如果显存不足不建议疯狂降低Batch Size那样会让梯度估计不稳应该优先开启混合精度并给Dataloader增加梯度累积步数。3.2 训练代码一份能直接改的骨架整个项目遵循YOLO系列通用的配置和脚本风格。下面这份代码是训练流程中最重要的核心入口我从项目中简化整理出来你可以当成骨架直接套用。import torch from model import YOLOv26Model # 模型定义 from dataset import create_loaders # 数据加载函数 from config import train_config # 超参配置 if __name__ __main__: cfg train_config() model YOLOv26Model(num_classes1) # 加载公开预训练权重 checkpoint torch.load(cfg.pretrained_path) model.load_state_dict(checkpoint, strictFalse) train_loader, val_loader create_loaders( data_rootcfg.data_root, image_sizecfg.img_size, batch_sizecfg.batch_size, mosaic_probcfg.mosaic_prob, copypaste_probcfg.copypaste_prob ) optimizer torch.optim.AdamW( model.parameters(), lrcfg.lr, weight_decay0.0005 ) scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_maxcfg.epochs, eta_mincfg.lr_final ) model.cuda() model.train() for epoch in range(cfg.epochs): for images, targets in train_loader: images images.cuda(non_blockingTrue) targets [t.cuda(non_blockingTrue) for t in targets] loss model(images, targets) optimizer.zero_grad() loss.backward() # 梯度累积模拟更大batch optimizer.step() scheduler.step() # 每若干epoch保存一次权重并做验证这段代码里有两个容易被忽略的点。第一个是strictFalse加载预训练权重因为YOLOv26检测头从通用类别数调整到单类别后最后一层的维度不一样如果直接strictTrue会报key缺失错误。第二个是数据增强的mosaic_prob我一般设置成0.5而不是1.0因为Mosaic虽然丰富背景但机场图本身目标数量密度不均过度使用会让模型看到大量合成边角影响真实场景泛化。训练过程中我建议每完成10个epoch就在验证集上测一次mAP并把最优权重单独保存。不要总认为最后一个epoch的模型一定最好因为余弦退火结束后模型可能已经轻微过拟合。3.3 推理与可视化把模型从训练拉到落地训练完成后推理脚本反而要写得越简单越稳。我的惯用流程是读取图像做等比例resize到1280×1280并补边送入模型前向传播得到一系列预测框然后做置信度过滤和NMS。以下是一个最小可用推理脚本import cv2 import torch from model import YOLOv26Model def detect(image_path, model, conf_thres0.35, iou_thres0.5): img cv2.imread(image_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) resized cv2.resize(img_rgb, (1280, 1280)) tensor torch.from_numpy(resized).float().permute(2, 0, 1).unsqueeze(0).cuda() / 255.0 with torch.no_grad(): preds model(tensor) # [N, 6]: cx, cy, w, h, conf, cls # 过滤低置信度框 preds preds[preds[..., 4] conf_thres] keep nms(preds, iou_thres) # 内部实现省略 boxes preds[keep] for box in boxes: x1, y1, x2, y2 xywh_to_xyxy(box[:4], scale_to_origTrue) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) return img推理时还有两个小技巧。一是置信度阈值不要设太低机场假阳性很多来自地面车辆0.35是我试下来微调比较平衡的值。二是对超大图最好先切成若干1280×1280的patch分别检测再把结果合并不要直接把原图resize到1280这样远机位小飞机基本会糊掉。4. 评估指标与效果复盘4.1 指标别只盯着一张图上若干个框做检测项目如果没有统一的量化指标你会陷入“看起来效果不错”的错觉里。我这次主要看四个指标Precision、Recall、mAP0.5、mAP0.5:0.95。Precision代表模型检测出来的飞机框里真飞机的比例Recall代表真实飞机里模型找到的比例。这两个指标是打架的阈值调高Precision升高但Recall下降。mAP0.5表示预测框和真实框IoU超过0.5时才算命中的平均精度这个指标相对宽容mAP0.5:0.95则要求更高等于在一系列IoU阈值下求平均能够更细致地反映出框的定位精准度。我的最终结果如下表指标数值Precision0.891Recall0.824mAP0.50.885mAP0.5:0.950.716推理速度单卡26 FPS小目标32pxRecall0.763对比未经优化的基线YOLOv26小目标Recall从0.61提升到0.763这个提升主要归功于P2检测头和Copy-Paste增强。mAP0.5:0.95也提升较大说明预测框的定位精度本身也有改善而不是单纯靠提高召回数量。4.2 实测效果哪些场景好用哪些还没搞定拿到指标之后还是要回到真实使用场景里看看。表现比较好的情况有三个天气晴朗、机场光照均匀、飞机与环境对比度高的场景。这时候模型对靠桥飞机和滑行道上的飞机检测非常稳基本能做到一图不漏。其次是大型枢纽机场的成排停机位Soft-NMS在相邻飞机挨得很近时起了关键作用明显减少了框合并现象。还有一个好用场景是中高空视角下飞机呈现完整俯视轮廓的情况机翼、尾翼结构清楚模型对这类目标识别很准。还没搞定的情况也有。低光照或夜晚飞机整体亮度低模型漏检率明显上升尤其深色涂装机几乎“隐身”。另一个容易错的是白色面包车和机场摆渡车它们从俯视角度看过去长宽比和飞机接近会被当成小飞机检测出来。我目前的做法是置信度阈值提至0.4来压制这种误检但代价是部分真实小飞机也被压制了后续打算专门收集负样本再训练一版。5. 常见问题与排查技巧实录5.1 飞机太小总漏检问题出在哪这个是我被问得最多的问题。常见原因是模型只在深层特征图上做预测而浅层高分辨率特征没有参与检测。YOLOv26如果默认没有P2检测头那么你要确认是否显式添加。操作层面可以看一眼特征图输出尺寸输入1280×1280如果检测头最低层的输出是80×80说明小目标检测能力有限建议增加160×160或更高分辨分支。还有一个原因是飞机只有二三十像素即使浅层特征有信息网络本身在下采样过程中丢失了大量边缘细节。我给的建议是优先做结构性改动加检测头、把neck中上采样次数增加同时配合Copy-Paste增强把训练样本中的小飞机数量做多。数据增强方面不要把小飞机放大后再训练这样会让模型不习惯真实的小目标定义。5.2 白色面包车被当成飞机我这样处理机场地面车辆被误检成飞机几乎是必然的尤其俯视角度下车顶轮廓和机身轮廓高度相似。我先尝试调整置信度阈值发现效果有限阈值调到0.45面包车是少了但小飞机也少了。后来改用两个方案并行。第一个是数据层面加负样本。我从背景图中随机裁剪大量不包含飞机的区域标注为背景参与训练。这种“负样本参与训练”的方式让模型学到了更多“像飞机但不是飞机”的特征。第二个是难样本挖掘具体来说是记录验证集中误检得分最高的图像然后把那些误检区域当背景硬负样本再进入下一轮训练。两个方案叠加后误检率下降了约50%。5.3 训练中途loss飞了先别慌loss突然变成NAN或者直接爆炸我遇到过两次。第一次是学习率太高AdamW虽然相对稳但在数据增强较强时偶尔初始阶段就会炸。解决方法是把warmup轮数增加到5个epoch且让初始学习率从0.0001起跳。第二次是Batch Size和输入尺寸搭配出了问题1280×1280输入加上大Batch Size导致显存溢出我在Dataloader里开了pin_memoryTrue并改为混合精度问题解决。还有一个容易忽视的原因标签文件里出现了超出图像范围的坐标比如某个框的x2大于图宽。建议在训练前执行一次数据清洗把所有越界坐标裁剪到图像边界内。5.4 推理速度不够快部署时的优化方案如果只是训练阶段26FPS已经够用但部署到监控视频流时最好到30FPS以上。我用过三个阶段的优化。第一步是模型轻量化。把输入尺寸从1280降到960推理速度能提升30%但小目标召回下降明显需要评估是否接受。第二步是TensorRT加速。用FP16精度转换后我的20层左右模型在单GPU上跑到35FPS且精度几乎没有损失。第三步是批处理优化。对视频流做多帧batch推理GPU利用率提升明显延迟反而更低。如果还嫌慢可以考虑INT8量化但机场检测需要在真实数据上重新校准不然小目标召回会崩。需要强调的是压缩模型前最好先确认瓶颈是不是后处理。我用profiler看过自己的推理链路NMS在大图上也会耗时把Soft-NMS换成普通NMS作为部署版本能省1~2毫秒同时保证检测效果差别不大。最后说点个人体会。这个项目做完我最深的感触是模型版本和结构改动固然重要但真正决定飞机检测上限的是对小目标场景的理解和数据的精细程度。YOLOv26帮我把基线拉到了一个不低的位置但最终能收获高召回、低误检靠的还是数据增强策略、检测头微调和一轮又一轮负样本清洗。如果你也要做类似机场目标检测项目建议先别急着魔改模型结构把数据基线、增强策略、评估闭环跑通然后拿着问题清单一项项去修复。这个顺序走下来你会少走很多弯路。