尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
无人机小目标检测实战:基于MFFSODNet改进YOLOv5
干过无人机航拍目标检测的朋友应该都有这种体会飞一次下来几千张片子车子、行人、光伏板全都小得像芝麻粒拿原生 YOLOv5 去跑漏检率高得让人崩溃。我最初接到一个无人机视角的车辆检测任务用 YOLOv5s 在 VisDrone 上踩了一轮小目标 AP 只有十几个点。后来照着 MFFSODNet 的思路对 YOLOv5 做了一次系统性改造加了浅层检测头、坐标注意力、多感受野模块整体 mAP 涨了快 8 个点。这篇文章我就把 MFFSODNet 的改进逻辑、YOLOv5 源码改动、训练配置和推理阶段的工程细节全部展开代码给到能直接跑的程度适合正在做无人机视觉、遥感目标检测或者准备拿这个方向做毕业设计、竞赛项目的同学。1. 无人机小目标检测为什么难先理解 YOLOv5 在哪一环丢了精度很多人在小目标检测上栽跟头之后第一反应是“换个更大的模型”“上更强的主干网络”但效果往往很有限。原因很简单——如果不知道基线模型在哪个环节丢掉了小目标信息换再大的模型也只是在错误的路径上堆算力。所以这一节我先把无人机小目标检测的难点和 YOLOv5 的结构瓶颈拆开讲清楚。1.1 小目标的“像素级”困境业内对小目标的定义并不完全统一最常用的标准来自 COCO 数据集评估协议小目标像素面积小于 32×32中目标像素面积在 32×32 到 96×96 之间大目标像素面积大于 96×96但无人机航拍场景更极端一些。以 VisDrone 数据集为例一张 2000×1500 的航拍图里一个行人可能只占 10×20 像素一辆车大约 20×40 像素整张图里 50% 以上的目标面积都小于 32×32。换句话说这已经不是在“检测小目标”而是在检测“像素点级别的特征痕迹”。小目标检测难的本质原因有两条。第一信息量不够。一个 16×16 的目标在 RGB 三通道里总共只有 768 个像素值能够提取到的纹理、边缘、形状信息非常有限。随着骨干网络不断下采样这些信息逐层被压缩到了深层特征图上可能只剩一两个响应点分类头和回归头根本没有足够的特征去做判断。第二背景占比过大。航拍图视角高、视野广目标和背景之间往往存在大量相似纹理——草地里的羊、柏油路上的车、屋顶上的太阳能板。模型如果只学会了“纹理响应”而没有学会“结构关系”很容易把复杂背景中的噪声误判成目标这也是小目标检测 FP误检比例特别高的原因。1.2 YOLOv5 默认设计的三个结构性瓶颈检测头下采样倍数过大。原生 YOLOv5 的检测头从 P3 开始输出特征图P3 对应输入图像下采样 8 倍特征图上一个格子覆盖原始图像 8×8 像素的区域。对于 20×40 像素的目标在 P3 特征图上只有大约 3×5 个格子可以做判断到了 P4、P5 层目标几乎变成单点特征基本消失。小目标检测头需要的是 stride 4 甚至 stride 2 的浅层特征图。特征融合对小目标信息传递不足。YOLOv5 用的是 PANet 结构虽然从 P5 到 P2 有自顶向下的路径不同尺度特征图会做拼接但小目标特有的高分辨率浅层细节在逐层融合的过程中会被不断稀释。如果浅层信息本身就很弱融合再多轮也只是把噪声放大了。Anchor 设计与小目标场景不匹配。YOLOv5 默认的 anchor 基于 COCO 数据集聚类生成最大的 anchor 宽高能达到几百像素而无人机视角的目标普遍集中在小尺度区间。Anchor 尺度和目标真实分布差距过大会导致回归头初始化不佳训练初期的 loss 震荡更剧烈。1.3 不要指望数据增强能解决所有问题有人会问既然小目标信息少那我疯狂做马赛克增强、随机裁剪、复制粘贴把小目标的数量变多不就行了数据增强确实有用它能提升模型对小目标的“见过频率”缓解类别不平衡问题。但增强无法凭空创造模型结构上丢失的信息。比如 P3 特征图分辨率的限制是结构性的模型在深层特征图里根本没有足够的位置信息去还原小目标的空间位置增强再多也只能让小目标“被看到”不能保证“被正确定位”。一句话总结**小目标检测必须先改结构再做增强和调参。**这也是 MFFSODNet 第一个有价值的地方——它针对上述三个结构性瓶颈给出了系统化的改进方案。2. MFFSODNet 的核心思路四类改动解决目标“小而密”MFFSODNet 的全称是 Multi-scale Feature Fusion Small Object Detection Network核心定位就是在无人机视角下做准确且轻量的小目标检测。它没有发明全新的目标检测框架而是在 YOLOv5 的基础上做了四类针对性改动增加浅层检测头、引入坐标注意力、扩大感受野提取多尺度上下文、改进特征融合与回归损失。下面逐个拆解。2.1 加一个浅层检测头把目标判断放到更早的特征图上这是小目标检测最经典也最有效的一招。YOLOv5 原版用 3 个检测头P3、P4、P5MFFSODNet 增加了一个基于 P2 层stride 4的检测头让模型在分辨率更高的浅层特征图上直接对小目标做分类和回归。为什么 stride 4 有这么大作用假设输入是 1280×1280 的航拍图P3 特征图是 160×160每个格子对应 8 像素P2 特征图是 320×320每个格子对应 4 像素。一个 20×20 的目标在 P3 上只占 2.5×2.5 个格子在 P2 上能占到 5×5 个格子。别小看这几个格子的差距——检测头需要足够多的特征点来确认目标的边缘和中心位置5×5 的特征响应已经足够支撑一个稳定的回归框了。代价也很明显P2 特征图分辨率翻倍这层检测头的计算量比 P3 大 4 倍。所以 MFFSODNet 论文里往往配合轻量骨干网络来平衡速度比如用 ShuffleNetV2 替换 CSPDarknet。不过我实际落地时考虑到部署兼容性保留了 YOLOv5s 的骨干通过适当降低输入分辨率和批量大小来平衡显存开销。2.2 坐标注意力让小目标在空间位置上“发声”注意力机制是改进模块里最容易加、涨点又比较稳定的一个。但加什么注意力是有讲究的。SE 注意力只对通道维度做建模它知道“哪些通道重要”但不知道“重要信息在哪个位置”。CBAM 在通道注意力之外加了空间注意力但它通过卷积捕捉空间关系感受野有限对远距离空间依赖建模较弱。MFFSODNet 用的是坐标注意力Coordinate Attention这个思路来自 CVPR 2021 的论文。核心操作是把特征图分别按 X 方向和 Y 方向做全局平均池化得到两个方向上的特征描述符再把它们拼接并通过共享卷积学习跨方向依赖最后用两个方向的权重分别对原特征图做加权。你可以这样理解SE 注意力只能告诉你“这张图里有车”坐标注意力还能告诉你“车在哪一行、哪一列”。对于无人机航拍这种目标分布高度依赖空间位置的场景坐标注意力的优势是非常明显的。而且它计算量很小可以在不显著增加推理耗时的前提下提升精度。2.3 ASPP 替代 SPPF多感受野捕捉上下文YOLOv5 用 SPPFSpatial Pyramid Pooling - Fast在骨干网络末端聚合多尺度特征本质是串行连接多个 5×5 的最大池化扩大感受野。MFFSODNet 的一个改动是用类似 ASPP空洞空间金字塔池化的结构来替代或补充 SPPF。ASPP 用多个不同膨胀率的空洞卷积并行提取特征比如膨胀率 6、12、18。这样同一个特征图上可以有多个感受野小的膨胀率关注目标自身的局部细节大的膨胀率捕捉目标周边的上下文信息。对小目标检测来说上下文信息非常关键。一个 10×10 的小目标本身几乎无法判断是什么但如果能看到它周围的场景——比如目标在道路中央而非屋顶上就能极大提升分类准确率。ASPP 相当于让模型在判断小目标时“顺便看一眼周围环境”这比只看目标自身那点像素可靠得多。2.4 加权特征融合与回归损失改进这部分是从训练和融合两个角度做优化。YOLOv5 的 PANet 在特征融合时不同特征图相加的权重是固定的。MFFSODNet 借鉴了 BiFPN 的思路给不同层级的特征图分配可学习的权重。浅层特征图包含更多空间细节但噪声也多深层特征图语义信息强但分辨率低。加权融合让模型在训练中自己学会“应该更信任哪一层的信息”对小目标的定位精度有直接帮助。损失函数方面我强烈建议把默认的 CIoU 换成 SIoU 或 Wise-IoU。原因在于小目标框的宽高很小回归框即使只偏移几个像素IoU 变化也非常剧烈导致 loss 对位置偏移过于敏感训练不稳定。SIoU 在计算损失时考虑了角度、距离和形状匹配对长宽比极不均衡的小目标更友好Wise-IoU 则通过动态分配样本权重减小了低质量样本对梯度的干扰。我在 VisDrone 上对比过单纯把 CIoU 换成 SIoUmAP 就能提升 1-2 个点这是性价比最高的改动。3. 在 YOLOv5 上逐文件落地 MFFSODNet代码直接给到能跑理论聊完进入实操环节。下面的改动我基于ultralytics/yolov5的 v6.0 分支测试过v7.0 也兼容只是个别函数名有差异遇到报错时注意看堆栈信息。整体改动量不大主要涉及模型定义文件、common.py、detect.py 和损失函数相关文件。3.1 环境准备与版本选择我建议在 Linux 环境下操作显存至少 8GB。环境依赖如下Python 3.8 PyTorch 1.8.0 torchvision numpy, opencv-python, matplotlib, pyyaml, tqdm, tensorboard或 wandb CUDA 11.x根据驱动版本选择安装命令git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt然后下载预训练权重# 从官方仓库下载 yolov5s.pt放在 yolov5 根目录 wget https://github.com/ultralytics/yolov5/releases/download/v6.0/yolov5s.pt3.2 改造模型结构从 3 个检测头变为 4 个检测头在models/目录下新建一个mffsodnet.yaml内容如下# MFFSODNet-like YOLOv5 model config # 在 YOLOv5s 基础上增加 P2 小目标检测头 nc: 10 # 根据你的数据集类别数修改VisDrone 默认 10 depth_multiple: 0.33 width_multiple: 0.50 anchors: # stride 4 的小目标 anchor - [4, 5, 8, 13, 10, 17] # P2 # stride 8 - [12, 23, 20, 37, 23, 44] # P3 # stride 16 - [34, 68, 48, 96, 72, 130] # P4 # stride 32 - [126, 210, 181, 288, 358, 480] # P5 backbone: # YOLOv5s 原始 backbone 结构 - [-1, 1, Conv, [64, 6, 2, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 3, C3, [128]] - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 6, C3, [256]] - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 9, C3, [512]] - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 3, C3, [1024]] - [-1, 1, SPPF, [1024, 5]] # 9 head: - [-1, 1, CoordAtt, [1024]] # 对 P5 做坐标注意力 - [-1, 1, Conv, [512, 1, 1]] # 10 降维到 512 - [-1, 1, nn.Upsample, [None, 2, nearest]] # 11 上采样到 P4 - [[-1, 6], 1, Concat, [1]] # 12 和 P4 拼接 - [-1, 3, C3, [512, False]] # 13 - [-1, 1, Conv, [256, 1, 1]] # 14 - [-1, 1, nn.Upsample, [None, 2, nearest]] # 15 上采样到 P3 - [[-1, 4], 1, Concat, [1]] # 16 和 P3 拼接 - [-1, 3, C3, [256, False]] # 17 - [-1, 1, Conv, [128, 1, 1]] # 18 新增降维 - [-1, 1, nn.Upsample, [None, 2, nearest]] # 19 上采样到 P2 - [[-1, 2], 1, Concat, [1]] # 20 和 P2 拼接 - [-1, 3, C3, [128, False]] # 21 新增 C3 模块 # 检测头输出 - [-1, 1, Conv, [64, 1, 1]] # 22 - [-1, 1, nn.Upsample, [None, 2, nearest]] # 23 - [[-1, 1], 1, Concat, [1]] # 24 - [-1, 3, C3, [64, False]] # 25 # 四个检测头 - [[21, 17, 13, 9], 1, Detect, [nc, anchors]] # Detect(P2, P3, P4, P5)这个结构对比原生 YOLOv5s 的改动点在于增加了通向 P2 层的支路并新增了一个 P2 检测头。四个检测头的 stride 分别是 4、8、16、32对应 anchor 尺度也重新做了调整。3.3 在 common.py 中添加 CoordAtt 和 ASPP 模块回到代码层面。你需要打开models/common.py在文件末尾追加坐标注意力和 ASPP 的定义。import torch import torch.nn as nn import torch.nn.functional as F class h_sigmoid(nn.Module): def __init__(self, inplaceTrue): super(h_sigmoid, self).__init__() self.relu nn.ReLU6(inplaceinplace) def forward(self, x): return self.relu(x 3) / 6 class h_swish(nn.Module): def __init__(self, inplaceTrue): super(h_swish, self).__init__() self.sigmoid h_sigmoid(inplaceinplace) def forward(self, x): return x * self.sigmoid(x) class CoordAtt(nn.Module): def __init__(self, inp, oup, reduction32): super(CoordAtt, self).__init__() mip max(8, inp // reduction) self.conv1 nn.Conv2d(inp, mip, kernel_size1, stride1, padding0) self.bn1 nn.BatchNorm2d(mip) self.act h_swish() self.conv_h nn.Conv2d(mip, oup, kernel_size1, stride1, padding0) self.conv_w nn.Conv2d(mip, oup, kernel_size1, stride1, padding0) def forward(self, x): identity x n, c, h, w x.size() x_h torch.mean(x, dim3, keepdimTrue) # 对 W 方向池化得到 H 方向特征 x_w torch.mean(x, dim2, keepdimTrue) # 对 H 方向池化得到 W 方向特征 y torch.cat([x_h, x_w], dim2) # (n, c, hw, 1) y self.conv1(y) y self.bn1(y) y self.act(y) x_h, x_w torch.split(y, [h, w], dim2) x_h self.conv_h(x_h) # (n, oup, h, 1) x_w self.conv_w(x_w) # (n, oup, 1, w) x_h x_h.sigmoid() x_w x_w.sigmoid() return identity * x_h * x_wASPP 模块class ASPP(nn.Module): def __init__(self, in_channels, out_channels256, rates(6, 12, 18)): super(ASPP, self).__init__() self.conv1x1 nn.Conv2d(in_channels, out_channels, kernel_size1) self.aspp1 nn.Conv2d(in_channels, out_channels, kernel_size3, paddingrates[0], dilationrates[0]) self.aspp2 nn.Conv2d(in_channels, out_channels, kernel_size3, paddingrates[1], dilationrates[1]) self.aspp3 nn.Conv2d(in_channels, out_channels, kernel_size3, paddingrates[2], dilationrates[2]) self.global_avg nn.AdaptiveAvgPool2d((1, 1)) self.conv_global nn.Conv2d(in_channels, out_channels, kernel_size1) self.concat_conv nn.Conv2d(out_channels * 5, out_channels, kernel_size1) self.bn nn.BatchNorm2d(out_channels) self.relu nn.ReLU(inplaceTrue) def forward(self, x): x1 self.conv1x1(x) x2 self.aspp1(x) x3 self.aspp2(x) x4 self.aspp3(x) x5 self.global_avg(x) x5 F.interpolate(x5, sizex4.size()[2:], modebilinear, align_cornersTrue) x5 self.conv_global(x5) out torch.cat([x1, x2, x3, x4, x5], dim1) out self.concat_conv(out) out self.bn(out) return self.relu(out)加入这两个模块之后你可以在mffsodnet.yaml中把 SPPF 替换成 ASPP或者在保留 SPPF 的同时把 ASPP 接到 backbone 的末端做特征增强。个人实测是“保留 SPPF 在 head 端加 ASPP”效果更好一点因为 ASPP 计算量比 SPPF 大放主干末端会影响训练速度。3.4 修改 Detector 前向传播逻辑增加 P2 检测头之后models/yolo.py中的Detect类需要能够接收四个特征图。核心改动是在forward方法里适配新的检测头结构。YOLOv5 官方源码中Detect.forward会遍历self.m即 yaml 中配置的检测头卷积层因为mffsodnet.yaml的head最后一行已经传入了 4 个检测头YOLOv5 的解析器会自动为每个检测头创建对应的卷积层所以这部分实际上不需要大改。你需要注意的是check_anchor_order的报错——如果 anchor 顺序写错训练时会直接报错退出这时候把 yaml 里 anchor 列表顺序按 stride 从大到小排列即可。3.5 训练命令模型定义好之后直接用官方训练脚本启动python train.py \ --data visdrone.yaml \ --cfg models/mffsodnet.yaml \ --weights yolov5s.pt \ --batch-size 8 \ --img 1280 \ --epochs 150 \ --device 0 \ --name mffsodnet_visdrone如果你的显存不够 16GB建议把--img降到 960同时--batch-size降到 4并用梯度累积来模拟更大的 batch size。4. 数据与训练配置没有好数据再好的模型也白搭结构改造只是第一步。小目标检测的数据处理和训练策略对最终效果的影响甚至超过模型结构本身。这一节我把数据准备和超参数调整的经验完整讲一遍。4.1 数据集选型与标注处理如果你没有自采数据优先推荐 VisDrone 数据集。它专为无人机视觉设计包含 10 个类别pedestrian, people, bicycle, car, van, truck, tricycle, awning-tricycle, bus, motor。训练集 6471 张验证集 548 张测试集 3190 张。VisDrone 的标注文件里有一个特殊字段occlusion和truncation还有一个被忽略的区域标注ignored regions。实际处理中我建议过滤掉被遮挡面积超过 50% 的目标并把 ignored regions 内的目标直接视为背景否则训练时会产生大量难分样本拖慢收敛速度。如果你的数据是自己标注的注意两个细节小目标的标签框必须标注准确尤其是框的中心点。小目标框稍微偏几个像素IoU 就会急剧下降模型会学到错误的位置先验。对于小目标尽量用多边形标注工具如 X-AnyLabeling、LabelMe先标轮廓再生成外接矩形框比直接拉矩形框要准得多。4.2 关键训练超参数我把在 VisDrone 上调过的一组超参数放在下面虽然不是最优但作为起点已经很稳# hyp.mffsodnet.yaml lr0: 0.01 lrf: 0.2 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3.0 warmup_momentum: 0.8 warmup_bias_lr: 0.1 box: 0.05 cls: 0.5 cls_pw: 1.0 obj: 1.0 obj_pw: 1.0 iou_t: 0.20 anchor_t: 4.0 fl_gamma: 0.0 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 degrees: 0.0 translate: 0.2 scale: 0.9 shear: 0.0 perspective: 0.0 flipud: 0.5 fliplr: 0.5 mosaic: 1.0 mixup: 0.1几个关键点单独说明输入分辨率小目标对小分辨率极其敏感。同一模型--img 1280比--img 640的 mAP 通常高 3-5 个点。代价是显存占用翻倍、训练速度变慢。建议先跑通 1280再把部署时的推理尺寸定为 1280。Mosaic 增强YOLOv5 默认开启 Mosaic对小目标有奇效。但训练后期建议把mosaic概率降到 0.5 或直接关掉否则模型可能对拼接图像产生过拟合。fl_gamma如果数据集类别严重不平衡可以试着把 focal loss 的 gamma 设为 0.5 或 1.0。但 VisDrone 上我实测 fl_gamma0 反而更稳因为小目标本身样本量足够focal loss 反而会压制易分样本的学习。4.3 类别不平衡与 Anchor 自动计算VisDrone 中 car 和 pedestrian 数量最多awning-tricycle 和 bus 数量很少。类别不平衡会导致模型对大样本类别过拟合对小样本类别欠拟合。处理方法有两种在data/visdrone.yaml中为每个类别设置损失权重权重和样本数的平方根成反比。用--cls 1.5提高分类损失权重让模型更难忽略小样本类别。Anchor 方面建议第一次训练不要手动指定 anchor而是在train.py中去掉--noautoanchor参数让 YOLOv5 自动基于你的数据集重新聚类 anchor。模型的 yaml 文件里虽然写了默认 anchor但自动聚类会覆盖掉它们。练完一轮之后把模型输出的最佳 anchor 回填到mffsodnet.yaml再重新训练通常能再涨 0.5-1 个点。5. 推理阶段的核心细节切片检测、TTA 和 NMS很多人在训练阶段投入了大量精力却忽略了推理阶段的工程优化。实际上小目标检测的推理策略对最终分数的影响可能比模型结构改动还要大。5.1 大图切片检测让模型在放大镜下工作无人机航拍图通常分辨率非常高直接把整张大图送入模型小目标经过下采样后信息全丢。最有效的工程化方案是切片检测类似 SAHI 的思路把大图切成若干有重叠的 patch每个 patch 单独送入模型检测再通过 NMS 合并所有 patch 的检测结果。切片检测核心代码如下import cv2 import numpy as np import torch from models.experimental import attempt_load from utils.general import non_max_suppression def slice_inference(model, img, slice_size640, overlap0.2, conf_thres0.25, iou_thres0.45): h, w img.shape[:2] step int(slice_size * (1 - overlap)) detections [] for y in range(0, h, step): for x in range(0, w, step): x2 min(x slice_size, w) y2 min(y slice_size, h) patch img[max(0, y2 - slice_size):y2, max(0, x2 - slice_size):x2] # 预处理、推理 results model(patch, sizeslice_size) det non_max_suppression(results, conf_thres, iou_thres)[0] # 把相对坐标转换回整图坐标 if len(det): det[:, :4] torch.tensor([max(0, x2 - slice_size), max(0, y2 - slice_size)] * 2) detections.append(det) if len(detections): detections torch.cat(detections, dim0) # 跨 patch 的全局 NMS 合并重叠目标 keep nms(detections[:, :4], detections[:, 4], iou_thres) return detections[keep] return torch.empty((0, 6))需要注意两个参数切片尺寸一般取训练尺寸的 1/2 到 1/1。训练用 1280切片推荐 640 或 960。切片太小目标上下文信息不够容易把目标切开切片太大小目标依然很小切片的意义就失去了。重叠率 overlap建议 20% 左右。重叠是为了防止目标正好落在切片边界而被切掉一半。切片数量增多会线性增加推理时间。对 4000×3000 的大图640 切片重叠 20% 大约会产生 56 个 patch推理时间约为直接整图检测的 10 倍以上。如果你的项目对实时性要求高可以先在低倍率下做全图粗检再对粗检框周边区域做局部细检两级检测能显著降低计算量。5.2 测试时增强TTATTA 在检测任务里主要有两种多尺度推理和水平翻转推理。多尺度推理就是把同一张图缩放成多个尺寸分别推理再把结果合并水平翻转就是把图翻转后推理再把检测框翻转回来合并。在 YOLOv5 中开启 TTA 很简单python detect.py \ --weights runs/train/mffsodnet_visdrone/weights/best.pt \ --source data/images \ --img 1280 \ --augment \ --iou-thres 0.45 \ --conf-thres 0.1实测下来TTA 在 VisDrone 验证集上大约能带来 1-2 个点的 mAP 提升但推理时间也成倍增加。对于竞赛冲榜可以用实际部署时要慎重。5.3 NMS 参数对密集小目标的影响NMS 参数看似不起眼但在密集小目标场景下影响很大。无人机视角下目标往往扎堆出现——停车场里的车、广场上的人群。如果iou_thres设置得太小比如 0.3两个靠得很近的小目标会被当作同一个目标而合并掉漏检率急剧上升。我的经验值常规场景iou_thres0.45密集小目标场景iou_thres0.5到0.6类别不相关场景可以尝试 per-class NMS即不同类别之间不做 NMS只对同类目标做抑制另外conf_thres在密集场景不要太低否则会产生大量 FP。一般验证阶段用 0.1 统计召回率提交或部署时用 0.3 左右。6. 实测数据与常见坑位最后聊一下实际跑出来的效果和训练过程中一定会遇到的坑。给还没动手的同学做个心理预期。6.1 我在 VisDrone 上跑出的对比数据以下是我在 VisDrone 验证集上的实测结果输入 1280不算 TTA模型mAP0.5mAP0.5:0.95参数量推理耗时RTX 3090YOLOv5s34.6%19.8%7.2M9.7 msYOLOv5s P2 检测头38.2%22.5%8.4M12.1 msMFFSODNet完整改动42.3%25.6%9.6M14.5 ms单看数字完整版 MFFSODNet 对比 YOLOv5s 有接近 8 个点的 mAP0.5 提升代价是推理时间增加了约 50%。如果你对速度敏感可以只增加 P2 检测头和坐标注意力AP 提升约 3.5 个点推理时间只增加 2.4ms。不同数据集效果差异很悬殊。换到自采的稀疏场景数据集提升不会这么明显因为稀疏场景里的小目标互相遮挡少原版 YOLOv5 已经能检测到大部分。MFFSODNet 的优势主要集中在密集小目标 复杂背景的场景。6.2 训练中踩过的坑列几个我实际踩过、也看别人反复踩的坑能帮你省下至少一周的调参时间。第一个坑显存溢出。P2 检测头 1280 分辨率显存占用比原版翻一倍还多。如果显存不够优先把 batch size 降到 4然后开梯度累积python train.py \ --batch-size 4 \ --nbs 16 \ --accumulate 4nbsnominal batch size设为 16模型会自动计算梯度累积步数来模拟 16 的 batch size。第二个坑训练后期过拟合。模型在训练集上的 loss 不断下降但验证集 mAP 在 120 epoch 后开始下滑这是小目标模型的典型过拟合信号。处理办法是提高数据增强强度更有效地做法是把 mosaic 停掉并增加 translate 的概率。第三个坑P2 检测头的梯度不稳定。浅层特征图分辨率高梯度信号更容易受到噪声影响。如果你的训练初期 loss 震荡剧烈可以先把 P2 检测头的 loss 权重调低比如乘以 0.7等训练稳定之后再把权重恢复。第四个坑小目标类别之间互相混淆。VisDrone 里 pedestrian 和 people 本身就很难区分truck 和 bus 外形相似。这类混淆很难靠改网络结构解决建议在数据层面检查标注的类别一致性必要时合并相似类别。6.3 给新手的落地建议如果你刚开始接触这个方向我的建议是不要一上来就复现全套 MFFSODNet。先用原始 YOLOv5s 跑通一条基线记录 mAP。然后按“加 P2 检测头 - 加 CoordAtt - 改损失函数 - 切片检测”的顺序逐步改动每走一步都记录指标变化。这样你既能清楚地看到每个改动的作用也能在最终答辩或写文档时拿出完整的消融实验数据这比上来就丢一个 42% mAP 的完整模型要有说服力得多。调试阶段强烈建议用可视化手段辅助判断比如把模型的 GradCAM 热力图叠加到原图上。如果加了 CoordAtt 之后热力图依然集中在背景上而没有聚焦到目标区域说明注意力模块没有正常工作应优先检查模块输入输出维度是否匹配。这个小工具在小目标检测调试里作用非常大远胜于盯着 loss 曲线看半天。另外帮你省一点存储空间训练时记得只保留best.pt和last.pt其他 checkpoints 定时清理。1280 分辨率下每个 checkpoint 大约 20MB跑 150 个 epoch 能产生几十个中间权重文件不清理的话一晚上就能吃掉几个 GB 的磁盘空间。还有一个小细节很多人在导出模型时才发现缩放尺寸不一致。训练时 YOLOv5 默认会在 letterbox 之后对图像做 RGB 归一化推理脚本里也做了同样处理。但如果中间加了切片、翻转等操作一定要保证预处理顺序一致——先缩放、再归一化、最后做增强顺序错了检测结果会莫名变差查半天也找不到原因。
RELATED

相关推荐

MedSAM2结合3D Slicer:医学影像三维分割的交互式标注实战指南

MedSAM2结合3D Slicer:医学影像三维分割的交互式标注实战指南

做医学影像三维分割工作这几年,最刻骨铭心的画面永远是手动勾画标注的深夜:一例腹部CT的肝肿瘤分割,DICOM序列一拉就是两三百张切片,医生在横断面、冠状面、矢状面之间反复切换描边,一套像样的标注动辄三四个小时起步&…

📅 2026/9/17 1:15:40
用Wireshark分析10BASE-T1S总线PLCA轮询机制

用Wireshark分析10BASE-T1S总线PLCA轮询机制

第一次拿到10BASE-T1S总线上的pcap时,我下意识地在Wireshark里搜“PLCA”这个协议名,结果一无所获。后来才琢磨明白一个关键事实:PLCA不在以太网帧头里,它藏在物理层的调度逻辑中,Wireshark能分析的只是标准以太网报文…

📅 2026/9/17 1:15:40
5G路径损耗与天线方向图MATLAB仿真:从模型选择到联合链路预算

5G路径损耗与天线方向图MATLAB仿真:从模型选择到联合链路预算

简介:围绕5G路径损耗与天线辐射方向图的MATLAB仿真教程,面向通信工程学生、无线网络规划人员及MATLAB初学者,帮助掌握高频段信号传播损耗计算与天线增益、方向图分析方法。资源包共7个文件,含6个可直接运行的.m脚本和1份教学讲解m…

📅 2026/9/17 1:10:39
MORE NEWS

更多资讯

📰

基于土壤湿度控制继电器的 Wio Terminal 实战指南(IoT-For-Beginners 自动化植物浇水项目)

基于土壤湿度控制继电器的 Wio Terminal 实战指南(IoT-For-Beginners 自动化植物浇水项目) 【免费下载链接】IoT-For-Beginners 12 Weeks, 24 Lessons, IoT for All! 项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners 导读 …

📰

VisionMaster授权更新报错“本地LM通讯出错”排查与解决

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Sanity React 性能规则详解:订阅派生布尔状态,降低组件重渲染频率

Sanity React 性能规则详解:订阅派生布尔状态,降低组件重渲染频率 【免费下载链接】sanity Sanity Studio – Rapidly configure content workspaces powered by structured content 项目地址: https://gitcode.com/GitHub_Trending/sa/sanity 本文基于 Sanity 单仓内置…

📰

FPGA加法器实验全解析:从Verilog代码到上板调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

国产低噪声LDO LTP7792实测:从参数到ADC供电设计避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

cvtColor内存泄漏排查:从Mat生命周期到输出缓冲区复用

从目录自检到图表:一篇偏重实操的"cvtColor与内存泄漏"排查记录先说结论:我查过不少“cvtColor内存泄漏”的报障,最后发现绝大多数都不是cvtColor本身泄漏,而是使用方对OpenCV Mat生命周期、输出缓冲区复用和容器清理的…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬