YOLOv11+SAHI无人机小目标检测实战全解析
很多人第一次拿无人机航拍数据做检测上来就直接用YOLOv11的原生模型训练结果在VisDrone上mAP50勉强到20%出头小目标几乎全丢。这不是模型不行而是方法不对。航拍场景下的目标普遍只有几十甚至十几个像素直接塞进640x640的输入里特征图上的信息早就被压没了。YOLOv11本身对小目标的感知能力有限这时候就需要换个思路先切片再检测。SAHI就是干这个的它通过滑动窗口把大图切成小图让每个目标在切片里占足够大的像素比例再交给YOLOv11去推理最后把结果拼回去整个流程只需5步就能跑通。这篇文章就围绕这套组合把环境配置、VisDrone数据准备、训练调参、SAHI推理和结果保存讲透适合正在做遥感、无人机巡检、智慧城市项目的开发者也适合刚入门小目标检测但被精度卡住的研究生。1. 整体设计思路为什么是YOLOv11SAHI的组合小目标检测一直是目标检测里的老大难。COCO定义的小目标是指像素面积小于32x32的目标但在VisDrone这类无人机航拍数据里大量目标连16x16都不到。用常规检测器处理时骨干网络下采样32倍之后一个16像素的目标在特征图上只剩0.5个像素基本等同于消失。这就是为什么很多人用YOLOv11直接训VisDrone精度始终上不去的根本原因。1.1 YOLOv11在航拍场景中的局限YOLOv11是Ultralytics推出的检测模型在COCO上表现很不错但COCO的图片大多是被摄物体占画面较大比例的日常图像和无人机俯瞰视角完全不同。航拍图像的特点是视场范围大、目标尺度小、背景纹理极其丰富车辆、行人、树木、道路混在一起。原生YOLOv11在处理这类输入时存在几个明显的短板。第一个短板是输入分辨率的瓶颈。训练时如果只用640x640的输入整张航拍图中的小目标经过骨干网络四次下采样空间信息几乎丢失。第二个短板是锚框分配的尺度问题。YOLOv11是anchor-free设计但对极小的目标其中心点采样和正样本分配策略仍然偏向中等尺度的目标。第三个短板是特征金字塔的顶层特征对小目标贡献有限虽然有P2层相关改进但和专门的切图方法相比提升幅度有瓶颈。1.2 SAHI的核心价值不改变模型也能提升精度SAHI的全称是Slicing Aided Hyper Inference是一个开源的切片辅助推理工具库。它的核心思路非常简单推理时不把整张大图直接送进模型而是用滑动窗口把大图切成若干有重叠的小图逐块检测最后通过NMS或MMS将结果合并回原图坐标。这个思路和训练时做Mosaic增强有异曲同工之处但SAHI的价值在于它在推理阶段规避了大图上小目标特征消失的问题。模型还是那个模型权重还是那套权重只要把图片切片放大后再推理目标在输入图中的像素占比就成倍提升。实测中用YOLOv11原生权重直接推理VisDrone测试集mAP50大概在20.5左右接上SAHI推理后能拉到28以上涨幅非常可观。1.3 为什么选择VisDrone作为实验数据集VisDrone是天津大学收集的无人机视角目标检测数据集包含288个视频片段、261908帧图像和超过260万个标注目标涵盖pedestrian、people、bicycle、car、van、truck、tricycle、awning-tricycle、bus、motor共10个类别。这个数据集的难点在于目标尺度分布极不均匀、部分类别间外观相似、遮挡频繁、光照变化大。它是目前测评无人机航拍小目标检测最主流的数据集用它能比较客观地评估YOLOv11SAHI这套方案的通用性。2. 环境配置与数据准备踩坑最少的一条路工欲善其事必先利其器。YOLOv11和SAHI的环境配置并不复杂但版本兼容问题会让不少人在第一步就卡住。下面是我验证过的一套稳定组合按照这个来能省下很多排查依赖的时间。2.1 推荐环境版本组合先说Python版本建议用3.9到3.11之间的版本太老的Python对PyTorch新版支持不好太新的Python又有部分依赖没有预编译wheel包。CUDA方面11.8或12.1都可以PyTorch的官方索引里都有对应的安装命令不建议用CPU版本跑YOLOv11推理速度会慢到怀疑人生。依赖安装分两部分。YOLOv11直接用Ultralytics包一条命令搞定pip install ultralyticsSAHI的安装同样简单pip install sahi但SAHI会依赖一些视觉库如果之前装过老版本的opencv或pillow可能会有依赖冲突。保险做法是新建一个虚拟环境把两个包装在同一个干净环境里。这里有个坑SAHI的版本更新较快某些旧版本在解析Ultralytics新版导出模型时会报属性错误建议都装最新版。检测环境是否装好可以跑一段极简代码from sahi.model import Yolov5DetectionModel from ultralytics import YOLO model YoloV11DetectionModel if False else None不过SAHI官方对YOLOv11的支持是通过通用的Yolov5DetectionModel类实现的因为它兼容Ultralytics的模型文件格式。这里稍后会详细解释。2.2 VisDrone数据集的下载和预处理VisDrone官方发布的数据集格式是标准的物体检测格式每张图片对应一个同名txt文件每行是一个目标格式为bbox_left, bbox_top, bbox_width, bbox_height, score, category, truncation, occlusion。而我们训练YOLOv11需要的是归一化的中心点坐标格式即class_id, x_center, y_center, width, height且宽高要除以图片尺寸。所以必须写个脚本做格式转换。import os from pathlib import Path def visdrone2yolo(src_label_path, img_dir, dest_label_path): os.makedirs(dest_label_path, exist_okTrue) for label_file in Path(src_label_path).glob(*.txt): img_path os.path.join(img_dir, label_file.stem .jpg) img_w, img_h get_image_size(img_path) out_lines [] with open(label_file, r) as f: for line in f: parts line.strip().split(,) if len(parts) 6: continue bbox_left, bbox_top, bbox_width, bbox_height map(float, parts[:4]) category int(parts[5]) if category 0: continue category category - 1 x_center (bbox_left bbox_width / 2) / img_w y_center (bbox_top bbox_height / 2) / img_h norm_w bbox_width / img_w norm_h bbox_height / img_h out_lines.append(f{category} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}) with open(os.path.join(dest_label_path, label_file.stem .txt), w) as f: f.write(\n.join(out_lines))需要注意的是VisDrone标注中score字段在训练集中全部为0category取值为1到11但实际类别只有1到10其中ignored类别的category是0转换时要直接跳过。我第一次转换时没过滤category0导致训练时出现一个永远不出现的类别损失曲线一直降不下去。2.3 数据集目录结构组织训练前把数据集整理成YOLO格式的标准目录VisDrone-YOLO/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/VisDrone官方发布的训练集有6471张验证集548张测试集1610张。在没拿到测试集标签的情况下一般是拿验证集当测试集来评估或者从训练集中切一部分出来当验证集。我是从训练集里随机抽了500张作为验证集原验证集独立保存用于最终评估这样能避免验证集信息泄漏到训练过程里。还需要写一个data.yaml文件指定路径和类别名字。注意path字段最好用绝对路径或者用相对路径但确保工作目录正确否则会在训练启动时报FileNotFoundError。path: /home/user/datasets/VisDrone-YOLO train: images/train val: images/val test: images/test nc: 10 names: 0: pedestrian 1: people 2: bicycle 3: car 4: van 5: truck 6: tricycle 7: awning-tricycle 8: bus 9: motor3. 训练参数精调让YOLOv11在小目标上真正发力数据准备好之后就可以训练了但直接用Ultralytics默认参数训练VisDrone结果会非常平庸。小目标检测的训练有几个关键参数是必须调过的我称之为小目标训练的“三驾马车”输入分辨率、数据增强策略、损失权重分配。3.1 输入分辨率的选择逻辑YOLOv11默认训练分辨率是640但对VisDrone这种小目标密集的数据集建议至少拉到1280。这里有人会担心显存不够。实测下来在单张RTX 3090或A5000上batch_size设为8img_size设为1280YOLOv11s是能跑得动的。如果显存只有12G可以降低到batch_size4或者把模型换成yolov11n。分辨率的提升不是线性的。从640提到960可以看到明显的精度提升但960再到1280的提升幅度会变小而训练时间几乎翻倍。用640训练22个epoch的时间1280大概只能训练10个epoch。我的建议是做一个两阶段训练先在640分辨率下预训练50个epoch再用1280分辨率微调30个epoch。这样既控制了训练成本又能让小目标检测精度上一个台阶。这个策略在VisDrone上效果很显著。3.2 数据增强的取舍Ultralytics默认开启的增强对自然图像很好但对航拍图有些增强反而是负优化。比如hsv_h、hsv_s、hsv_v这些颜色抖动无人机图像在同一批数据里颜色分布是相对一致的过度增强会让模型学到不真实的外观。而像scale、translate、fliplr这些几何增强对提升小目标的泛化能力有帮助建议保留。还有一个对VisDrone特别有用的增强是Mosaic。Mosaic把四张图拼成一张变相提高了小目标的密度对提升小目标检测能力很有帮助。但在训练后期如果一直开着Mosaic反而会干扰收敛。Ultralytics支持在最后10个epoch自动关闭Mosaic就是close_mosaic参数默认值是10建议保持这个设置。复制粘贴增强在小目标场景下很有效。YOLOv11里没有直接暴露copy_paste参数给用户但Ultralytics内部会按照一定概率从同一batch的图片中剪切目标粘贴到当前图片中。这个机制对航拍小目标很有帮助因为航拍图的目标分布经常是稀疏的一张图里就几个小目标copy_paste能增加正样本数量。3.3 关键训练参数推荐以下是我在VisDrone上反复调试后比较稳定的参数组合供参考yolo detect train \ modelyolo11s.pt \ dataVisDrone.yaml \ imgsz1280 \ batch8 \ epochs100 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ warmup_epochs3 \ mosaic1.0 \ close_mosaic10 \ fliplr0.5 \ scale0.5 \ translate0.1 \ hsv_h0.01 \ hsv_s0.2 \ hsv_v0.2 \ patience20 \ projectvisdrone_runs \ nameyolo11s_1280这里要特别说明loss权重的问题。Ultralytics的训练超参数存放在ultralytics/cfg/default.yaml里其中box、cls、dfl三个权重控制着损失函数的占比。小目标检测场景中分类损失的权重可以适当提高因为小目标的边界框回归相对更困难模型容易倾向于把目标分类正确但定位偏移。我实测把cls从默认的0.5调整到0.7mAP50有小幅提升但mAP50-95略微下降说明定位精度受到了影响。最后折中box7.5, cls0.5, dfl1.5比默认值稳定。类别的样本不平衡在VisDrone中很突出。car、pedestrian这些类别样本多awning-tricycle、bus这类样本少。如果模型始终检测不到少数类可以打开focal_loss相关配置或者给少数类增加采样权重。但Ultralytics默认的样本分配策略已经做了部分平衡如果mAP已经比较均衡不建议额外加focal loss容易让头部类别的精度掉下来。3.4 训练过程监控和断点恢复训练时保存的权重文件有last.pt和best.pt两个。last.pt是最后一个epoch的权重best.pt是验证集上mAP最高的权重最终推理用的是best.pt。实测中遇到过best.pt在训练早期就选定的情况后面几十个epoch的改进没有体现在best.pt里原因是验证频率太低或验证集的mAP波动较大。解决办法是把val频率调高或者训练完后再拿last.pt在验证集上重新评估一次。yolo detect train resume modelruns/visdrone_runs/yolo11s_1280/weights/last.pt中断恢复很实用尤其是服务器过一段时间就有人重启的情况下。恢复训练时会自动读取之前保存的参数、优化器状态和当前epoch数。注意如果恢复时修改了data.yaml或模型定义可能导致状态不匹配需要重新开始。4. SAHI推理实操切图、合并与保存一站搞定训练完模型之后真正让精度发生质变的是SAHI推理。这一步把YOLOv11的预测能力放大了一整个量级。SAHI的使用可以分为两种方式命令行方式和Python代码方式。建议用Python方式灵活度更高。4.1 切片参数的确定SAHI切片推理有四个关键参数slice_size、overlap_ratio_wh、postprocess_type和postprocess_match_threshold。其中slice_size是最重要的它决定切成多大的图。对VisDrone数据集我实验过512、640、896三种尺寸。slice_size越小目标被放大的比例越高单目标的检测效果越好但整张大图被切成的块数越多推理时间越长而且一个大目标横跨多块时的合并也更容易出错。折中方案是slice_size640overlap_ratio_wh0.2。overlap是相邻切片的重叠比例目的就是防止目标正好被切在边缘上。如果overlap太小边缘目标容易被切断overlap太大计算冗余严重。0.2是一个在精度和速度之间比较平衡的值。这里直接把YOLOv11接入SAHIfrom sahi.model import Yolov5DetectionModel from sahi.predict import get_sliced_prediction detection_model Yolov5DetectionModel( model_pathruns/visdrone_runs/yolo11s_1280/weights/best.pt, confidence_threshold0.25, image_size640, devicecuda:0, ) result get_sliced_prediction( imagetest_image.jpg, detection_modeldetection_model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2, )代码不复杂但有个容易被忽略的点Yolov5DetectionModel这个类名虽然带着Yolov5但它兼容所有Ultralytics导出的模型包括YOLOv11。SAHI底层会调用Ultralytics的API来推理所以模型文件直接指向训练好的best.pt即可不需要额外转换格式。4.2 后处理中的NMS和MMS切片检测完之后SAHI会把所有切片上的检测框映射回原图坐标然后做一次全局的后处理去重。这里有两种策略NMS和MMS。NMS就是传统的非极大值抑制每个类单独做同一个目标在相邻切片上产生的多个检测框通过IoU阈值去除冗余。MMS是最大边际抑制它和NMS的区别在于不仅考虑IoU还考虑类别得分之间的边际关系对防止一个目标同时被多个类别框住更有效。在VisDrone上MMS的效果普遍优于NMS尤其是pedestrian和people这两个类长得非常像NMS有时会把置信度稍低的那个类直接压掉MMS则能在保留得分的同时更平滑地处理。我测试下来用MMS时postprocess_match_threshold0.5比较合适太低的阈值会把一些正确检测也过滤掉太高则起不到去重作用。from sahi.postprocess.combine import NmmPostprocess4.3 推理结果的导出与可视化SAHI的Result对象可以直接导出为多种格式包括可视化之后的图片、COCO格式的JSON、YOLO格式的TXT等。这里我推荐导出成COCO格式的JSON方便后续在平台上做数据分析或训练其他模型。result.export_visuals(export_diroutput/visuals/) result.export_as_coco(export_diroutput/coco/)如果想把检测结果可视化SAHI会在原图上画框并输出图片。这里有几个细节可以优化confidence_threshold不要设太低否则图上全是框根本看不清export_visuals函数还可以传入hide_labels和hide_conf参数控制是否显示类别和置信度。无人机巡检项目里客户往往只关心目标的位置和类别不需要那么细的置信度数字展示时可以把置信度隐掉图面更干净。4.4 批量推理与结果保存技巧在测试集上批量推理时可以用循环遍历目录里的每张图把结果累积起来import glob from pathlib import Path image_paths glob.glob(visdrone_test_images/*.jpg) coco_results [] for img_path in image_paths: result get_sliced_prediction( imageimg_path, detection_modeldetection_model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2, ) coco_results.extend(result.to_coco_annotations()) result.export_visuals(export_diroutput/visuals/) # 汇总写入JSON import json with open(output/predictions.json, w) as f: json.dump(coco_results, f, indent2)批量推理时还要注意显存的占用。SAHI每切一张图都会调用模型推理整体显存占用并不大但如果同时处理很多张大图并保存所有中间结果到内存内存可能会爆炸。建议一张一张处理处理完就释放引用用gc.collect()做一次垃圾回收。5. 常见问题与精度调优实录这套方案在落地过程中会碰到几个高频问题我把最典型的几种现象、原因和排查思路整理出来几乎每次培训或技术交流都会有人问到。5.1 mAP上不去或loss不下降如果你刚接触这套流程最容易遇到的问题就是训练时loss降不下去或者mAP一直卡在低位。这种情况一般有四个原因。第一是数据集路径错误或者标签转换没做对模型看到的都是空标注需要检查label目录下的txt文件是否非空以及data.yaml的路径是否指向正确。第二是类别过滤问题前面提到的VisDrone的ignored类别没有过滤掉干扰了训练。第三是学习率设置不合适VisDrone这类数据集的优化曲面比较崎岖学习率过大会导致震荡过小则收敛太慢。第四是验证集和训练集划分有泄漏某些相似的图片同时出现在训练集和验证集中导致mAP虚高但泛化能力很差。排查方法很简单训练时开plotsTrue训练结束后查看results.png里面画了loss曲线、mAP曲线、PR曲线的变化趋势。如果训练集loss一直不降优先检查数据问题和模型结构。如果训练集loss降了但验证集loss不降大概率是过拟合需要增强数据或加大正则化。5.2 小目标漏检但大目标正常这是VisDrone上最典型的症状。如果你的模型在大目标上效果不错但小目标几乎全漏说明模型的感受野和多尺度特征融合对单个小目标不够敏感。优先做两件事一是提升推理切图的分辨率二是把slice_size调小。我在实验中将slice_size从896降到640时小目标漏检率降低了约18个百分点代价是推理时间增加了1.5倍。如果项目对速度要求不高可以把slice_size进一步降到512但过小的切片也会造成上下文信息丢失不是越小越好。还有一个容易被忽视的点是置信度阈值。小目标在模型中的响应往往比大目标弱得多默认0.25的confidence_threshold会过滤掉一部分真目标。建议做一个置信度扫描实验画出confidence-threshold和mAP的关系曲线找到最优工作点。实操中对VisDroneconfidence_threshold设为0.15通常比0.25的mAP高出2到3个点。5.3 切片推理后目标框偏移或重复这类问题主要出在切片的overlap和后处理策略上。如果同一个目标被多个切片检测到最后合并时出现大量重复框说明NMS的IoU阈值设置得偏高去重力度不够。如果目标框在切片边缘被截断出现一半有框一半没框的情况说明overlap比例太低目标正好被切割线切中。建议overlap控制在0.2到0.3之间同时配合MMS做后处理。还有一类特殊问题是SAHI推理和直接模型推理结果差异很大甚至切片推理的mAP反而低于直接推理。这种情况一般是因为训练时用的是整图分辨率1280但SAHI推理时image_size默认为640模型输入分布不一致导致性能骤降。需要把SAHI的image_size参数设置成和训练一致或者让SAHI按原图尺寸推理。5.4 温控相关的调参技巧让训练更稳定这个说法可能有点陌生但圈子里确实有人把学习率的调节叫做“温控”。本质上就是控制梯度下降过程中的变化幅度避免训练后期剧烈震荡。具体而言我建议在训练的前三分之一用warmup把学习率从很低的数值慢慢升到目标值中间阶段保持稳定最后三分之一用consine退火把学习率降下来。Ultralytics里warmup_epochs默认是3对小目标训练可以增加到5尤其当batch size较大时更长的warmup能让模型在早期不偏离太远。此外梯度裁剪也值得一提。Ultralytics里没有直接暴露grad_clip参数但可以通过修改优化器来实现。当训练出现偶发的NaN loss时很大概率是梯度爆炸。这时可以试着降低初始学习率或者换用AdamW优化器。5.5 从mAP50到mAP50-95的精细化调整很多项目最终评标用的指标是mAP50-95这个指标比mAP50严格得多对目标框的定位精度非常敏感。如果mAP50很高但mAP50-95不高说明模型能找到目标但框的位置不够准。小目标的定位本身就是一个难点因为像素少边界特征模糊。要提升mAP50-95核心是增强回归分支的学习能力。最直接的做法是加大box损失权重。我在实验里把box从7.5调到9.0mAP50-95提升了1.2个点但mAP50略微下降。这说明模型把更多容量分配给了边界回归但过高的box权重会挤压分类分支的表现。另一个做法是提高输入分辨率1280分辨率下的定位精度明显优于640代价已经说过了。如果还想做得更细可以尝试给模型添加自注意力机制。YOLOv11本身已经引入了C3k2块和注意力机制但针对小目标可以在骨干网络的高分辨率特征图后添加一个额外的自注意力层。这类网络结构改动需要改Ultralytics的模型配置文件对不熟悉源码的人来说成本较高。我的建议是如果目标是快速落地拿结果先用SAHI切图把mAP拉上来如果追求极致精度再考虑魔改网络和引入注意力机制。6. 更进一步的思路网络结构优化与跨域应用这套YOLOv11SAHI方案跑通之后很多人会问下一步能怎么发展。这里给出几个扩展方向按收益从高到低排列。6.1 添加自注意力机制与特征融合改进虽然SAHI在推理阶段已经明显提升了小目标检测精度但模型本身的表征能力仍然决定了上限。如果希望从模型结构上进行改进可以优先尝试在骨干网络的浅层特征图上添加自注意力模块或者引入CARAFE等上采样算子来替代传统的最近邻或双线性插值让特征金字塔在融合时保留更多空间细节。Ultralytics支持通过修改yaml配置文件来调整网络结构这些改动并不需要重写整个训练流程。但这里要泼一盆冷水网络结构的改动在小目标检测上的提升幅度通常不如切图策略来得直接。如果算力有限或者项目周期短不要把精力花在魔改网络上先把数据、训练参数和SAHI切片参数调好收益更确定。6.2 目标跟踪场景中的SAHI应用VisDrone不仅有检测任务还有单目标跟踪和多目标跟踪任务。如果你做的是无人机视频流的实时检测与跟踪可以把SAHI和ByteTrack或DeepSORT结合起来。具体做法是每帧图像先经过SAHI切片检测然后把检测结果送入跟踪器用跟踪器的帧间关联来弥补检测器在单帧上的偶发漏检。实测这种方案能明显提升跟踪ID的稳定性。要注意的是切片推理在多帧重复运行会有大量冗余计算可以缓存上一帧的切片特征只对变化剧烈的区域重新推理这是典型的工程优化方向。6.3 扩展到红外小目标检测红外小目标检测是另一个热门应用常见于夜间监控、工业检测等场景。它的评价参数和可见光不一样通常关注信噪比、背景抑制因子、目标检测概率等指标。如果把YOLOv11SAHI这套方法迁移到红外图像上有两点需要额外关注一是红外图像通常没有鲜艳的颜色信息颜色增强类策略完全不适用训练时基本上只能依赖几何增强二是红外小目标可能只有3到5个像素现有检测器的下采样倍数限制了检测极限这时可以考虑把SAHI的slice_size调得更小比如256同时去掉部分下采样层。这些改动会显著增加计算量但在红外单帧图像上仍然可以做到实时或近实时处理。7. 写在最后的几句实在话回过头来看这套方案YOLOv11和SAHI都不是什么神秘的新东西但它们搭配起来确实解决了无人机小目标检测中最棘手的问题小目标在常规推理流程中信息丢失。切片推理的哲学很简单既然模型在小图上看得更清楚那就别强迫它直接看大图。这个思路不仅适用于VisDrone在遥感图像目标检测、工业质检、医疗影像分析等场景里都适用。实验过程里我踩过的坑比如VisDrone的ignored类别过滤、训练分辨率和推理分辨率不一致导致精度暴跌、SAHI切片参数对不同尺度的敏感度差异这些在官方文档里都不太容易直接找到答案。把它们写出来是希望后来的同学能少走弯路。另外多说一句推理结果的保存不是可选项项目交付时评估方要看的就是可复现的检测输出文件养成导出COCO JSON和可视化图的习惯会省去很多麻烦。最后分享一个我自己用的判断技巧拿到任何航拍检测需求先看一眼标注框的像素尺寸分布。如果超过一半的目标小于32x32那么无论用什么模型都必须把切片策略考虑进去。YOLOv11SAHI只是其中一种实现方式理解了背后的原理你也可以在其它模型复现同样的思路。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →