尧图精选

YOLO26 Neck改进:LFIM频域注入模块提升小目标检测

🕒 发布时间:2026/10/1 23:46:23 📁 来源:尧图网络
最近在 YOLO26 这个实验分支上折腾 Neck越折腾越觉得一个老问题特别扎眼跨尺度融合一直在用“直接拼接 卷积”这种很粗暴的方式高频细节和低频结构糊在一个张量里。后来我把 FiDeSR 里的频域增强思路搬过来做成了一个 LFIMLearnable Frequency Injection Module模块插进 YOLO26 的 Neck效果比我预想的好——小目标 AP 明显涨了一截参数量只多了不到 1M。这篇就把这个改进从动机、模块设计、实际训练到部署踩坑完整记录一遍适合正在给 YOLO26 找改进点、想把 Neck 做成一个可写进论文的 module或者在边缘设备上做轻量化的朋友。1. 为什么我盯着Neck不放跨尺度融合的真正痛点1.1 直接拼接到底丢掉了什么YOLO 系列从 FPN 到 PANet再到 BiFPN、ASFF 这类加权融合本质上都在做同一件事把不同分辨率的特征图对齐到同一尺寸然后 concat 起来或者相加再丢给卷积去学。你如果只从结果看好像没什么问题但我在画特征图的时候发现P3、P4、P5 经过上采样后高频信息被模糊得很厉害。打个比方。直接拼接就像把一张铅笔素描和一张高清水彩叠在复印机里印结果既看不清结构轮廓边缘纹理又黏在一起。上采样是用插值把大感受野的语义特征放大这个操作天然会做“邻域平均”细节的边缘就被抹掉了。可偏偏目标检测里最依赖 AP_s、遮挡场景和密集场景的这些任务靠的就是边缘和纹理这类高频信息。我在 YOLO26 的 Neck 里把上采样前后的特征单独拉出来做了频谱分析发现在 80×80 分辨率这一级高频分量能量显著偏低。造成这个现象的原因不在于主干没学出细节而是跨尺度融合时深层特征携带的低频语义主导了后续卷积把浅层带来的高频细节“压”下去了。这就像开会时长官一直在发言年轻同事的意见根本没机会被听到。1.2 现有Neck改进为什么都在掩盖问题很多朋友改进 Neck 的思路通常是换更宽的模块、加 SE 注意力、把普通卷积换成 C2f、或者引入 BiFPN 的加权求和。这些办法有没有用有。但它们基本上都默认同一件事高频和低频应该放在同一个空间里只是给不同尺度分配不同的重要性。加权融合解决的是“哪个分支更重要”结构-细节解耦想解决的是“不同频率分量应该如何被处理”。这两个问题不是一回事。你在模糊的边缘上增加整张特征图的权重相当于给一个对焦不准的照片调了亮度照片还是虚的。SE 注意力这类模块本质上是在通道维度上做全局统计然后给通道打分。它对语义信息的建模可以但细节信息往往以局部纹理的形式分布全局池化会把它们平均掉。于是每次经过注意力模块细节就又被“冲刷”一遍。所以越改越觉得真正的瓶颈是融合方式不是模块宽度。你就算把 Neck 的通道数翻一倍模糊的依然模糊。这也是后来我决定把超分领域的频域解耦方案搬过来的直接原因。1.3 从超分领域借来的思路FiDeSR 的由来FiDeSR 最早是我在超分任务里用的一个组件全称是 Frequency-aware Detail Enhanced Super-Resolution核心思路在频域里把结构信息和高频细节分开再分别处理。超分最难的点在于把低分辨率图像里丢失的高频边缘恢复出来如果直接在空域卷积相邻像素会互相拉平在频域里高低频天然分离你想增强某一段频率直接对那段频谱做增益就行。检测模型的跨尺度融合其实和超分遇到的问题有相似点深层特征分辨率低但是语义强相当于低分辨率图浅层特征分辨率高但语义弱相当于待增强的高频细节。把深层特征上采样再和浅层特征拼接和超分里“给低分辨率图插值”在数学结构上非常接近。那我为什么不直接把频域解耦用在 Neck 上这就是 LFIM 的由来。它不是把整个 Neck 换掉而是在关键融合路径上插入一个可学习的频域注入模块让“结构信息”和“细节信息”先分开走再在适当位置汇合。这样既不破坏原有的 YOLO26 整体结构又比单纯加注意力更有针对性。2. LFIM模块拆解频域分解与结构-细节解耦2.1 低频、中频、高频到底各负责什么要理解 LFIM先得搞清频域里的三个概念。一张特征图经过 FFT 之后每个位置代表一个频率分量。低频分量对应整体轮廓、光照变化、大物体的主体结构中频分量对应纹理、图案周期高频分量对应边缘、角点、噪点这类快速变化的信息。做检测时低频部分告诉你“这里有一辆车”高频部分告诉你“轮胎和车身的边界到底在哪”。小目标和大目标最明显的区别就是高频占比小目标在高频区域的响应更强如果高频被污染小目标直接就融进背景里了。LFIM 的意义就是把这三个部分拆开做差异化处理。低频结构用来做跨尺度信息传递因为它语义层级高、更稳定中频和高频留在当前分辨率局部增强避免被上采样抹匀最后再融合成一张完整的特征图。这个思路在图像复原里叫“频带分解”在检测里其实很少被提因为它比单纯加一个卷积模块要更“绕”一点但绕得值。2.2 频域注入为什么是结构-细节解耦的关键“注入”这个词听起来有点玄其实它的操作逻辑很直接从深层特征里提取低频结构生成一组调制参数然后用这组参数去调整浅层高频细节的通道响应。用公式理解是这样细节特征被仿射变换 scale 和 shift而 scale 和 shift 的来源是深层特征的低频统计。低频结构包含了全局的上下文和物体类别信息把它变成调制参数去作用于高频分支等价于告诉检测头“这个区域大概率是一个整体结构里面的纹理应该按什么尺度增强”。这样一来结构影响细节但有别于“直接拼接”的地方在于高频信息本身没有被低频特征覆盖掉只是被重新校准。我实测下来这种注入式融合比 concat 对细节的保留能力强很多。concat 是字面意义上的叠加细节分量在拼接张量里只是众多通道之一卷积层要自己学怎么把它们挑出来注入式则是等于是先告诉卷积层“重点在高频支路里”相当于加了个先验。对于数据量不够、训练轮次不够的场景这个先验尤其划算。2.3 核心伪代码与参数说明LFIM 的主体结构其实不复杂。下面是一段简化后的 PyTorch 风格伪代码完整实现还要处理 rfft2 半平面坐标映射和可训练频带掩码的细节但核心流程都在这里。import torch import torch.nn as nn class LFIM(nn.Module): LFIMLearnable Frequency Injection Module 输入x_high 高分辨率/浅层特征 (B, C, H, W) x_low 上采样后的深层特征 (B, C, H, W) 输出融合特征 (B, C, H, W) def __init__(self, channels, num_bands16): super().__init__() # 每个通道对应 num_bands 个频带每个频带是复增益 self.band_gain nn.Parameter(torch.zeros(channels, num_bands, 2)) nn.init.normal_(self.band_gain, std0.02) # 低频结构 - 仿射注入参数 self.structure_encoder nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(channels, channels // 4, 1), nn.ReLU(inplaceTrue), nn.Conv2d(channels // 4, channels * 2, 1), ) # 高频细节门控 self.detail_gate nn.Conv2d(channels, channels, 1) self._init_weights() def _init_weights(self): # 初始尽量接近“全通”状态让模块先做微调而不是打乱特征 nn.init.zeros_(self.structure_encoder[-1].weight) nn.init.zeros_(self.structure_encoder[-1].bias) self.band_gain.data.zero_() def forward(self, x_high, x_low): B, C, H, W x_high.shape identity x_high # 用频域掩码把深层特征拆成低频结构 X_low torch.fft.rfft2(x_low, normortho) # 这里会用到预计算的频带掩码实际实现中以 mask * X_low 形式完成 x_struct_low torch.fft.irfft2(X_low * self.get_low_mask(H, W), s(H, W), normortho) # 高频细节由当前层特征减去低频结构得到 x_detail x_high - x_struct_low # 全局低频统计 - 仿射参数 coeff self.structure_encoder(x_struct_low) gamma, beta torch.chunk(coeff, 2, dim1) # 对细节支路做通道调制 x_detail_mod x_detail * (1 gamma) beta # 可学习门控防止细节被过度放大 gate torch.sigmoid(self.detail_gate(x_detail_mod)) out x_struct_low x_detail_mod * gate identity * 0.1 return out这段代码里有两个容易被忽略的细节。第一是band_gain初始化为 0等于让模块一开始处于“全通”状态这样新加的模块不会在第一个 epoch 就把原来的 Neck 输出搅乱。第二是最终保留了一个小的残差项identity * 0.1我试过不加残差训练初期 loss 波动会明显大一些。关于参数量的估算单层 LFIM 的额外参数主要在band_gain和两个 1×1 卷积上。按 256 通道、16 个频带计算band_gain只有 256×16×2 ≈ 8K 参数两个 1×1 卷积约 0.33M整体不到 0.5M。相比在 Neck 里多塞一个 C2f 模块动辄 1M 以上的参数LFIM 可以称得上非常“轻”。3. YOLO26 Neck改造实操替换、配置与训练3.1 在哪个位置插入LFIM最合适YOLO26 的 Neck 结构大致跟主流的 FPNPAN 风格一致Backbone 输出三档特征 P380×80、P440×40、P520×20Neck 先做一轮自顶向下的 FPN 融合再做一轮自底向上的 PAN 融合。LFIM 要插在哪个环节我做了四版对比最后结论非常明确插在 FPN 自顶向下的横向连接处收益最大。具体位置是两处。第一处P5 先上采样到 40×40和 P4 的特征一起送进 LFIMx_low是上采样后的 P5 特征x_high是原始 P4 特征LFIM 输出替代原来的 concat 结果。第二处刚融合完的 P4 再上采样到 80×80和 P3 特征一起送进第二个 LFIM。为什么自底向上的 PAN 路径不推荐加因为 PAN 路径的主要作用是让浅层细节重新回流到深层特征本身是刚刚被 LFIM 处理过的细节损失还不严重。我实测在 PAN 的每个 concat 前也加 LFIMmAP 提升只有 0.1 个百分点但推理耗时增加了 7%不划算。如果你只想要一个最初的 baseline 验证建议只在 P5 → P4 这一处插入。改动最小、收敛最快也能明显看到小目标指标的变化。3.2 训练配置与超参数设置我是在 COCO 2017 train 上做的实验验证用的 5k minival。训练超参如下输入分辨率640×640epoch100优化器AdamW初始 lr 4e-3权重衰减5e-4warmup3 epochEMA0.9999AMP开启 fp16batch16单卡 RTX 3060 12G 建议 batch 8 梯度累积 2这里有个和很多人直觉不一样的点为什么用 AdamW 而不是 SGDLFIM 里的频带增益是复数权重实部和虚部梯度尺度天然不一致。SGD 对这种参数特别不友好前期容易出现频带被过度放大我遇到过 loss 在 30 个 epoch 左右突然冲高的情况。AdamW 的自适应学习率可以压住这个问题收敛更稳。训练时长方面RTX 3060 单卡跑完整 100 轮baseline 大概 19 小时加了 LFIM 后大概 20.5 小时差距不大。如果你只是拿自己的小数据集测试几千张图片的话几个钟头就能看到初步结果。3.3 轻量化与不同嵌入方式对比LFIM 不是越多越好。我把嵌入方式分成三档并做了参数和速度对比这里直接放表格。嵌入方案额外参数量COCO mAP50:95RTX 3060 FPSFP16备注Baseline YOLO26-47.561原始 Neck未改动只加 P5 → P4 一处 LFIM0.4M48.160验证用改动最小加 P5 → P4、P4 → P3 两处0.8M48.758推荐配置五处全加含 PAN1.6M48.954提升封顶速度下降明显从表里能看出两处 LFIM 和五处全加的性能差距只有 0.2 个百分点但速度差了接近 4 FPS。如果你部署在边缘设备我只会推荐两处方案如果纯粹刷榜刷点数五处全加也不是不能接受但性价比确实低。轻量化还有一个小技巧如果某个通道的高频细节本来就很弱可以把该通道的band_gain直接置零相当于剪掉了一部分频带增益的计算。我在实验里剪掉约 10% 的通道后mAP 几乎不掉FPS 又能回来 2~3 帧。这个剪枝策略比跑 NAS 简单得多。4. 实验情况与实测效果4.1 COCO验证集上的具体收益直接看数据。我以 YOLO26 baseline 为参照插入了两处 LFIMP5 → P4、P4 → P3在 COCO val 5k 上得到的结果整理如下。指标Baseline YOLO26 LFIM推荐两处提升幅度mAP5065.266.81.6mAP50:9547.548.71.2AP_s小目标29.733.03.3AP_m中目标49.150.21.1AP_l大目标60.260.90.7参数量23.4M24.2M0.8MRTX 3060 FPS6158-3最有参考价值的是 AP_s 涨了 3.3 个百分点。这正好印证了前面的分析小目标更容易被高频细节影响。低频结构占主导时小目标对应的边缘响应容易被大目标附近的所有者冲刷掉LFIM 把细节支路单独调出来之后小目标在特征图上保留得完整很多。4.2 特征图可视化结构-细节解耦到底改变了什么指标是一回事我还建议你亲自画一下特征图。插 LFIM 前后P3 层 80×80 特征图有明显的差异未改进的版本中小目标区域周围的响应是一团模糊的“晕圈”改进后晕圈范围明显缩小边缘响应更锐利。有人可能会问这会不会只是 L2 正则或者训练随机性的结果我做了对照实验只在原来的 Neck 里加一个相同的参数量 1×1 卷积mAP 提升只有 0.3。所以提升不是来自参数多而是来自“解耦”这个行为本身。我还尝试用高斯模糊近似低频、原图减低频当高频对特征图做频谱能量统计。LFIM 版本的高频能量占比从原来的 26% 提高到了 34%。这个数字很直白同样的通道数下模型在细节支路上保留了更多信息。4.3 与常见Neck改进的对比我们把 LFIM 和几种常见的 Neck 改进放在同一套训练配置下对比结论如下。改进方案mAP50:95参数量增量AP_sBaseline YOLO2647.5-29.7加权 BiFPN48.00.5M30.5C2f SE 注意力47.90.4M30.2轻量级注意力融合AFP48.10.7M31.0LFIM推荐两处48.70.8M33.0加权 BiFPN 和 AFP 这类融合策略能全面涨点但小目标 AP 提升幅度没到 3 个点。LFIM 的优势在于它直接照顾了高频细节这个薄弱环节。放到密集场景、小目标占比较高或者航拍数据集里这个差距会进一步拉大我在自建的无人机视角数据集上对比过LFIM 比加权融合多涨了 2.1 个点。5. 踩坑记录与常见问题排查5.1 训练Loss冲高甚至NaN先查频带掩码初始化这是我踩的最大一个坑。第一版实现里我让band_gain服从标准正态分布初始化结果训练到第 30 轮时 loss 突然从 7.2 冲到 14.3然后开始震荡。原因在于随机初始化会让某些频带的增益被放得很大比如把高频噪声放大 5 倍梯度回传时这部分噪声会主导更新方向。解决办法有两个方向建议一起做。第一个是把band_gain初始化为 0让 LFIM 在一开始处于“全通”状态只允许它从 0 开始学习残差。第二个是加一个频带能量约束用 softmax 对每个通道的 16 个频带增益做 normalize保证总和不超过 1。我在代码里用的是归一化加残差组合gain gain * softmax_ratio 1.0效果最稳。5.2 显存不够怎么办把FFT放在低分辨率上FFT 会引入复数中间张量16 个频带的掩码也占显存。我用 RTX 3060 12G 跑 640 输入时如果直接在 P380×80上用 batch 16 训练显存会爆。建议把 batch 调到 8然后开梯度累积 2。还有一个更取巧的办法LFIM 中深层特征低频结构提取这一步可以先在 40×40 分辨率上完成 FFT再把结果插值回 80×80这样内存占用能降到原来的三分之一。实测 mAP 几乎不掉。5.3 NCNN部署把FFT算子“伪装”成卷积说完训练说部署。YOLO26 转 TensorRT 或者 NCNN 时最麻烦的就是 FFT。ONNX 对 FFT 算子的支持并不理想NCNN 更不用说自定义算子的实现难度不小。但如果直接把 LFIM 整块丢掉那训练时的提升就白搭。我的做法是在导出推理模型时把频域掩码这一整套逻辑合并成空间域等效卷积。原因很简单一个线性频域滤波器等价于空间域卷积只是在边界处有差异。对特征图应用可学习频带增益等价于用一个大小为 5×5 或 7×7 的卷积核在空域上做 depthwise 卷积。具体做法是把频域掩码逆 FFT 回空间域截取中心 5×5 的区域reshape 成 5×5 的 depthwise 卷积参数替换原来的 FFT 逻辑。这样一个 LFIM 在导出后就变成一个 1×1 卷积生成仿射参数 一个 5×5 depthwise 卷积做频带滤波。两个都是标准算子NCNN 和 TensorRT 原生支持。我按照常规tr转ncnn的流程导出 bin 和 param在安卓端实测单帧推理时间只增加了大约 4~6 毫秒。5.4 训练自己的数据集需要注意什么YOLO26 转成自己的数据集老生常谈的就是改num_classes和 YAML 路径。LFIM 不感知类别数所以不需要做额外调整。但有一个容易忽略的坑如果使用预训练 checkpoint分类头最后一层的权重维度不匹配加载时会报错。建议加载时把这层strictFalse跳过新数据集上它会随机初始化几轮就能学回来。另外如果你要用 LFIM 在一个非常小的数据集上微调比如只有几百张图片建议把新增的 LFIM 参数放在较低的学习率下训练甚至可以把 LFIM 模块单独学习率设为全局的 0.1 倍。这样能防止新模块在数据不足时过快拟合噪声。最后再说两句这个改进对我来说最大的收获不是那几个点涨了多少而是“先分解、后注入”这个思路本身是通用的。如果你正在做 YOLO26 的轻量化改进我建议你按“只加 P5→P4 一处”开始跑用控制变量法对比特征图看看高频支路的响应是否变得更清晰。不要一开始就贪心全部路径都加很多模块死在过度堆叠。我个人踩过几次坑之后觉得频域方案受欢迎是因为它给网络增加了一个非常明确的归纳偏置这比单纯增加参数更可靠。下一步我打算把结构分支的仿射注入换成可变形门控专门对付密集遮挡场景到时候再单独写一版实测记录。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →