080、BSRGAN原理与实现:盲超分辨率中的退化建模与网络设计

080、BSRGAN原理与实现:盲超分辨率中的退化建模与网络设计 080、BSRGAN原理与实现盲超分辨率中的退化建模与网络设计从一张模糊到怀疑人生的照片说起上个月帮实验室师弟调试一个老照片修复项目输入是一张90年代扫描的毕业照分辨率低得离谱还带着扫描仪特有的条纹噪声。我们试了EDSR、RCAN、SwinIR结果要么把噪点放大成马赛克要么把纹理抹成油画。师弟崩溃了“这些模型在Set5上跑得那么好怎么一到真实场景就翻车”这个问题其实戳中了超分辨率领域最尴尬的痛点——退化模型和真实退化之间的鸿沟。传统SR假设退化是简单的双三次下采样但现实世界里的低分辨率图像模糊类型千奇百怪运动模糊、散焦模糊、传感器模糊噪声分布五花八门高斯噪声、泊松噪声、JPEG压缩伪影再加上下采样核未知这根本就是个盲反问题。BSRGAN就是冲着这个痛点来的。2021年CVPR的工作核心思路很朴素既然真实退化太复杂那就用随机化的退化模型去覆盖它。今天这篇笔记我会从退化建模、网络设计、训练trick三个角度拆解BSRGAN最后附上我踩过的坑和调参经验。退化建模别再用双三次下采样骗自己了传统SR的退化模型长这样LR (HR ⊗ k) ↓s n其中k是模糊核↓s是下采样n是噪声。但BSRGAN把这个公式玩出了花——它把退化过程拆成两个阶段模糊下采样和噪声每个阶段都引入随机性。模糊核的随机采样BSRGAN的模糊核不是固定的高斯核而是从各向异性高斯核族里随机采样。具体来说核的协方差矩阵Σ由两个参数控制旋转角度θ和两个方向上的尺度λ1、λ2。代码里是这样写的# 生成各向异性高斯核别用cv2.getGaussianKernel那个只能生成各向同性的defrandom_anisotropic_kernel(kernel_size21,min_var0.2,max_var4.0):# 这里踩过坑kernel_size必须是奇数否则中心偏移thetanp.random.uniform(0,2*np.pi)lambda1np.random.uniform(min_var,max_var)lambda2np.random.uniform(min_var,max_var)# 构造协方差矩阵Rnp.array([[np.cos(theta),-np.sin(theta)],[np.sin(theta),np.cos(theta)]])Dnp.diag([lambda1,lambda2])covR D R.T# 生成网格坐标x,ynp.meshgrid(np.arange(kernel_size),np.arange(kernel_size))x-kernel_size//2y-kernel_size//2# 计算高斯值别忘记归一化kernelnp.exp(-0.5*(x**2*cov[0,0]y**2*cov[1,1]2*x*y*cov[0,1]))kernel/kernel.sum()returnkernel这里有个细节论文里kernel_size从7到21随机选但实际训练时我发现固定为21效果更稳定因为小核容易丢失高频信息。下采样别只用双三次传统方法只用双三次下采样BSRGAN引入了最近邻、双线性、双三次、Lanczos四种插值方式的随机选择。更骚的是它还允许在模糊之后、下采样之前加入一个sinc滤波器来模拟光学系统的截止频率。这个sinc滤波器的截止频率在[0.1, 0.5]之间随机采样相当于模拟不同质量的镜头。噪声模型从高斯到JPEG噪声部分BSRGAN做了两件事高斯噪声标准差在[0, 50]之间随机图像范围0-255JPEG压缩质量因子在[30, 95]之间随机注意这里噪声是加在LR图像上的不是加在HR上。而且论文里强调噪声和模糊的顺序是固定的先模糊下采样再加噪声。别搞反了否则噪声会被下采样放大。网络设计U-Net 残差 通道注意力但不止于此BSRGAN的网络结构看起来像SwinIR的变体但有几个关键差异。主体架构深度残差U-Net网络由三个模块组成浅层特征提取一个3x3卷积深层特征提取堆叠的RRDBResidual-in-Residual Dense Block上采样模块像素洗牌PixelShuffleRRDB是ESRGAN里提出的BSRGAN直接拿来用。但有个改动去掉了BN层。原因很简单BN在超分里会破坏图像的局部统计特性导致颜色偏移。别问我怎么知道的我试过加BN输出图像偏绿调了三天才发现。通道注意力Squeeze-and-Excitation每个RRDB内部嵌入了SE模块但实现时有个坑classSEBlock(nn.Module):def__init__(self,channels,reduction16):super().__init__()# 别这样写nn.AdaptiveAvgPool2d(1) 会丢失空间信息self.poolnn.AdaptiveAvgPool2d(1)self.fcnn.Sequential(nn.Linear(channels,channels//reduction,biasFalse),nn.ReLU(inplaceTrue),nn.Linear(channels//reduction,channels,biasFalse),nn.Sigmoid())defforward(self,x):b,c,h,wx.shape yself.pool(x).view(b,c)yself.fc(y).view(b,c,1,1)returnx*y这里reduction默认16但实验发现对于大模型比如通道数256以上reduction8效果更好因为通道间的相关性更强压缩太狠会丢失信息。全局残差连接BSRGAN在深层特征提取模块外还加了一个全局残差连接把浅层特征直接加到深层特征上。这个设计很关键因为盲超分需要同时保留低频结构和高频纹理全局残差相当于给网络开了一条“高速公路”让低频信息可以直接传递到输出。训练策略GAN 感知损失但别照搬ESRGANBSRGAN的训练分为两个阶段预训练阶段和GAN微调阶段。预训练阶段只用L1损失学习率2e-4batch size 16训练300k iterations。这里有个经验退化参数的范围要慢慢扩大。一开始把模糊核方差限制在[0.2, 2.0]噪声标准差限制在[0, 25]训练100k后逐步扩大到论文里的范围。否则网络会直接崩溃输出全是噪声。GAN微调阶段判别器用的是U-Net结构不是PatchGAN。为什么因为盲超分需要判别器关注局部纹理的真实性而U-Net能提供像素级的判别结果。损失函数是L_total L1 0.1 * L_perceptual 0.005 * L_GAN感知损失用的是VGG19的relu1_2、relu2_2、relu3_4、relu4_4四个层的特征权重分别是[1/32, 1/16, 1/8, 1/4]。这里注意不要用relu5_1那层特征太抽象对纹理细节不敏感。数据增强BSRGAN在HR图像上做了随机旋转90度、180度、270度和水平翻转但没有做颜色抖动。因为颜色抖动会改变图像的统计分布导致退化模型和真实场景不匹配。踩坑实录那些论文没告诉你的细节坑1退化参数的采样分布论文里说模糊核的方差在[0.2, 4.0]之间均匀采样但实际训练时发现均匀采样会导致大部分核过于平滑或过于尖锐。我的做法是对数均匀采样让核的尺度分布更均匀。坑2JPEG压缩的实现用PIL的save函数模拟JPEG压缩时注意图像要先转成uint8压缩完再转回float。而且不要用cv2.imencode那个函数对RGB通道顺序有要求容易搞混。坑3训练稳定性GAN训练时判别器容易过拟合。我的解决方案是每训练5次生成器才训练1次判别器并且判别器的学习率设为生成器的1/10。坑4测试时的退化参数BSRGAN在测试时不需要知道真实的退化参数但如果你知道大概的模糊程度可以手动调整退化参数的范围。比如老照片通常有散焦模糊就把模糊核方差设大一点。个人经验与建议不要迷信论文里的超参数。BSRGAN的退化参数范围是针对DIV2K数据集调的如果你的数据是医学图像或遥感图像需要重新调整。比如医学图像噪声通常是非高斯的可以考虑加入泊松噪声。退化模型的多样性比准确性更重要。BSRGAN的核心思想是用随机化覆盖真实退化所以不要纠结于某个参数是否精确关键是让退化空间足够大。GAN不是必须的。如果你的应用场景对保真度要求高比如人脸识别只用L1损失训练的BSRGAN效果就很好GAN反而会引入伪影。代码实现时注意边界处理。模糊核卷积时padding方式会影响边缘效果。我习惯用replicate padding比zero padding效果好。最后一条也是最重要的不要指望一个模型解决所有退化。BSRGAN在真实场景下比传统方法强很多但遇到极端退化比如运动模糊强噪声JPEG压缩还是需要针对性调整。我的做法是准备一组退化参数模板根据输入图像的退化程度选择最合适的模板。BSRGAN不是终点但它教会了我们一件事在不知道退化模型的情况下最好的策略不是去估计它而是去覆盖它。这个思路后来被Real-ESRGAN、BSRGAN-Plus等改进但核心思想没变。下一篇我会写怎么把BSRGAN用到视频超分上那个坑更多但效果也更惊艳。