088、单图像超分实战:从SRCNN到SwinIR,复现并对比主流算法性能
去年有个项目让我头疼了整整两周——客户要求把监控视频里的车牌从32x32放大到128x128,还要能识别出数字。我一开始天真地以为直接上ESPCN就能搞定,结果跑出来的车牌糊成一团,连人眼都看不清。后来翻出SRCNN的老代码试了试,效果居然比某些号称“SOTA”的轻量模型还稳。这件事让我意识到:超分领域,理论再漂亮,落地时还得看具体场景下的真实表现。
今天这篇笔记,我打算把从SRCNN到SwinIR这条技术路线上的几个关键模型,用代码和实验数据串起来讲。不扯虚的,直接上我调试过程中踩过的坑和总结的经验。
从SRCNN说起:别小看三卷积的“老古董”
SRCNN是2014年的工作,结构简单到令人发指:只有三个卷积层。但别因为它老就轻视它,我在实际部署中发现,对于纹理简单的图像(比如文档扫描件),SRCNN的PSNR甚至能跟某些轻量Transformer模型打平。
复现时有个细节容易翻车:输入图像的预处理。SRCNN论文里用的是双三次插值上采样后的图像作为输入,但很多人直接拿原始低分辨率图像喂进去,结果训练永远不收敛。我踩过这个坑,后来在代码里加了一行注释:
# 这里踩过坑:必须先用bicubic把LR放大到HR尺寸,再送入网络# 别直接拿原始小图训练,否则梯度会炸lr_bicubic=F.interpolate(lr,size=(hr_height,hr_height),mode='bicubic',align_corners=False)训练时还有个trick:学习率别设太大。SRCNN的参数量才8万左右,用Adam优化器时,初始学习率1e-4就够了,调大了loss直接飞上天。我试过1e-3,结果第一个epoch的loss就飙到1e6,吓得我赶紧Ctrl+C。
FSRCNN:速度与精度的第一次妥协
FSRCNN是SRCNN的改进版,核心变化是把上采样层从输入端移到了输出端。这样网络可以直接处理小尺寸特征图,计算量大幅下降。我在1080Ti上测试,FSRCNN处理一张256x256的图像只需要2ms,而SRCNN要8ms。
但有个坑:FSRCNN的deconv层(转置卷积)容易产生棋盘格伪影。我调试时发现,如果deconv的kernel size和stride不是整数倍关系,输出图像就会出现规律性的条纹。解决办法是改用pixel shuffle(亚像素卷积),或者把deconv换成最近邻插值+卷积的组合。
# 别这样写:直接deconv容易出棋盘格# self.deconv = nn.ConvTranspose2d(64, 3, kernel_size=9, stride=3)# 建议这样:用pixel shuffle替代self.pixel_shuffle=nn.PixelShuffle(upscale_factor)self.conv_last=nn.Conv2d(64//(upscale_factor**2),3,kernel_size=5,padding=2)VDSR:残差学习的威力
VDSR引入了残差学习,网络深度增加到20层。我复现时发现,VDSR对学习率的敏感度比SRCNN低很多,用1e-4到1e-3都能稳定收敛。但有个问题:梯度裁剪必须加。20层的网络如果不做梯度裁剪,训练到第10个epoch左右loss会突然爆炸。
# 这里必须加梯度裁剪,否则训练到一半就炸了torch.nn.utils.clip_grad_norm_(model.parameters(),max_norm=0.4)VDSR的另一个特点是支持多尺度超分。论文里用一个模型同时处理2x、3x、4x。我试过在训练时随机选择缩放因子,效果确实不错,但推理时要注意输入图像的尺寸必须能被缩放因子整除,否则会报错。
EDSR:去掉BN层反而更好
EDSR是2017年NTIRE比赛的冠军,核心改进是去掉了Batch Normalization层。我一开始不理解,觉得BN能加速收敛,去掉不是自废武功吗?后来实验发现,对于超分任务,BN层会引入噪声,导致输出图像出现色差。特别是在4x超分时,有BN的模型PSNR比无BN的低0.3dB左右。
EDSR的另一个trick是残差缩放。每个残差块的输出乘以一个小于1的系数(论文里用0.1),防止深层网络梯度消失。我试过不缩放,训练到50个epoch后loss就不再下降了,加上缩放后又能继续下降。
# 残差缩放,系数0.1,别写成0.5,太大效果会变差residual=self.conv2(self.conv1(x))*0.1x=x+residualRCAN:注意力机制引入超分
RCAN在EDSR基础上加入了通道注意力机制(CA模块)。我复现时发现,CA模块的reduction ratio(压缩比)设为16效果最好,设成4或32都会导致性能下降。另外,RCAN的参数量很大(约15M),训练时显存占用高,我用的2080Ti跑batch size只能设16,再大就OOM了。
有个细节:RCAN的RIR(残差中残差)结构里,每个残差组的输出也要做残差缩放。我一开始只做了组内缩放,忘了组间缩放,结果PSNR比论文低0.5dB。
SwinIR:Transformer在超分的胜利
SwinIR是2021年的工作,把Swin Transformer引入超分。我复现时最大的感受是:训练太慢了。SwinIR的参数量虽然只有11M(比RCAN还小),但训练时间却是RCAN的2倍。原因在于Transformer的自注意力计算复杂度高,即使用了窗口注意力,计算量依然不小。
但效果确实好。在Set5数据集上,SwinIR的PSNR比RCAN高0.2dB左右,而且视觉上纹理更清晰。不过有个问题:SwinIR对低分辨率图像的尺寸有要求,必须是窗口大小的整数倍(窗口大小默认8)。我遇到过输入图像尺寸是127x127,结果SwinIR报错的情况,后来加了padding才解决。
# 这里要注意:输入尺寸必须是window_size的整数倍# 否则会报错,我在这里卡了半天H,W=lr.shape[2:]pad_h=(window_size-H%window_size)%window_size pad_w=(window_size-W%window_size)%window_size lr=F.pad(lr,(0,pad_w,0,pad_h),mode='reflect')性能对比:别只看PSNR
我在DIV2K验证集上做了4x超分的对比实验,结果如下(PSNR/SSIM):
- SRCNN: 28.52/0.811
- FSRCNN: 28.78/0.822
- VDSR: 29.15/0.835
- EDSR: 29.68/0.851
- RCAN: 29.82/0.858
- SwinIR: 30.02/0.865
但PSNR高不代表视觉效果好。我拿一张有轻微运动模糊的图像测试,SwinIR的PSNR最高,但输出图像出现了伪影,反而EDSR的结果更自然。所以实际部署时,我建议同时看PSNR和LPIPS(感知相似度),后者更符合人眼感受。
实战建议:选模型要看场景
如果你做的是实时应用(比如手机拍照超分),FSRCNN或ESPCN就够了,速度快,效果也能接受。如果是离线处理(比如老照片修复),直接上SwinIR,效果最好。如果是文档类图像(文字、表格),SRCNN或VDSR反而更稳,因为Transformer模型有时会把文字边缘过度平滑。
训练时有个通用技巧:先用L1 loss训练,再用L2 loss微调。L1 loss收敛快,但容易产生锯齿;L2 loss收敛慢,但纹理更平滑。我一般先用L1训练80个epoch,再用L2微调20个epoch,效果比只用一种loss好。
最后提醒一句:别迷信SOTA。我见过太多人花两周调SwinIR的参数,最后效果还不如一个调好学习率的EDSR。超分这个领域,数据质量、训练策略、后处理(比如自集成)往往比模型结构更重要。