基于YOLO26骨干的空间频率与通道转置注意力超分优化
做单图像超分辨率的人大概率都有过这种体验模型训练到后期PSNR还在涨可把图放大一看头发丝糊成一团金属表面的高光边缘变成了一片“晕染”砖墙的纹理直接被抹成一块均匀的灰色。这说明网络根本没学会恢复高频细节只是把低频信息做了个更平滑的插值。最近我在一个基于YOLO26思路的骨干网络上试了一组优化把空间频率注意力和通道转置注意力同时塞进特征提取阶段专门用来解决单图像超分辨率里的高频细节恢复问题。这篇就记录整个过程包括为什么这么设计、模块怎么写、训练时踩过哪些坑希望能给正在做类似方向的人一个参考。1. 先拆掉“超分难”这层壳高频细节到底丢在哪1.1 降质模型与不可逆的信息损失超分辨率任务本质上是解一个病态问题。我们可以把退化过程写成一个不太严谨但很直观的模型y (x ⊗ k) ↓s nx是清晰的高分辨率图k是模糊核s是缩放因子n是噪声。超分要做的是给定y反推x。但问题在于下采样这个过程已经把一部分信息彻底丢掉了——高频分量在采样时混叠、丢失模糊核又进一步压制了高频能量。也就是说x里那些细密的纹理、锋利的边缘在y里根本没有留下完整记录。这就像你听了一首被严重压缩的MP3想凭这段音频恢复出无损母带中间缺失的信息只能靠“猜”。模型本质上不是在放大一张图而是在给缺失的细节补一个合理的先验估计。怎么估计得准靠的是网络有没有能力从大量数据里学到“自然的纹理长什么样”。YOLO26这类检测骨干在这里的价值在于它的特征提取结构经过了大量真实场景图片的训练对不同尺度目标、复杂背景、边缘纹理都有丰富的响应。直接把这类骨干拿来做超分编码器等于让一个“见过很多图”的模型来提供视觉先验比从头训练一个网络更容易收敛也更容易保留细节相关的特征表达能力。1.2 为什么普通SR网络偏爱“磨皮”而不是“锐化”很多人训练超分模型时都有这种体会L1损失跑了很多轮PSNR也不低但出来的图总有一种塑料感、磨皮感。尤其是一些规则纹理区域比如砖墙、百叶窗、毛衣的编织纹理网络直接给糊成一片均匀的颜色。原因不复杂主要有三点。第一L1/L2这类像素级损失对“大误差”和“小误差”的惩罚方式决定了它天然偏好低频信息。高频细节往往只占图像能量的一小部分但对视觉感知却至关重要。如果损失函数主要衡量像素差网络会把精力集中在那些面积大、能量高的低频区域对边缘附近的几个像素误差反而没那么敏感。第二卷积运算本身是局部的、平滑的。普通卷积核在空间上感受野有限哪怕堆了几十层也容易被默认的平滑趋势带着走。除非网络里显式设计了能捕捉全局关系的模块否则它很难在远端像素之间建立关联从而“猜”出被抹掉的周期性纹理。第三大多数SR模块在特征提取时没有区分“空间位置的重要性”和“频率成分的重要性”。它们默认所有位置、所有频段的信息同等重要。但实际上一张图里平坦的天空区域几乎没有高频成分而头发丝、栏杆边缘则集中了绝大部分高频能量。注意力机制要做的就是让网络明白该把算力花在哪里。我用的解决思路很直接在特征提取阶段插入空间频率注意力让网络在频域里为不同频率成分分配权重同时插入通道转置注意力让网络在通道维度上建立依赖关系。两个模块配合一个管“哪些频率该增强”一个管“哪些特征通道之间该通信”。2. 空间频率注意力把注意力放到频域里去2.1 空间注意力的天花板先看常见的空间注意力。这类机制的做法是对特征图每个位置计算一个重要性分数生成一个空间mask然后和原特征图逐元素相乘。理论上网络可以学出哪些位置更重要比如边缘位置应该给更高权重。但空间注意力有一个先天的盲区它对“频率”没有感知。空间域里的权重图本质上是每个像素位置的一个标量重要性它无法表达“这个位置的边缘方向该不该强化”或“这个位置的纹理周期是多少”这类频率特性。你可以把空间注意力理解成给照片上每一块区域贴标签但不能决定这块区域的锐度、对比度、细节强度应该怎么变化。超分任务恰恰最需要后者。因为我们需要网络决定的不是“哪里重要”而是“哪些频率分量需要被恢复、增强”。这两个问题的颗粒度完全不同。空间注意力在一张低分辨率图上能指出“栏杆在左侧”但它说不出“栏杆的垂直边缘需要多少高频能量补偿”。频率注意力就是为了补上这个能力。2.2 振幅与相位频率注意力的两个把手图像从空间域变换到频域之后每个频率分量有两个关键信息振幅和相位。振幅谱描述的是“这个频率成分有多强”相当于声音里的音量。相位谱描述的是“这个频率成分在什么位置出现”相当于声音里不同声源的方位。在二维图像里一张竖条纹畸变成的照片频率谱里水平方向会有明显的高振幅成分横条纹则对应垂直方向的高振幅成分。对于高频细节恢复来说振幅和相位都重要。振幅决定了细节的强度和可见度相位决定了细节的位置和结构。如果网络只调整振幅而不管相位可能出现“细节有了但位置不对”的鬼影如果只调相位不管振幅可能出现“位置对了但强度不够”的孱弱纹理。空间频率注意力的做法是通过二维傅里叶变换把特征图拿到频域分别对振幅谱和相位谱做卷积变换然后施加注意力权重最后逆变换回空间域。这样网络就拥有了“增强哪些频率、减弱哪些频率”的能力而且这个能力是数据驱动学出来的。2.3 一个可直接用的空间频率注意力实现我用PyTorch实现了一个轻量的空间频率注意力模块核心逻辑如下import torch import torch.nn as nn class FrequencyAttention(nn.Module): def __init__(self, channels, ratio8): super().__init__() hidden max(channels // ratio, 8) self.amp_conv nn.Sequential( nn.Conv2d(channels * 2, hidden, 1), nn.ReLU(inplaceTrue), nn.Conv2d(hidden, channels, 1), nn.Sigmoid() ) self.phase_conv nn.Sequential( nn.Conv2d(channels * 2, hidden, 1), nn.ReLU(inplaceTrue), nn.Conv2d(hidden, channels, 1), ) self.norm ortho def forward(self, x): b, c, h, w x.shape x_fft torch.fft.rfft2(x, normself.norm) amp torch.abs(x_fft) phase torch.angle(x_fft) amp_feat torch.cat([amp, torch.ones_like(amp)], dim1) phase_feat torch.cat([phase, torch.zeros_like(phase)], dim1) amp_att self.amp_conv(amp_feat) phase_delta self.phase_conv(phase_feat) amp_new amp * amp_att phase_new phase phase_delta x_fft_mod amp_new * torch.exp(1j * phase_new) x_out torch.fft.irfft2(x_fft_mod, s(h, w), normself.norm) return x x_out这里面有几个细节值得说明。第一个细节是rfft2。它只计算实数输入的半边频谱显存大约省一半速度也更快。逆变换时用s参数指定原始尺寸保证形状一致。第二个细节是振幅和相位的处理方式不同。振幅是能量强度用sigmoid门控的乘法比较合适相当于“通断”相位是结构位置用加性偏移更合理相当于“微调”。我在实验里对比过才确定如果相位也用乘法门控容易出现频率错位重建出来的图会有轻微“抖动感”。第三个细节是残差连接。模块输出加回原始特征x而不是直接覆盖。这样网络一开始学的时候基本是恒等映射训练稳定性好很多。实践下来这个模块放在网络的中后段效果最好。放在太前面特征还比较原始频率信息太杂放在太后面细节特征已经被上采样打散注意力能调整的余地变小。一般放在主干网络的倒数第二个阶段前后比较合适。3. 通道转置注意力用最小的代价建模通道依赖3.1 从SE到自注意力通道建模的两种老路通道注意力大家不陌生。最经典的做法来自SENet的思路对特征图做全局平均池化得到每个通道一个标量然后接两个全连接层生成一组权重逐通道重新加权。这个思路很有效但有两个天花板。一是全局池化把空间信息全丢了。它只知道“某个通道整体激活度高”但不知道“这个通道在哪个区域激活度高”。举个例子一个通道可能专门响应水平边缘另一个专门响应皮肤纹理全局池化之后它只能得到一个笼统的数值无法表达这些通道和空间位置之间的耦合关系。二是全连接层的表达能力有限。SE模块里通道权重是一个静态的向量只能做缩放不能建立通道间的复杂交互。你无法让通道A根据通道B的具体响应模式决定自己要不要增强——这种关系是非线性的、随输入变化的。自注意力可以解决第二个问题它在每个位置计算其他所有位置的加权和本质上是对特征图两两建模。但直接用在图像上计算量是像素数的平方。一张64x64的特征图已经有4096个位置算出4096x4096的矩阵对显存压力非常大。SR任务通常还要处理更大的patch这条路基本走不通。3.2 转置注意力到底“转置”了什么转置注意力的思路很聪明既然位置数量太大那不如反过来在通道维度上计算注意力。常规自注意力算的是Q和K的点积得到一个位置与位置的序列关系矩阵。转置注意力把Q和K的角色换一下算的是通道与通道的关系矩阵。假设特征图有C个通道、N个位置原来需要N²的复杂度转置之后只需要C²。在图像任务里C通常从64到256不等而N可能是几千到几万。C²和N²的差距是两个数量级以上。举个例子64x64的特征图256个通道N²1677万C²6.5万差了250倍。这还没有把后续矩阵乘法的开销算进去。所以转置注意力用一个很小的代价获得了通道间全局依赖的建模能力。用更生活的话说普通自注意力是在“每个像素和所有像素”之间开大会转置注意力是在“每个通道和所有通道”之间开小会。对于超分任务来说通道之间的语义相关性非常强——响应边缘的通道往往和响应纹理的通道有协同关系这种协同可以用C²级别的矩阵精确建模。3.3 代码落地转置注意力模块的完整写法我的实现参考了常见的转置注意力写法但针对SR任务做了简化去掉了不必要的投影层import torch import torch.nn as nn import torch.nn.functional as F class ChannelTransposeAttention(nn.Module): def __init__(self, channels): super().__init__() self.gamma nn.Parameter(torch.zeros(1)) self.q_conv nn.Conv2d(channels, channels, 1) self.k_conv nn.Conv2d(channels, channels, 1) self.v_conv nn.Conv2d(channels, channels, 1) def forward(self, x): b, c, h, w x.shape q self.q_conv(x).reshape(b, c, -1) # B,C,N k self.k_conv(x).reshape(b, c, -1) # B,C,N v self.v_conv(x).reshape(b, c, -1) # B,C,N # 通道维度的关系矩阵 attn torch.matmul(k, q.transpose(1, 2)) # B,C,N B,N,C - B,C,C attn F.softmax(attn, dim-1) out torch.matmul(attn, v) # B,C,C B,C,N - B,C,N out out.reshape(b, c, h, w) return x self.gamma * out这里有两个关键点。第一个是维度搭配。很多人第一次写转置注意力会搞混matmul的顺序。要得到通道关系矩阵应该用k矩阵和q矩阵的转置相乘得到B,C,C如果反过来写就变成了普通的位置注意力复杂度直接爆炸退回去。你可以先拿小张量算一遍维度确认输出形状正确再接进主网络。第二个是初始化技巧。gamma初始化为0让模块一开始是恒等映射训练稳定很多。不要小看这个参数我试过直接不带gamma前几百步loss明显震荡更厉害。另外softmax温度也可以调。默认是logit直接softmax如果训练不稳定可以在softmax前除以sqrt(c)或者加一个可学习的温度参数。我实践中除以了常数效果不错。特别是当C比较大比如256时logit数值范围大直接softmax容易出现接近one-hot的情况梯度也不太好。4. 塞进YOLO26骨干整体结构与训练策略4.1 检测骨干做超分保留什么、砍掉什么YOLO26对我来说的价值在于它的特征提取骨架结构多尺度特征金字塔、跨阶段连接、以及相对轻量的卷积设计。做超分时不需要检测头也不需要anchor分配这些后处理逻辑只保留骨架的向前特征部分重新接一个重建头。具体来说我会把原骨干的检测分支全部去掉只拿主干网络输出的多尺度特征图。低分辨率输入经过几个stage会得到下采样2倍、4倍、8倍、16倍的若干层特征。超分重建需要的是这些不同尺度的特征融合而不是某一层单独输出。保留骨干的另一个好处是初始化。检测任务的预训练模型里已经包含了大量对自然图像的视觉先验这些先验对于超分重建有直接的帮助。虽然检测训练的损失目标是检测相关的但特征本身是有语义代表性的微调起来比从零开始容易得多。4.2 重建头与注意力模块的排布方式结构上我采用了这样一个顺序骨干输出多尺度特征选取最高分辨率的几层特征送入重建模块。重建模块内部先做通道融合然后依次经过空间频率注意力和通道转置注意力最后用亚像素卷积上采样到目标倍数。关键的设计决策是两个注意力模块不是串行堆很多个而是每个重建模块只放一组。因为SR任务的特征图比较大太深的注意力结构收益递减还会拖慢训练速度。一组就够用了关键是把位置放对。一个简化版的PyTorch结构如下class SRHead(nn.Module): def __init__(self, in_channels, out_channels, scale4): super().__init__() self.fusion nn.Conv2d(in_channels, out_channels, 1) self.sfa FrequencyAttention(out_channels) self.cta ChannelTransposeAttention(out_channels) self.up1 nn.PixelShuffle(2) self.up2 nn.PixelShuffle(2) self.tail nn.Conv2d(out_channels, out_channels, 3, padding1) def forward(self, feats): x self.fusion(feats) x self.sfa(x) x self.cta(x) x self.up1(x) x self.up2(x) x self.tail(x) return x这里有个细节PixelShuffle会把通道数除以4上采样2倍所以tail层输入的通道数需要精心设计。更稳妥的做法是把上采样拆成两个独立的步进每一步都重新卷积对齐通道数。实际工程里我会封装一个通用的上采样块避免通道数混乱。4.3 损失函数不止L1还要管频率超分重建最常见的损失是L1或Charbonnier损失。它训练稳定、收敛快但对高频细节的约束很弱。我的方案是三层损失叠加。第一层是像素域损失用Charbonnier而不是纯L1它的梯度在小误差区域更平滑不容易出现震荡L_pix sqrt((I_pred - I_gt)^2 epsilon^2)第二层是感知损失。把预测图和真值图都送进一个预训练的感知特征网络取中间若干层的特征算L1距离。这一项对纹理、结构整体相似度贡献很大能有效阻止“磨皮感”。第三层是频率域损失。对预测图和真值图分别做傅里叶变换计算振幅谱的L1距离。这个损失直接约束网络在频域上的表现和空间频率注意力模块配合起来效果明显。我用的权重比例大约为L_pix:L_perc:L_freq 1:0.01:0.05具体数值要看数据尺度调整。这里有个容易踩的坑频率域损失如果权重过大会让网络过度增强高频成分结果在平坦区域出现颗粒噪声。我遇到过这种翻车后来把L_freq权重压到L_pix的5%以下才算稳定。频率损失适合“助推”不适合“主导”这点要记住。4.4 训练流程与评估指标数据方面我用公开的2K分辨率超分数据集作为训练集。训练时随机裁剪HR图块比如96x96大小然后双三次下采样生成对应LR图块。数据增强包括随机翻转、旋转90度和颜色抖动。LR输入需要和HR完全对齐不能有crop偏移这是很多新人容易忽略的。评估指标用PSNR和SSIM在Y通道上计算。这两个指标对超分任务来说虽然老但最有说服力。除此之外我还额外统计了梯度幅值均值和边缘强度用来量化高频细节恢复的程度。PSNR涨0.1但梯度幅值明显提升的情况说明高频确实恢复得更好了。训练时batch size受显存限制我用梯度累计来模拟更大的batch。学习率从1e-4开始先warmup 1000步之后用余弦退火。总训练步数大约几十万步中途在验证集上监控PSNR每遇到平台期就降低学习率。5. 实操记录踩过的坑和排查速查表5.1 数据、显存、精度均衡先说显存问题。SR任务最吃显存的不是模型本身而是特征图尺寸。96x96的HR crop经过4倍上采样后变成384x384中间再插上注意力模块一块GPU可能只能塞下很小的batch。我的做法有三个。一是训练时用混合精度能省不少显存且几乎不掉精度。二是把大patch切成小patch训练比如64x64验证时再换大patch。三是用梯度检查点让反向传播不缓存中间特征用时间换空间。数据准备阶段有个常被忽视的细节HR和LR的边界对齐。我的做法是先对HR做边界反射padding再下采样保证LR和HR之间没有crop偏移。如果直接用不同随机裁剪的LR和HR模型会学到“位置偏移补偿”这种错误模式重建结果会有一种微妙的错位感很难查出来。5.2 三个典型的翻车现场翻车现场一重建图有环状伪影尤其在边缘周围。这是典型的频域处理副作用。傅里叶变换默认会假设信号周期延拓如果特征图边界不连续会产生Gibbs振铃。我的解决办法是在频域模块之后加一个轻量的3x3卷积做空间平滑反向引入一点空间先验同时把FFT的norm设置成ortho功率归一化之后振铃会减轻不少。翻车现场二平坦区域出现颗粒噪声。这是频率损失权重太大导致的。我在某个版本里把L_freq权重调到0.1结果天空、墙面这些区域变得脏兮兮的。后来把权重降回来并且在损失里加了一项简单的局部方差约束对重建图计算梯度在梯度很小的区域压低其方差相当于惩罚无端出现的纹理。翻车现场三训练前期loss下降很快但到某个阶段PSNR就卡住不动。这个问题出在通道转置注意力的softmax分布上。当通道数较大时初始化的logit范围太广softmax输出接近one-hot梯度流不进去。把温度调低一点或者初始化gamma为0问题就解决了。5.3 高频恢复效果的灰度测试法分享一个我常用的主观验证方法比看PSNR数字更直观。把原图转成灰度图然后做一次差分用原图减去高斯模糊后的图得到的差异图里保留了所有高频成分。对重建图做同样的操作再比较两幅差异图的视觉强度。如果重建图的差异图在边缘处明显弱于原图说明高频丢失如果差异图出现原图没有的杂点说明高频过冲。这个测试不用任何额外代码用常用图像编辑软件就能做非常快的初步判断。我每次训练到中期都会抽几张图跑一遍这个流程比盯着PSNR曲线效率高得多。5.4 常见问题速查表现象可能原因解决方案重建图发糊边缘不锐利频率注意力没有生效或L_freq权重过低检查频域模块是否正常更新调高L_freq到0.05-0.1边缘周围有振铃伪影FFT边界效应使用ortho归一化在频域模块后加3x3卷积平滑平坦区域颗粒噪声明显L_freq权重过大降低频率损失权重或加入局部方差惩罚项训练前期loss震荡转置注意力softmax温度过高除以sqrt(c)或初始化gamma为0显存不足特征图尺寸过大使用混合精度、梯度检查点、小patch上采样报通道数错误PixelShuffle维度问题封装独立上采样块先卷积对齐通道再shuffle我把整个优化过程中最常碰到的几个问题都列在上面了基本覆盖了从开始训练到最终部署的大部分坑。你要是照着这个方案做大概率会在其中某一条上栽跟头到时候回来翻这张表就行。最后说点个人体会。做了这么长时间超分相关的优化我越来越觉得注意力模块不是越多越好而是要对准任务发力点。单图像超分的本质是从低分辨率图里找回缺失的确定性空间频率注意力和通道转置注意力一个管频率、一个管通道恰好把两个最容易丢信息的维度都照顾到了。你要是也在做类似方向的优化建议从这两个模块入手先在小patch上验证效果再放大比一上来就堆大模型稳得多。