
做目标检测的人大概都绕不开小目标这个坎。尤其是无人机航拍视角下的数据集像Visidron这类场景地面目标小、背景复杂、数量密集用普通训练方式很容易出现漏检。我最近完整跑了一遍YOLOv8在小目标检测数据集上的训练流程从数据准备、模型配置到精度提升踩了不少坑也总结出一套可以复用的方法。这篇文章就把我实际的操作和调优经验记录下来重点讲清楚怎么做才能把小目标从“看不见”变成“看得清”适合正在用YOLOv8做小目标检测、或者在Visidron这类航拍数据集上效果不佳的朋友参考。1. 项目整体设计与思路拆解1.1 Visidron数据集的特点与小目标检测难点Visidron是一个以无人机高空俯拍视角为主的检测数据集场景里出现的车、人、船、自行车等目标在原始分辨率下往往只有十几个到几十个像素。按照COCO数据集对小目标的定义小于32×32像素就能算小目标而Visidron里这类目标占比非常高部分图像中甚至一半以上的标注框都落在这个区间。小目标检测难本质上难在两点一是目标本身携带的像素信息太少卷积网络经过几次下采样之后目标的特征基本被“稀释”掉了二是航拍图像背景极其复杂纹理丰富的屋顶、道路、树木都可能被模型误认为目标。普通目标检测模型往往更关注中等以上尺度目标对小目标容忍度很低。直接用默认参数在Visidron上训练最直观的感受就是验证集上mAP50低、漏检框多有时看起来连“人眼可见”的目标都没检测出来不是模型笨而是整个pipeline没有针对小目标做设计。在动手之前我先统计了训练集中目标的宽高分布发现大量目标在原始图片中的比例不足0.5%。这个数字直接决定了后续对输入分辨率和检测层结构的调整方向。1.2 为什么选择YOLOv8作为训练框架选YOLOv8主要是因为它对小目标检测的“底子”比前代好而且生态成熟改起来方便。YOLOv8相比YOLOv5有几个关键变化骨干网络中的C2f结构比原先的C3模块拥有更丰富的梯度流有利于保留低层特征检测头改成Anchor-Free的Decoupled Head分类和回归分支分离在小目标边界框回归上更直接数据增强内置了Mosaic、MixUp、随机仿射等一套组合策略训练阶段就能让模型接触到更多尺度变化。另外YOLOv8官方提供了n / s / m / l / x一整套不同规模的预训练权重可以让我在“模型容量”和“训练成本”之间快速试错。再加上命令行交互友好训练、验证、导出一条命令搞定不需要写太多胶水代码。对于Visidron这种中小规模数据集YOLOv8是最省心也最容易出效果的选择。1.3 精度提升的整体思路我一开始以为小目标检测效果差单纯是模型小或者预训练权重不行于是直接换大模型、加训练轮数结果验证集分数几乎没动。后来把整个流程拆开看才发现精度提升是“数据质量—模型结构—训练策略—推理后处理”四个层面的合力指望靠单项优化解决所有问题不现实。数据层面要保证标签准确、增删合理不能让模型学到错误或者模棱两可的目标。结构层面需要让模型有专门负责小目标的检测头比如增加P2层。训练策略层面要调高输入分辨率、合理设置Mosaic关闭时机、控制学习率。推理层面可以引入切片推理或TTA把模型在原始大图上的表现充分压榨出来。后面几个章节我会按这个思路逐一展开。2. 训练前的数据准备与预处理2.1 数据标注格式与目录组织YOLOv8训练需要的数据目录一般长这样Visidron/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/labels目录下每个.txt文件和图片同名每行代表一个标注框格式是class_id x_center y_center width height注意这里面的x_center、y_center、width、height都是相对于图片宽高归一化后的值取值在0到1之间。如果拿到的是COCO格式的JSON标注需要转换一下。我写了一个简单的解析脚本结构如下import json from pathlib import Path def coco_to_yolo(json_path, output_label_dir): with open(json_path, r, encodingutf-8) as f: data json.load(f) images {img[id]: img for img in data[images]} for ann in data[annotations]: img_id ann[image_id] img images[img_id] w_img, h_img img[width], img[height] x, y, w, h ann[bbox] cx (x w / 2) / w_img cy (y h / 2) / h_img nw w / w_img nh h / h_img label_path Path(output_label_dir) / (img[file_name].replace(.jpg, .txt)) with open(label_path, a) as f: f.write(f{ann[category_id]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n)坐标转换时最容易出错的是没有除以图片实际宽高而是直接用了像素坐标。这个问题在新手里非常常见一旦出现训练loss会莫名其妙不降或者验证集完全失效。我每次转完标注都会随机抽几张图把标签画到原图上人工看一眼。2.2 数据清洗与目标尺寸统计数据清洗不是随便删图而是要把明显有问题的样本剔除掉。我整理Visidron时主要处理了四类问题标注框超出图片边界的重新裁剪或者删掉。标注框宽高为0或坐标异常的直接清理。类别标错的需要人工复核。重复图片和完全模糊的航拍图能删就删。清洗之后我写了一个统计脚本统计每个类别目标数量、目标宽高分布、单张图片目标密集程度。这个统计很重要因为后面调节锚框、选择切片大小都要参考它。比如我统计后发现Visidron中大多数目标宽度在10~50像素之间原图分辨率是2000×1500左右这提示我训练时不能简单resize到640否则这些目标就被压缩没了。训练集和验证集的划分也建议按场景划分避免同一区域或同一航迹的图片同时出现在两边。无人机数据经常有连拍如果随机划分相邻帧几乎一样验证集分数会虚高实际应用时被打回原形。2.3 数据增强策略给小目标留“活路”YOLOv8默认开启了不少数据增强其中Mosaic对目标检测最有帮助。它把4张图片拼成一张等于变相增加了模型对图像上下文的感知。但小目标增强有个矛盾点Mosaic在拼接时会对每张图做随机缩放放大的小目标可能变大缩小的大目标也可能变成小目标这对增加目标尺度多样性有好处但也可能把小目标缩到3×3像素这种完全无法辨认的程度。所以不能一味依赖默认增强。我在实际训练中调整了几个关键参数mosaic1.0训练初期全开但设置了close_mosaic10也就是最后10个epoch将Mosaic关闭让模型在真实分布上稳定收敛。mixup默认开我降到0.1因为航拍小目标本身就密集MixUp叠太多会让目标更加混乱。关闭了随机旋转90度的参数因为无人机航拍虽然视角多变但旋转后标注框会变形容易引入错误标签。将HSV增强的饱和度变化稍微调低避免小目标颜色失真。如果你使用的是Ultralytics官方库可以在yaml配置文件里直接覆盖mosaic、mixup、degrees、fliplr等参数。增强是必要的但小目标场景下要“适量”过度增强反而会让模型学到大量模糊样本。3. YOLOv8训练配置与核心参数调优3.1 模型选型与输入分辨率YOLOv8提供了多个scale参数量从几M到几十M不等。在小目标数据集上我的经验是不要盲目上最大模型先根据显存和验证集尺寸选一个合适的基线。我做了简单对比模型参数量速度小目标特征保留适用场景YOLOv8n最小最快弱资源受限快速验证YOLOv8s小快中个人GPU初步实验YOLOv8m中中中上精度优先且显存充足YOLOv8l大慢强服务器训练精度诉求高我用YOLOv8s作为基线因为Visidron数据量不算特别大模型太大会过拟合太小又学不够小目标特征。输入分辨率方面我没有用默认的640而是直接拉到1024。这个改动非常关键。原图2000×1500缩到640很多小目标缩成不到5个像素模型根本没法学缩到1024小目标还有10~20像素勉强能保留基本形态。如果你的显存有限可以先用--batch 8 --imgsz 1024试跑。如果显存还是不够我建议优先保分辨率把模型从s降到n而不是把分辨率降回640。分辨率对小目标检测的影响远大于模型大小。3.2 修改网络结构给YOLOv8加P2检测层默认YOLOv8检测层包含P3、P4、P5分别对应输入图像的1/8、1/16、1/32步长。这些层主要负责中大型目标小目标基本在P3层上勉强检测。对于Visidron这样的小目标集直接改为P2、P3、P4、P5四层检测更有效。P2层对应原图的1/4分辨率特征图更大空间信息更细能让模型在小目标的早期特征上就能做出判断。Ultralytics官方其实提供了yolov8-p2.yaml但我更推荐自己改因为这样能更清楚模型结构。核心思路是在骨干网络的浅层引出P2特征然后参与Neck的FPN融合。简化示意如下# 在yolov8.yaml基础上增加P2检测层示意 neck: - [-2, 1, Conv, [512]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, -3], 1, Concat, [1]] - [-1, 1, C2f, [512]] - [-1, 1, Conv, [256]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, -4], 1, Concat, [1]] - [-1, 1, C2f, [256]] - [-1, 1, Conv, [128]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, -2], 1, Concat, [1]] # 引出P2 - [-1, 1, C2f, [128]]不过这样手写容易出错我会先把官方yolov8-p2.yaml拷贝出来再按数据集类别数修改比如把nc改成Visidron的类别数。增加P2层后检测头从3个变成4个显存占用和推理时间都会上涨需要同步考虑。我的训练命令中通过--cfg yolov8-p2.yaml --weights yolov8s.pt指定如果不想从零训练官方预训练权重会自适应匹配大部分层。3.3 训练超参数与优化器设置超参数配置直接决定训练成败。我记录一组实测有效的配置epochs100小目标训练通常需要更多轮次才能收敛但也要配合早停避免过拟合。batch16在8GB显存下配合--imgsz 1024比较危险实际用了8。optimizerAdamWYOLOv8默认是SGD但我用AdamW在小目标场景下loss下降更平滑尤其是收敛后期。lr00.001AdamW下学习率不能太大否则容易震荡。weight_decay0.0005防止大模型过拟合。warmup_epochs5预热让训练更稳定。close_mosaic10最后10轮关闭Mosaic验证集指标会有一个明显的回稳。degrees0关闭随机旋转避免航拍目标方向变化过大导致的标签错位。这些参数不是唯一解但如果你在小目标数据集上没有明确方向可以直接用这套先跑一个baseline再根据loss曲线微调。3.4 训练环境与显存优化小目标检测训练最痛的是显存。分辨率1024、模型YOLOv8s、batch 8在我的显卡上勉强能跑如果再增加P2层梯度回传时的中间特征图数量更多显存直接爆掉。我后来开了AMP混合精度训练峰值显存降低了接近30%而且loss曲线基本没受影响。Ultralytics的train.py里已经有AMP支持直接加amp参数即可。如果batch size还是太小可以用梯度累积yolo detect train dataVisidron.yaml modelyolov8s-p2.pt imgsz1024 batch8 epochs100 optimizerAdamW lr00.001 close_mosaic10 ampTrue注意如果batch设成4梯度累积16相当于每次优化器更新时看到了64张图片的梯度这能改善小batch带来的噪声问题。但梯度累积也会让训练时间变长尽量优先保batch。4. 小目标检测精度提升的实战技巧4.1 用切片推理SAHI解决小目标训练完成后如果直接把2000×1500的Visidron原始图resize到1024去推理小目标依然会丢失严重。一个非常有效的手段是切片推理也就是SAHISlicing Aided Hyper Inference。原理很简单把大图切成若干有重叠的小块每个小块单独送入模型推理最后再把所有检测框拼回原图坐标。切片推理能避免整体resize带来的目标缩放损失。我用imgsz1024、slice_size640、overlap_ratio0.2的配置把原图切成4~6个640×640的patch每个patch内的小目标相对于patch的大小放大了一倍多检测率明显上升。用UltralyticsYOLOv8配合sahi库推理代码大致如下from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model AutoDetectionModel.from_pretrained( model_typeyolov8, model_pathbest.pt, confidence_threshold0.25, image_size1024, devicecuda:0, ) result get_sliced_prediction( imagetest.jpg, detection_modeldetection_model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2, ) result.export_visuals(export_diroutput/)切片重叠比例建议在0.2~0.3之间太低会导致目标被切开、跨切片检测不到太高会重复计算推理耗时翻倍。这是我在小目标推理中收益最大的一步视觉效果立竿见影。4.2 自制切片数据集训练让模型在小图上“长大”切片推理虽然好但如果训练阶段完全没见过这种“近景”视角推理时也会不习惯。因此我更推荐把整个Visidron训练集先做一次离线切片用切片后的小图重新训练模型。具体操作是把每张原图按固定步长切成多个patch保留包含目标的patch作为新训练样本。切片尺寸和推理时保持一致我用640×640步长640不重叠对于目标刚好落在边缘的还要适当扩展padding。这样处理后训练集里的目标在patch中占的比例大大增加模型不需要自己“脑补”小目标的细节。离线切片的好处是训练时输入分辨率可以降低到640显存压力小模型遇到的目标尺度相对更大更容易学到目标本身的纹理和轮廓。代价是训练图像数量暴增单张图像可能变成5~10个patch训练时间翻倍。我在Visidron上试过mAP50比直接原图1024训练高了5个点左右且推理速度更快因为后续推理也走切片管线。4.3 检测后处理与结果融合训练完成后很多人在后处理阶段把置信度阈值设成0.5甚至0.7导致小目标被过滤掉。小目标因为特征弱模型输出的置信度普遍不高所以推理时应该把conf降到0.2左右再用NMS过滤重叠框。YOLOv8的NMS参数iou默认是0.7小目标场景可以调到0.6甚至0.5因为小目标重叠较容易被误删。如果你追求极致效果可以使用TTATest Time Augmentation把多尺度、翻转后的推理结果合并OpenMMLab等工具都支持但YOLO的官方TTA实现比较轻量直接加--augment开启即可。多个模型推理结果再融合时我用过最简单的NMS合并以及稍微复杂一点的WBFWeighted Boxes Fusion。WBF对低置信度小目标更友好因为它不只是简单删除低分框而是根据多个模型的置信度加权合成新框。不过WBF需要自己写或调库对于大多数场景NMS已经够用。4.4 结合P2层与切片策略的实测效果我的最优组合是P2检测层 1024输入分辨率 离线切片训练 切片推理 低置信度阈值。这条链路组合下来Visidron验证集上小目标AP从最初的0.12提升到了0.28mAP50从0.31提升到0.52整个提升过程不是靠某一个参数而是每一步都贡献了一部分。其中贡献最大的还是P2层和切片推理两者分别解决了“模型看不到小目标”和“推理时小目标被缩小”的问题。需要提醒的是P2层和切片策略会同时增加计算量实际部署时要根据硬件条件取舍。如果只能在嵌入式设备上跑建议只保留P2层推理时用传统resize如果服务端算力充足切片推理是性价比最高的提升手段。5. 常见问题与排查技巧实录5.1 训练loss不下降或者直接变成NaN我遇到过一次loss震荡不降后来发现是数据集中有一张图片的标注框坐标写成负数模型一直在拟合非法目标梯度被带偏。清洗脚本加上合法性校验后loss很快就降下来了。还有一种常见情况是使用了太高的学习率。YOLOv8默认SGD时lr00.01但如果换成AdamW而不降学习率很容易在训练初期出现NaN。我的经验是换优化器一定要同步调整学习率比如AdamW用0.001且开启warmup如果loss一开始就NaN先把学习率除以10再试。5.2 小目标漏检严重召回率低漏检严重的原因通常是输入分辨率不足或后处理阈值太高。先检查你的imgsz设置是否用了640、512甚至更小的输入再检查推理时conf是否过高。如果两者都没问题那就是模型结构上的问题需要引入P2层或增大模型容量。还有一个容易被忽略的因素NMS在小目标密集场景下可能会把相邻的两个小目标合并成一个。如果输出结果里很多目标被连成一片可以降低NMS阈值或者在YOLOv8的后处理中调整max_det上限。5.3 显存不足batch size设不上去显存不足不需要硬扛可以先开AMP再把batch降到4。如果仍然不够就降低输入分辨率但注意不要低于640否则小目标彻底没希望。也可以对训练图像做离线切片用更小的训练分辨率配合切片后的patch效果往往比原图低分辨率更好。另外YOLOv8在训练时会在最后一个epoch做验证验证阶段也需要不少显存。如果训练到验证阶段报CUDA OOM可以临时关闭验证集上的TTA或者每次验证时batch1分批处理。5.4 完整训练命令与实验记录技巧最后分享一条我在Visidron上验证过多次的完整训练命令yolo detect train \ dataVisidron.yaml \ modelyolov8s-p2.yaml \ pretrainedyolov8s.pt \ imgsz1024 \ batch8 \ epochs100 \ optimizerAdamW \ lr00.001 \ close_mosaic10 \ cos_lrTrue \ ampTrue \ patience20其中cos_lr是我比较推荐开启的学习率余弦衰减在小目标训练中能让后期收敛更稳。实验记录方面我每次跑新配置都会在结果目录名里带上关键参数比如exp_s1024_p2_amba避免几天后看着一堆exp1、exp2不知道哪个是哪个。这一步看似不起眼但对多轮调参的帮助非常大。我跑了这么多轮最深的感觉就是小目标检测没有银弹必须把数据、结构、训练、推理每个环节都打磨一遍。P2层、切片推理、合适的增强策略这三板斧每一斧砍下去都有效果。如果你也在Visidron或类似小目标数据集上碰壁建议照着这套流程从头捋一遍至少能把“模型疯狂漏检”这个最棘手的问题解决掉。