news 2026/10/19 14:03:57

FSCFNet频域空域融合:让YOLOv10精准检测红外小目标

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FSCFNet频域空域融合:让YOLOv10精准检测红外小目标

第一次拿 YOLOv10 去跑红外小目标数据集,很多人都会撞见同一个场景:验证集上的 mAP 看起来还算体面,但一可视化,框全部压在云层边缘、海杂波和高亮噪声上,真正只有十几个像素的小亮点反而被漏掉。这真不是 YOLOv10 不行,而是红外小目标检测这个任务,和常规目标检测的“游戏规则”很不一样。

TGRS 2025 上的 FSCFNet,就是专门冲这个难题去的。名字里的 FSCF,可以理解为 Frequency-Spatial Collaborative Fusion,也就是把频域信息和空域信息协同起来做特征融合。它把红外小目标检测拆成两路信号——频域看全局、空域抓局部——再以即插即用模块的形式融进 YOLOv10 的检测框架里,在不动整体结构的前提下,把红外小目标检测这件事做得更稳。

这篇文章我会从“为什么红外小目标检测这么难”讲起,再拆解 FSCFNet 的频域与空域协作思路,最后给出一份我自己复现和接入 YOLOv10 时总结的实操过程、踩坑记录和调参经验。无论你是刚接触红外小目标,还是已经跑了几个月相关数据,应该都能从中找到一点能直接拿去用的东西。

1. 红外小目标检测到底难在哪:YOLOv10 为什么会水土不服

1.1 三个数据特征让通用检测器直接崩溃

先把“小目标”三个字量化一下。常规目标检测里的小目标,一般指像素尺寸小于 32×32 的物体;在红外遥感、安防监控这类场景里,小目标的标准要苛刻得多。很多公开红外小目标数据集里,目标面积只有 3×3 到 9×9 像素,整张 640×512 的图上,目标占据的面积比例可能不到千分之一。

这个量级意味着什么?一个 3×3 的目标,在 512 分辨率的特征图上,连一个标准卷积核的感受野都填不满。对 YOLOv10 这种通用检测器来说,目标本身既没有颜色信息,也没有可依赖的纹理边缘,只剩下一个稍亮一点的散点,和传感器热噪声、云层反射、海面耀斑混在一起,普通人眼都不一定能分辨出来。

这些数据特征可以总结成一张表来看:

数据特征对检测的影响典型表现
目标极小下采样后特征几乎消失深层特征图上没有可响应的像素点
单通道灰度缺乏颜色和纹理线索模型只能用亮度差异做判断
低信噪比背景噪声和目标难以区分云边缘、热噪声被当成目标
正负样本极不均衡模型倾向把所有框都预测为背景训练困难,漏检率居高不下

很多人在红外数据集上直接套 YOLOv10 预训练权重,跑出来的结果往往是大目标一个不漏、小目标一个不中,本质上就是这些问题叠加的结果。

1.2 一个关键矛盾:下采样与小目标的消失

YOLOv10 的 Backbone 从输入图像开始,通过多次 stride 为 2 的卷积和池化,把特征图逐级缩小。常规检测任务里,这是优势,因为更大的感受野能帮助模型看到更大的上下文。可红外小目标的问题在于,目标本身就那么几个像素,每下采样一次,目标在特征图上的有效响应就少一圈,到 P5 层(stride 32)的时候,一个 3×3 的目标可能连一个像素都占不到。

这就引出了第一个矛盾:要识别小目标,需要高分辨率、高细节的特征图;但 YOLOv10 为了获得上下文和全局语义信息,又必须不断下采样。FSCFNet 之所以要把频域引入进来,很大程度上就是在尝试缓解这个矛盾——不是单纯靠更深的网络去“看”,而是换一个视角,从频率成分里去发现那些空间域上几乎要消失的信号。

2. FSCFNet 的“协奏”思路:频域与空域互补原理

2.1 频域视角:用频谱识别目标

图像可以看作一个二维信号,傅里叶变换或 DCT 能把它从空间域变到频率域。在频率谱里,图像的整体背景通常集中在低频部分,而边缘、角点这类突变细节集中在高频部分。红外小目标本质上就是一个局部亮度突变,天然对应高频成分。

但问题是,云层边缘、海杂波、传感器噪声也都是高频成分,单纯做高通滤波会把背景轮廓和噪声一起框进来。FSCFNet 的做法不是做一个固定高通滤波器,而是通过可学习的频域门控,让网络自己去判断哪些频带的响应更像目标。这种处理有一个空间域很难替代的优势:频域操作是全局的,可以捕捉到目标与背景在周期性、方向性上的细微差别。

2.2 空域视角:局部对比度更能抓住“亮斑”

频域虽然擅长全局感知,但对目标的精确位置并不敏感。空间域分支正好补上这一块:通过小卷积核、深度可分离卷积,在原图上提取局部对比度和邻域差异。换句话说,频域分支告诉模型“这个区域的结构性异常在哪个频段”,空域分支告诉模型“这个亮斑在哪个坐标、多大范围”。

传统红外小目标检测方法里,有一类叫局部对比度方法,比如 LCM、MPCM,用一个滑窗计算中心区域和周围区域的差异,核心思想就是“目标比周围亮一截”。FSCFNet 的空域分支,某种程度上就是把这种传统手工特征用深度卷积的形式重新表达了一遍,而且可以和频域分支一起端到端训练。

2.3 “协奏”不是简单相加:融合门控才是关键

把空域特征和频域特征直接 concat 再过一个卷积,能起作用,但很难算得上“协奏”。FSCFNet 的思路更像是给两路分支一个可学习的融合权重:空域特征强的时候,模型多依赖空域;频域特征更干净的时候,模型多依赖频域。

实际设计中,模块会把空域分支输出和频域分支输出做 concat,通过两个 1×1 卷积加激活函数生成融合特征,再经过一个可学习残差系数叠加到原特征图上。残差结构保证了模块在初始化阶段接近恒等映射,不会因为插入新模块导致预训练权重被破坏。这一点对“即插即用”来说非常重要。

3. 即插即用实战:把 FSCFBlock 装进 YOLOv10

3.1 为什么选 YOLOv10 当底座

FSCFNet 选择 YOLOv10 作为骨干,而不是单独设计一个全新的检测器,我认为有几个很实际的原因。

YOLOv10 的 Backbone 在通用目标检测上积累了大量预训练知识,即使红外数据和自然图像分布差异较大,迁移起来也比从零训练快得多。其次是它的 Head 和标签分配机制成熟稳定,训练工程化程度高,适合快速验证新模块。最后,YOLOv10 的模型结构清晰,修改 Backbone 或 Neck 的成本很低,把一个自定义模块插进去只需要改很少的代码,非常适合做“即插即用”研究。

需要说明的是,FSCFNet 的核心模块,我下面统称为 FSCFBlock。FSCFNet 可以理解为一个用了 FSCFBlock 作为核心特征增强模块的检测网络,真正能迁移到其他任务上的,正是这个 FSCFBlock 本身。

3.2 模块插在哪:Backbone 还是 Neck

插入位置对最终效果影响很大。我试过几种接法,简单对比如下:

插入位置优点缺点实测感受
Backbone 每层后特征最原始,信息保留完整计算量太大,梯度回传路径长显存压力大,收益不划算
Neck 输出后多尺度特征已经融合,语义更丰富对极小目标来说细节可能已经丢失综合性价比最高
只接 P5 高层特征计算量小,对大目标有增益小目标在 P5 上几乎无响应对红外小目标帮助有限
额外接 P2 层后分辨率高,小目标细节保留最多计算量大,需要改 Head 结构小目标召回率提升最明显

我的最终建议是:优先在 Neck 的各个输出特征层后接 FSCFBlock,如果任务里目标真的只有 3×3~5×5 像素,还需要再加一个 P2 层(stride 4)并同样接上模块。只改 Backbone 不碰 Neck,效果通常不明显。

3.3 一个可以直接跑的 FSCFBlock 参考实现

下面这段代码是我参考论文思路写出的一个简化版 FSCFBlock,保留了最核心的两个分支。它不是为了逐行复现官方实现,而是帮助你理解即插即用模块是如何接入 YOLOv10 的,并可以直接搬到自己的代码库里跑起来。

import torch import torch.nn as nn class FSCFBlock(nn.Module): """ 简化版 FSCF 模块:频域与空域协作融合。 - 空域分支:深度可分离卷积提取局部对比度 - 频域分支:RFFT 转到频域,用 1x1 卷积做门控,再 IRFFT 回空间域 - 最后通过可学习残差系数叠加到输入特征上 """ def __init__(self, channels): super().__init__() self.spatial = nn.Sequential( nn.Conv2d(channels, channels, 3, padding=1, groups=channels), nn.GELU(), nn.Conv2d(channels, channels, 1), ) # 频域门控:输入是实部+虚部堆叠后的通道 self.freq_gate = nn.Conv2d(channels * 2, channels * 2, 1) self.fuse = nn.Sequential( nn.Conv2d(channels * 2, channels, 1), nn.GELU(), nn.Conv2d(channels, channels, 1), ) # 可学习残差系数,初始为 0.5,避免刚插入时破坏原特征分布 self.alpha = nn.Parameter(torch.tensor(0.5)) def forward(self, x): # 空域分支 branch_s = self.spatial(x) # 频域分支 B, C, H, W = x.shape x_freq = torch.fft.rfft2(x, norm="ortho") # [B, C, H, W//2+1] x_freq = torch.view_as_real(x_freq) # 拆成实部虚部 x_freq = x_freq.permute(0, 1, 4, 2, 3).contiguous() x_freq = x_freq.reshape(B, C * 2, H, W // 2 + 1) # 实部虚部堆到通道维 x_freq = self.freq_gate(x_freq) # 1x1 卷积门控 x_freq = x_freq.view(B, C, 2, H, W // 2 + 1) x_freq = x_freq.permute(0, 1, 3, 4, 2).contiguous() x_freq = torch.view_as_complex(x_freq) branch_f = torch.fft.irfft2(x_freq, s=(H, W), norm="ortho") # 融合两路特征 fused = torch.cat([branch_s, branch_f], dim=1) fused = self.fuse(fused) return x + fused * self.alpha

接入 YOLOv10 的方式很直接,比如在 Neck 输出特征列表后做一次循环:

# 假设 neck_out 是 YOLOv10 输出的多尺度特征列表 fscf_blocks = nn.ModuleList([ FSCFBlock(channels=ch) for ch in [256, 512, 1024] ]) enhanced_feats = [] for feat, block in zip(neck_out, fscf_blocks): enhanced_feats.append(block(feat)) # 后续检测头继续使用 enhanced_feats

3.4 红外灰度输入提前要做的改造

很多红外数据集是单通道灰度图,而 YOLOv10 官方预训练权重是按 RGB 三通道设计的。第一个要处理的坑就在这里。最简单的方案是把单通道复制三份,喂给原本为三通道设计的网络:

import cv2 import numpy as np img = cv2.imread("infrared.png", cv2.IMREAD_GRAYSCALE) img_3c = np.stack([img] * 3, axis=-1)

这样做的好处是不改模型第一层,完全复用预训练权重;缺点是等于让模型额外多看了两遍相同信息,理论上没有增加信息量,但实测中影响不大。

如果想更彻底一点,可以直接修改第一层卷积,把输入通道从 3 改成 1,并将预训练权重在三通道上取平均作为单通道权重:

import torch.nn as nn old_conv = model.model[0].conv old_w = old_conv.weight.detach() # [out_ch, 3, k, k] new_conv = nn.Conv2d(1, old_w.shape[0], old_w.shape[2], padding=old_conv.padding, stride=old_conv.stride) with torch.no_grad(): new_conv.weight.copy_(old_w.mean(dim=1, keepdim=True)) new_conv.bias.copy_(old_conv.bias) model.model[0].conv = new_conv

这种做法的好处是模型结构和单通道数据完全对齐,训练时真实输入只有 1 个通道,参数量也稍小一点。两种方案我都跑过,最后选择哪个更多取决于你手里的预训练权重和可投入的调参时间。想省事就复制三通道,想追求上限就改造第一层。

4. 训练细节与调参玄学:让 FSCFNet 真正收敛

4.1 数据增强操作:关掉 Mosaic 之后反而更好

我刚开始训练时直接沿用了 YOLOv10 默认的数据增强,其中 Mosaic 对小目标检测来说是双刃剑。Mosaic 把四张图拼在一起,原本就小的目标在拼接图里变得更小,而且跨边界的目标容易被裁剪成残片,标签信息直接被破坏。在常规 COCO 场景里这并不是大问题,但在红外小目标数据集上,Mosaic 带来的负面影响往往大于收益。

我实测下来,红外小目标场景更建议关闭 Mosaic,或者至少把 mosaic 概率降到 0.3 以下。与此同时,可以增加两类增强:一是随机调整灰度和对比度,模拟不同热成像环境下的信噪比变化;二是随机叠加高斯噪声和椒盐噪声,让模型对传感器噪声更鲁棒。目标太小的时候,任何几何形变都可能让目标变成不可辨识的散点,反而不要用。

4.2 损失函数和后处理阈值选择

YOLOv10 默认的回归损失在常规目标上表现稳定,但红外小目标只有几个像素时,回归框偏移两个像素,IoU 就会掉到很低的水平。如果条件允许,可以把回归损失替换为 SIoU 或 WIoU 这类对尺度不敏感、对小目标更友好的变体。替换损失函数后需要重新跑验证,不是所有数据集都能获得提升,但红外小目标上一般值得一试。

后处理阈值也需要调整。默认置信度阈值 0.25 对可见光目标够用,但在红外小目标场景下,目标本身的响应本来就弱,阈值设太高容易把小目标直接过滤掉。我一般会把置信度阈值降到 0.1 左右,NMS 的 IoU 阈值从默认 0.7 调到 0.3。小目标密集出现的时候,阈值太高会导致邻近目标被合并成一个框。

4.3 训练策略:冻结、解冻、微调三步走

插入 FSCFBlock 后,如果立刻全模型一起训练,预训练权重很容易被随机初始化的新模块带偏。更稳妥的做法是分阶段训练:

  1. 冻结 Backbone 和 Neck 原有参数,只训练 FSCFBlock,跑 5~10 个 epoch,让新模块先学会在当前特征上做有效的频域空域融合。
  2. 解冻 Neck,保持 Backbone 冻结,再训练 20~30 个 epoch,让 Neck 逐步适应新模块的输出分布。
  3. 最后全部解冻,用小学习率微调 10 epoch 左右。

学习率上,新模块可以使用与骨干相同的初始学习率,但如果发现 loss 震荡明显,可以把新模块的学习率单独调低到骨干的十分之一。Batch size 尽量保持较高,因为 FSCFBlock 中的频域分支涉及批量复数运算,BN 层统计量不稳定会更敏感。

5. 复现 FSCFNet 时我踩过的四个坑

5.1 坑一:频域分支反向传播时显存翻倍

torch.fft 系列函数在前向计算时很快,但反向传播需要保存整个复数频谱,显存开销比普通卷积高不少。我在 512×512 输入、P4 层通道数 512 时插入 FSCFBlock,显存直接涨了 6GB 以上,把训练顶爆了。

解决办法有三个方向:一是只在 P5 等低分辨率特征层上接模块,放弃 P2/P3 层;二是把 FSCFBlock 内部的通道先降维再做频域变换,比如先把 512 通道压到 128,处理完再升回来;三是使用梯度检查点(gradient checkpoint),省显存但增加计算时间。实际工程里,我通常把通道降维和低分辨率插入组合使用。

5.2 坑二:验证 mAP 很高但框全部偏掉

有一轮训练,我的验证 mAP 到了 0.8 以上,看起来很不错,可视化时却发现预测框总是偏在目标旁边,甚至框在噪声上。原因在于小目标的 IoU 计算对空间偏移极其敏感:一个 3×3 的 GT 框,预测框只要偏移 1~2 个像素,IoU 就已经低于 0.5 了。如果验证集本身标注就存在微小的中心点偏差,mAP 很容易虚高。

后来我做了两件事:一是同时上报 IoU=0.3、0.5、0.75 三档 mAP,观察不同阈值下的掉点速度;二是把预测框和标注框可视化叠加,直接检查中心偏移量。只看单点 mAP 是不够的,红外小目标领域尤其要养成“曲线+可视化”双验证的习惯。

5.3 坑三:P2 层加与不加的纠结

前面提到小目标需要高分辨率特征。YOLOv10 默认从 P3 起步,如果一个目标只有 3×3 像素,经过 stride 8 后,它在特征图上可能只剩下 0~1 个有效像素。加上 P2 层(stride 4)后,目标还能保留 1~2 个像素,召回率会有明显提升。

但 P2 层的代价是计算量大幅增加,尤其是再配上 FSCFBlock 的频域分支,训练速度会慢一半以上。我最后折中的方案是:P2 层只做轻量通道的 FSCFBlock,比如把通道降一半再进频域分支;P3~P5 层保留完整通道。效果接近完整方案,训练速度可控。

5.4 坑四:权重初始化把梯度洗没了

FSCFBlock 里的频域门控卷积如果初始化范围太大,模型一开始就可能把所有频域分量压掉,导致频域分支在整个训练过程中都学不到东西,成了摆设。这也是我为什么在模块里加了 alpha 残差系数的原因。

我推荐把 alpha 初始化为 0,或者把 freq_gate 的权重初始化为接近 0 的小随机数。这样模块在一开始近似恒等映射,不会破坏 YOLOv10 原有特征分布,之后随着训练逐步把频域分支激活起来。这个小改动帮我避开了好几次“加了模块反而掉点”的尴尬。

6. FSCFNet 的适用边界:什么时候值得上

6.1 真正擅长的问题

从实测看,FSCFNet 这类频域空域协作方案最适合目标极小、对比度低、背景杂波强的单帧红外图像检测任务。比如远距离无人机和飞鸟检测、海面舰船目标搜索、输电线异物监测、森林火点早期预警。这些场景的共同特点是目标没有丰富纹理,且经常淹没在复杂自然环境里,频域分支能提供空间域难以感知的额外线索。

另一个值得关注的方向是“即插即用”本身。FSCFBlock 并不要求骨干必须是 YOLOv10,换到 YOLOv8、YOLOv11、RT-DETR 等结构同样可以接在 Neck 后使用。换句话说,这篇工作的价值不只是 FSCFNet 这一个模型,更是一个可以被反复迁移到其他检测器里的频域空域融合模块。

6.2 不适合的场景

如果目标本身已经有 50×50 像素以上,或者在可见光图像里拥有丰富的颜色和纹理信息,FSCFNet 的优势就不明显了。常规目标检测器的特征提取能力已经足够,额外引入频域分支只会增加计算开销。再比如视频实时处理场景,且部署芯片不支持高效的 FFT 算子,频域分支会拖慢整体速度,这时候宁可用纯空域的轻量模块。

还要注意,频域分支对高频噪声放大是双刃剑。如果数据集里大量存在条纹噪声、周期性干扰,模型可能反而把频域中的噪声模式当成目标,训练时需要一个较强的去噪预处理或更多噪声样本。

6.3 模块下一步可以怎么扩展

我目前正在尝试两个方向:一是把 FFT 换成 DCT,因为 DCT 没有复数运算,实现更轻量、部署更友好;二是把 FSCFBlock 里的频域门控升级成动态注意力,根据输入图像的信噪比自适应调节频域分支权重。这些改动本质上还是延续“频域与空域协奏”的思路,只是让融合过程更灵活。

另外,这种频域空域协作模块不只适合检测,也可以迁移到红外小目标分割、遥感变化检测等任务。如果你正在做相关方向,建议先在自己的骨干网络上接一个简化版 FSCFBlock 试试效果,再看要不要引入更完整的频域分支。我的体会是,这类模块的价值往往不是单独提升某一类指标,而是让模型在低信噪比场景下变得更“稳”,减少那些莫名其妙的漏检和误检。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 14:03:52

Spring AI Alibaba实战:结构化输出、函数调用与多轮记忆全解析

上一篇我们把 Spring AI Alibaba 的架子搭起来了,能跑通基本的对话,也能看到模型返回的普通文本结果。但真正把它放进业务系统里,事情远没有这么简单:多轮会话怎么接住上下文,模型输出怎么直接变成 Java 对象&#xff…

作者头像 李华
网站建设 2026/10/19 14:02:01

Simulink实时修改参数:ExportedGlobal与External Mode在线调参实战

简介:这份资源面向需要在Simulink仿真过程中动态调整模块参数的开发者与学习者,核心是用S函数实现对模型内部参数的实时修改,并以正弦波形作为验证对象,在线改变其幅值与频率,直观观察参数变化对输出的影响。压缩包共2…

作者头像 李华
网站建设 2026/10/19 14:00:38

ESP32不依赖音频库:用I2S与SD卡直接播放WAV文件全攻略

简介:面向Arduino/ESP32开发者的音乐播放器示例工程,基于Arduino-ESP32库与I2S数字音频接口,实现了从SD卡读取WAV文件并连续播放的功能,解决数字音频传输中信号衰减与缓冲管理难题,适合物联网音频项目入门及功能扩展。…

作者头像 李华
网站建设 2026/10/19 14:00:28

深度强化学习从原理到实战:DQN、经验回放与训练避坑指南

[博主手记] 上周有个做图像识别的朋友问我:现在的模型动不动就是“预测”,但我想让程序自己学着玩游戏、控制机器人,该看哪一套东西?我让他别急着翻神经网络,先去看看强化学习。结果他抱着某个经典教材的第9章&#xf…

作者头像 李华
网站建设 2026/10/19 13:59:23

VLLM 0.7.3源码深度解析:PagedAttention v2与CUDA调度优化实战

简介:本资源为VLLM-0.7.3大模型推理框架完整开源源码包,面向AI工程师、大模型系统优化研究者及高性能推理部署实践者,聚焦解决超大规模语言模型在生产环境中推理速度慢、显存占用高、吞吐瓶颈突出等核心问题。压缩包共1896个文件,…

作者头像 李华