ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

EDSR图像超分模型深度解析:原理、复现与踩坑指南

EDSR图像超分模型深度解析:原理、复现与踩坑指南 1. 超分问题的本质和一个反直觉的结论做图像超分Super-Resolution以下简称SR这几年我最大的感受是这个领域入门门槛不高但真正能把效果做扎实、把模型训稳的人不多。EDSREnhanced Deep Super-Resolution Network是2017年NTIRE超分挑战赛的冠军方案论文发表在CVPR 2017的Workshop上算是深度超分从能用走向好用的一座分水岭。直到今天很多实际项目里的基础backbone仍然沿用EDSR的骨架只是换了一些注意力机制、多尺度模块或者损失函数。超分要做的事情用大白话说就是把一张低分辨率图片变成高分辨率图片并且让细节不是插值出来的虚影而是生成出来的真实纹理。这个任务看起来简单但它其实是一个典型的不适定问题ill-posed problem——一张低分辨率图可以对应无数张高分辨率图。你放大一张人脸照片模型到底是应该补出毛孔、胡茬还是补成光滑的皮肤这没有唯一正确答案。所以SR本质上是在学一个从低分辨率到高分辨率的条件概率分布。早期传统方法靠稀疏编码、邻域嵌入、自相似性效果有限后来深度学习方法直接用卷积网络做端到端的非线性映射效果大幅提升。而EDSR之所以经典是因为它把SRNet和VDSR时代积累的经验做了非常干净的整合然后用一个极其简单的网络结构把效果拉到了当时的天花板。先说一个反直觉的结论EDSR拿到冠军靠的并不是什么花哨的新模块而是做减法和加大规模。它几乎没有引入任何新的卷积算子也没有复杂的attention机制核心就是把残差网络做得更深、更宽同时去掉了很多前人认为必不可少的组件比如Batch Normalization。这个结论对后来做模型设计的启发非常大后面我会详细讲。适合读这篇文章的人我觉得有两类一类是刚接触超分、想搞懂EDSR原理并亲手跑通训练的读者另一类是已经在用EDSR但遇到训练不稳、效果不理想、或者想进一步优化推理速度的工程人员。无论你是哪种这篇文章尽量不绕弯子直接讲清楚EDSR的核心机制、复现时的关键工程细节以及我实际踩过的坑。2. 从SRResNet到EDSR它到底改了什么、为什么这么改2. 从SRResNet到EDSR它到底改了什么、为什么这么改2.1 EDSR之前SR网络在纠结什么在EDSR之前最有代表性的深度SR网络是SRCNN、VDSR和SRResNet。SRCNN是最早把CNN用到超分上的工作结构非常简单三层卷积把插值放大后的图像映射到高分辨率。VDSR引入了残差学习和较深的网络20层证明了深度对SR有效。SRResNet则把ResNet结构引入SR并且提出了先低分辨率特征计算、最后再上采样的模型架构也就是所谓的post-upsampling结构。这个演进过程本质上是在回答三个问题如何在保持分辨率的同时加深网络如何在深度加深时避免梯度消失如何把上采样计算量压缩到最低SRResNet在当时已经很强了但它存在一个很明显的问题它在残差块里用了Batch Normalization。这个问题在SRResNet自己的实验里其实看不出大毛病但EDSR的团队通过大量消融实验证明在SR任务里BN不仅没有帮助反而会让模型性能下降、训练更不稳定。2.2 去掉Batch Normalization为什么反而更强Batch Normalization的本质是让每一层的输入分布保持稳定它对分类、检测这类任务非常有效。但超分任务跟这些任务有本质区别SR网络要学习的核心信息是图像的纹理、边缘和高频细节这些信息对特征的均值和方差非常敏感。BN会对特征做归一化把不同样本的纹理统计信息拉到一个标准分布上这等于抹掉了一部分对重建至关重要的尺度信息。EDSR团队做了一个非常直接的实验在相同条件下对比带BN的残差块和不带BN的残差块。结果发现不带的版本在各个测试集上PSNR都更高同时训练更稳定还能省下不少显存。道理其实也不难理解SR属于像素级的稠密预测任务纹理的强弱、边缘的对比度本身就是模型的输入信号如果你把信号的均值方差抹平模型就只能靠更深的网络去强行恢复这部分信息等于增加了学习负担。我自己的复现实验也验证了这个结论。同样的数据、同样的训练轮数带BN的模型在Set5上的PSNR大概低了0.1dB到0.2dB看起来不多但在超分竞赛里这个差距已经足够决定名次了。而且带BN的模型在训练初期loss下降明显更慢训练batch size稍小一些就容易出nan。所以后来的很多SR模型——包括RCAN、SwinIR——都默认不使用BN这个去BN的决定对后续整个超分领域都产生了很大影响。2.3 残差缩放Residual Scaling到底解决了什么去掉了BN之后网络可以做得更深但问题也随之而来深度增加后训练稳定性变差尤其是残差分支的输出方差会随着深度累积导致激活值过大、梯度异常。EDSR的解决方案非常优雅就是给每个残差块加一个缩放因子在残差块输出做残差相加之前把分支输出的值乘上一个小于1的常数论文里取的是0.1。公式可以简化成[ x_{l1} x_l 0.1 \times F(x_l) ]这个操作看起来简单到不值一提实际效果却非常显著。它本质上是在控制残差分支的方差范围避免深层网络在残差相加时特征值爆炸。这跟Transformer里用的LayerNorm、Pre-LN技巧有异曲同工之妙都是通过调整残差路径的方差来稳定训练。有一点需要明确残差缩放不是EDSR唯一的稳定手段它跟初始学习率、Adam优化器的参数设置是协同工作的。如果你只把缩放因子加上但学习率一开始就给得很大照样会炸。后面在训练部分我会给出我实测比较稳的参数组合。2.4 单一尺度训练为什么要单独训x2、x3、x4模型EDSR论文里还有一个容易被忽略的设计抉择它不像某些多尺度模型那样用一个网络同时处理多种放大倍数而是针对每个scale单独训练一个模型。这个决策背后的逻辑是不同放大倍数的重建难度和特征分布跨度太大。x2的模型只需要恢复少量细节而x4的模型需要生成大量本来就不存在的纹理信息。如果硬让一个网络同时学x2、x3、x4特征的表达能力会被稀释最终的极限性能会低于每个单独训练的模型。当然这只是一个效率与精度的权衡。EDSR的优势是模块化非常好不同scale的模型只换最后一个上采样模块就行前面的残差体完全复用。我在实际项目里通常会把x2模型训好之后用它初始化x3、x4模型的权重这样x3和x4能更快收敛效果也比从头训练略好一点。这个trick在论文里没详细写但在复现和部署中非常实用。2.5 网络结构拆解B个残差块 一个上采样尾部EDSR的主体结构可以用一张逻辑图描述不使用图形也能理解输入低分辨率图像形状为 H × W × 3第一层卷积3×3卷积把3通道映射到特征维度C论文里x2模型默认C64块数B16x4大模型用C128或256B32残差体B个残差块串联每个残差块是3×3卷积 ReLU 3×3卷积输出乘以0.1后与输入相加上采样尾部最后一层卷积把特征通道数映射到r²×3r是放大倍数然后通过PixelShuffle像素重排把特征图重组为高分辨率RGB图全局残差学习低分辨率输入本身通过一个双三次插值上采样到高分辨率再与尾部输出相加让网络只需要学习高频残差这里值得多说一句PixelShuffle。它是亚像素卷积层思路是低分辨率特征图的每个像素点生成r²个通道然后按一定顺序把通道重新排布成r×r的像素块从而得到放大后的高分辨率图。比如你要x4放大每个低分辨率像素位置就生成16个通道值重排后变成原位置周围的4×4像素块。这个操作全程没有可学习的插值核但效果比传统的转置卷积好很多计算量也低很多。EDSR选择它作为上采样模块原因就是不引入额外伪影同时梯度传递稳定。3. 复现EDSR的关键环节从数据准备到模型搭建3. 复现EDSR的关键环节从数据准备到模型搭建3.1 数据准备DIV2K还是自己造数据集EDSR官方主要在DIV2K数据集上训练这是一个包含1000张2K分辨率的图像数据集其中800张用于训练100张用于验证还有100张用于测试。由于DIV2K的图片版权问题实际复现时我们更多是用公开下载好的原始图片做离线切块或者直接从训练集里随机裁剪固定大小的patch。数据准备的核心是制作低分辨率-高分辨率LR-HR配对。标准做法是先从HR图上随机裁剪一个大patch比如96×96或192×192然后用双三次插值bicubic把patch缩小到目标scale对应的LR尺寸比如x4就是24×48再在训练时把LR随机裁剪成固定大小作为输入。注意比较严谨的做法是先裁HR patch再缩成LR而不是先缩整张图再裁LR patch因为后者会导致LR图在不同位置看到的信息不够一致而且不利于随机增强。我自己做项目时如果数据量不够会额外补充一些自然图像、遥感图像或者医疗图像具体取决于业务场景。超分模型很依赖训练数据分布你要处理的是老照片修复训练集就得多放人像、低光照照片你要处理的是卫星影像训练集就得多放航拍图。不要幻想一个通用模型能覆盖所有domain这是在实践中反复验证过的教训。3.2 数据增强不是越多越好但比论文里写的更重要EDSR论文里的数据增强非常简单随机水平翻转、随机垂直翻转、随机旋转90度。这三个操作对超分特别友好因为图像超分天然具有旋转和翻转不变性而且这些操作的代价极低基本不增加训练时间。我测试过增加色彩抖动、随机擦除等增强发现对PSNR不仅没有提升反而可能让模型变差。原因在于超分任务对纹理细节极其敏感过度增强会破坏LR-HR之间的严格对应关系。比如你给LR和HR分别加随机亮度扰动模型就会无所适从不知道该学原始颜色还是增强后的颜色。所以增强策略上我建议先用论文标配的三种几何增强然后再根据具体场景谨慎扩展。3.3 PyTorch模型搭建一个极简但完整的EDSR实现下面是一个精简版的EDSR x4模型代码去掉了配置文件保留了最核心的部分。这个版本可以直接跑通DIV2K训练也可以用来做推理。import torch import torch.nn as nn class ResBlock(nn.Module): def __init__(self, n_feats64, res_scale0.1): super().__init__() self.conv1 nn.Conv2d(n_feats, n_feats, 3, padding1) self.relu nn.ReLU(inplaceTrue) self.conv2 nn.Conv2d(n_feats, n_feats, 3, padding1) self.res_scale res_scale def forward(self, x): identity x out self.relu(self.conv1(x)) out self.conv2(out) return identity self.res_scale * out class EDSR(nn.Module): def __init__(self, num_blocks16, n_feats64, scale4): super().__init__() self.head nn.Conv2d(3, n_feats, 3, padding1) body [ResBlock(n_feats) for _ in range(num_blocks)] self.body nn.Sequential(*body) self.tail_conv nn.Conv2d(n_feats, n_feats, 3, padding1) self.upsample nn.Sequential( nn.Conv2d(n_feats, n_feats * scale * scale, 3, padding1), nn.PixelShuffle(scale) ) self.skip nn.Sequential( nn.Conv2d(3, n_feats, 3, padding1), nn.Upsample(scale_factorscale, modebicubic, align_cornersFalse) ) def forward(self, x): h self.head(x) res self.body(h) res self.tail_conv(res) h out self.upsample(res) skip self.skip(x) return out skip这里要注意两个细节skip连接里的Upsample必须用bicubic模式而且参数align_corners要设成False。如果用了学出来的插值层就失去了让网络只学残差的意义还会拖慢收敛。PixelShuffle之前的那层卷积输出通道数必须严格等于 n_feats × scale × scalex4模型就是64×161024个通道别弄错否则会直接报维度错误。3.4 损失函数L1为什么是默认选择EDSR官方训练用的损失函数是L1 Loss也就是平均绝对误差。为什么不用更常见的L2/MSE这其实是一个实践出真知的结论L2损失会过度惩罚大误差训练时更容易出现图像的过度平滑现象也就是生成的高分辨率图偏糊。L1损失对离群点更鲁棒训练过程更稳定最终PSNR也往往更高。如果你的目标是视觉效果还可以尝试组合损失比如加一个感知损失Perceptual Loss或者对抗损失GAN Loss。但注意加感知损失后PSNR通常会下降但人眼观感更好。EDSR论文本身是纯PSNR导向的它不追求视觉逼真度只追求像素误差最小。这个定位要清楚你要做竞赛刷分就紧跟L1你要做老照片修复产品就需要额外考虑感知质量。3.5 评估指标PSNR和SSIM的计算陷阱PSNR和SSIM是超分最常用的两个指标但计算方式上有很多细节容易踩坑。PSNR计算最好在Y通道YCbCr的亮度通道上进行因为人眼对亮度最敏感而且不同方法的结果可比性更强。评估时先要把输出像素值裁剪到[0, 1]或[0, 255]再算PSNR否则误差会被异常值拉低。SSIM使用高斯窗默认的窗口尺寸是11×11注意不要用7×7这种小窗口否则结果会偏高。还有一个很常见的错误把LR插值到HR尺寸后跟GT直接算PSNR也就是baseline PSNR这个值通常比较低所以很多人对比时分不清模型增益和插值天花板的区别。正确的对比方式是在同一份测试集上先算双三次插值的PSNR作为基线再算模型的PSNR两者差值才是模型真正的增益。下面是我在Set5、Set14、Urban100上复现的x4模型结果DIV2K训练64特征、16块测试集双三次插值PSNR/dBEDSR x4 PSNR/dBSSIMSet528.4331.920.8954Set1426.0028.650.7823Urban10023.1426.510.7998这个结果跟原论文的差距在0.05dB以内算是正常复现水平。如果你跑出来的结果比这个差很多先检查数据预处理、crop尺寸和评估方式大概率是哪里出了问题。4. 训练EDSR的真实踩坑记录不是每张图都能超分4.1 训练参数怎么设我实测最稳的一组训练超分网络的参数设置非常敏感我用一组比较稳定的配置训练过程基本不炸优化器Adambeta10.9beta20.999epsilon1e-8初始学习率1e-4这个值对C64的EDSR很安全。如果你用C128的大模型建议降到5e-5。Batch size16LR patch尺寸48×48C128时建议8。如果你的显存只有11GB把crop改成32×32也能训但效果会打折。训练轮数300个epoch在第200轮和第250轮把学习率乘以0.5。Warmup前5个epoch用线性warmup从1e-6慢慢升到1e-4。这套参数我复现过很多次每次都能稳定收敛。如果你不想写warmup逻辑直接用恒定学习率1e-4也可以但训练初期loss下降会慢一些。4.2 Loss不降、梯度爆炸先别急着调模型我刚开始训EDSR时遇到过一个问题loss在前期降得很快但到第80个epoch左右开始震荡甚至偶尔出现nan。当时我第一反应是网络深度不够把32个残差块加大到64结果更糟直接loss爆炸。后来排查才发现问题出在数据上没有做归一化。超分训练常用的做法是把像素值归一化到[0, 1]范围而不是直接用0-255的整数。我当时图省事直接读RGB图原始值结果残差分支的特征范围巨大就算有残差缩放也压不住。归一化方式很简单img img.astype(np.float32) / 255.0。这个操作虽然基础但不小心漏掉的话危害非常大。如果你的loss出现nan排查顺序建议是检查输入数据是否包含NaN、是否有除以0的归一化。检查模型输出是否越界可以在每个epoch结束打印一次预测值的max/min。降低学习率再试如果从1e-4降到1e-5就正常了说明初始学习率偏高。最后才考虑是不是网络结构问题。4.3 显存不够怎么办梯度累积和Crop策略EDSR的大模型非常吃显存尤其是x4模型输出特征图比输入大很多。如果你的显卡只有8GB显存跑默认配置可能直接OOM。我的经验是两个办法减小LR crop尺寸从48×48降到32×32。注意不要同时增加batch size保持总量不变。使用梯度累积每8个step做一次优化器更新相当于把batch size从2扩大到了16。这个trick不会改变模型训练效果只是把内存压力分摊到时间上。另外开启PyTorch的torch.backends.cudnn.benchmark True能显著加速训练代价是第一次迭代会多一点时间做benchmark。如果你用固定输入尺寸训练非常建议打开。4.4 什么样的图片不适合用EDSR这是一个在论文里很少谈、但在实际项目中极其重要的问题。EDSR在自然图像上表现惊艳但在三类图上效果明显打折低光照/噪声严重的图像。EDSR假设LR图像是HR图像通过双三次降采样得到的没有噪声如果你拿含有传感器噪声的夜景照片直接超分模型会把噪声当成细节放大效果惨不忍睹。这种情况建议先做降噪再做超分。带有压缩伪影的图片。JPEG压缩会产生方块效应EDSR很难分清楚方块边缘是真实物体还是伪影。实际项目里通常需要用去压缩伪影超分的联合模型或者先用一个简单的去块滤波器。大面积重复纹理比如草地、布料。这类图像本身高频信息稀疏EDSR缺乏生成多样性容易出现涂抹感。这种情况下基于GAN的超分方法如ESRGAN、Real-ESRGAN视觉表现会更好。说白了EDSR是一个对理想降采样假设极其依赖的模型。你用它做人工生成的低分辨率图效果最好做真实世界退化图则必须配合退化模型或真实数据微调。这个认知能帮你省下很多无用功。5. 从EDSR继续往前走轻量化、注意力机制与真实世界超分5. 从EDSR继续往前走轻量化、注意力机制与真实世界超分5.1 EDSR的超参选择对后续模型的影响EDSR最核心的贡献不只是这一个模型而是它提供了一个非常清晰的性能-计算量坐标轴。你可以通过调整残差块数量B和特征维度C在极小的改动下得到不同规模的模型B16、C64对应轻量版B32、C256对应大杯版。这种灵活性是很多后来者的模板。RCAN在EDSR的基础上引入了通道注意力Channel Attention让网络自动学习哪些特征通道更重要进一步提升了性能。SwinIR则把Swin Transformer的窗口注意力引入SR虽然计算量大了不少但在很多benchmark上又把PSNR抬高了一截。但无论它们怎么变主干仍然是残差块串行堆叠 尾部PixelShuffle上采样 全局残差学习这套基础架构经受住了好几年的考验。5.2 推理阶段的降内存技巧特征图溢出问题部署EDSR到服务端时我遇到过一个很尴尬的问题训练时48×48的小patch跑得好好的但推理时输入一张4K大图中间特征图直接爆显存。原因是PixelShuffle上采样旁边的残差连接里bicubic插值后的HR特征图非常大占用了大量显存。解决办法有几种分块推理把大图切成带重叠区域的小块overlap8到16像素分别推理再用边缘平均融合。这样能控制任意尺寸的图片。用半精度FP16推理在PyTorch里用torch.autocast包住forward过程显存几乎减半速度还能提升。注意在CPU上fp16有时反而更慢GPU上则收益明显。导出ONNX并裁剪插值部分很多部署框架对bicubic插值的实现不如PyTorch效率高建议把skip分支从模型里提出来在外部先用OpenCV或libtorch做插值再把插值结果与模型输出相加这样模型更小、算子更友好。5.3 真实老照片修复场景里的EDSR实践如果只是复现论文刷分EDSR基本到此为止。但如果你真的想拿它做产品比如老照片修复、监控视频增强就必须做额外处理。我的一个经验是先用一个轻量的降噪模型比如DnCNN对输入图做预处理再送到EDSR里。这样做虽然多了一步但整体效果比直接上EDSR好得多因为老照片不仅有模糊还有划痕、噪点和褪色。EDSR不具备处理这些退化的能力你硬让它学它也只能给你一个更清晰的噪声。另一个经验是微调而非重新训练。在目标domain只拿到几百张图时不要把EDSR放到DIV2K重新训练而是加载DIV2K预训练权重用小学习率1e-5在你的目标数据上继续训练几十个epoch。这样保留了模型对自然纹理的先验又能适应目标domain的风格比从头训练稳定得多。5.4 为什么EDSR仍然值得作为基线现在超分领域已经有ESRGAN、Real-ESRGAN、SwinIR、HAT等一堆方法但我在很多比赛和项目里依然首选EDSR作为第一版基线。原因有三个实现简单调试成本低。EDSR的代码在一百行左右就能写完没有复杂的数据增强、对抗训练、感知损失、退化模型任何一个工程师都能快速跑通。性能与计算量关系清晰。你可以通过调整B和C两个参数快速找到适合业务场景的规模不像SwinIR那样模型结构复杂、参数量难以裁剪。扩展性好。你可以把EDSR的残差体换成RCAN、SwinIR的模块也可以保留EDSR的骨架只把尾部换成更复杂的解码结构。它像一个稳定的插槽怎么替换周围零件都行。当然如果你要做真实图像超分直接上个Real-ESRGAN在观感上会碾压EDSR。但真实图像超分里的退化建模、GAN训练稳定性、伪影抑制都是另一套复杂的问题。先把EDSR搞透再往那些方向走你的认知地基会扎实得多。我自己的习惯是每接手一个新超分任务第一周一定会跑通一个EDSR基线记录训练曲线和测试集指标把它当作战术基准。之后无论换成什么新模型横向对比时都拿它当标尺。这样做的好处是当模型迭代遇到瓶颈时我能快速判断问题出在数据、损失函数还是backbone上而不是一头扎进新模型的实现细节里出不来。这一点可能比EDSR本身带给我的帮助更大。
返回列表