018、Real-ESRGAN实战:高阶退化模型与真实图像盲超分训练
018、Real-ESRGAN实战高阶退化模型与真实图像盲超分训练从一次翻车说起去年接了个老照片修复的项目客户给了一堆上世纪八十年代的胶片扫描图。我信心满满地甩上ESRGAN结果出来的效果惨不忍睹——人脸像被揉过的纸团背景全是伪影。后来才意识到真实世界的退化远比我们训练用的双三次下采样复杂得多。模糊、噪声、压缩伪影、传感器噪声混在一起这就是所谓的“盲超分”问题。Real-ESRGAN就是冲着这个痛点来的。它不再假设退化过程是简单的双三次下采样而是用“高阶退化模型”模拟真实世界的复杂退化。今天这篇笔记我把自己踩过的坑和调参经验都写出来希望能帮你少走弯路。高阶退化模型到底在模拟什么传统超分训练我们通常这样造低分辨率图像# 别这样写——太理想化了lrF.interpolate(hr,scale_factor0.25,modebicubic)真实世界哪有这么干净一张手机拍的夜景照片可能同时包含镜头模糊高斯模糊或散焦模糊传感器噪声泊松-高斯混合噪声JPEG压缩块效应运动模糊手抖下采样过程中的锯齿Real-ESRGAN的退化模型分两个阶段每个阶段都包含模糊、下采样、噪声、JPEG压缩这四个操作。为什么搞两阶段因为真实退化往往是多次叠加的——比如先被相机镜头模糊然后被软件缩放再被社交媒体二次压缩。这里踩过坑一开始我试图用单阶段退化模拟所有情况结果模型学到的退化模式太单一遇到真实图像就崩。两阶段退化虽然计算量翻倍但泛化能力提升明显。代码实现中的关键细节退化流程的搭建核心逻辑在degradation.py里我贴一段自己魔改过的版本classRealESRGANDegradation:def__init__(self,sf4):self.sfsf# 放大倍数这里踩过坑别设太大4倍以上效果会崩defdegrade(self,img):# 第一阶段退化imgself._blur(img,kernel_size21)# 模糊核大小别小于15否则模拟不了真实镜头模糊imgself._downsample(img,scale0.5)# 先缩一半# 这里有个坑噪声强度要随机我试过固定噪声模型学到的去噪能力很差noise_levelnp.random.uniform(1,15)imgself._add_noise(img,noise_level)# JPEG压缩质量也要随机别写死qualitynp.random.randint(30,95)imgself._jpeg_compress(img,quality)# 第二阶段退化重复上述流程但参数不同imgself._blur(img,kernel_size15)imgself._downsample(img,scale0.5)# 第二次下采样凑出4倍# ... 重复噪声和JPEGreturnimg注意这里下采样分两次完成每次缩一半最终得到4倍下采样。为什么不一步到位因为真实相机的ISP pipeline里缩放往往是分阶段做的一步到位反而丢失了中间过程的退化特征。判别器的改动Real-ESRGAN的判别器用了U-Net结构而不是传统的PatchGAN。为什么因为真实图像的退化在不同区域差异很大——背景可能是平滑的模糊边缘却是锐利的压缩伪影。U-Net能输出像素级的判别结果让生成器知道“哪里该优化”。classUNetDiscriminator:def__init__(self):# 这里别用太深的网络我试过ResNet-101训练慢且容易过拟合self.encoderself._build_encoder(depth4)self.decoderself._build_decoder(depth4)defforward(self,x):featself.encoder(x)# 这里踩过坑decoder的skip connection一定要加否则判别器学不到局部细节outself.decoder(feat)returnout# 输出和输入同尺寸的判别图训练策略别让生成器太早学坏训练Real-ESRGAN最忌讳的就是生成器过早“放飞自我”。一开始生成器啥都不会判别器随便就能区分真假这时候如果让生成器去对抗一个太强的判别器它就会学出各种奇怪的纹理来骗判别器——结果就是伪影满天飞。我的经验是分三个阶段第一阶段前5000步只用L1损失和感知损失训练生成器冻结判别器。让生成器先学会基本的重建能力别急着对抗。第二阶段5000-20000步加入判别器但判别器的学习率要调低我通常设为生成器的0.1倍。这里有个小技巧判别器的更新频率设为每2个生成器步更新1次防止判别器太强。第三阶段20000步以后恢复正常对抗训练但要注意监控生成器的输出。如果发现伪影增多立即降低判别器权重。# 训练循环中的关键部分forstep,datainenumerate(train_loader):lr,hrdata# 生成器前向srgenerator(lr)# 第一阶段只用重建损失ifstep5000:lossL1_loss(sr,hr)0.1*perceptual_loss(sr,hr)loss.backward()optimizer_g.step()continue# 第二阶段加入对抗但判别器学习率减半ifstep20000:forparamindiscriminator.parameters():param.requires_gradFalse# 先冻结判别器# ... 计算生成器损失# 每2步更新一次判别器ifstep%20:# 解冻判别器更新pass数据增强被忽视的细节很多人训练Real-ESRGAN只做随机翻转和旋转这远远不够。真实图像的退化方向性很强——比如运动模糊有方向JPEG压缩有块对齐。我加了以下增强随机色域抖动模拟不同相机的色彩偏移随机伽马校正模拟曝光差异随机通道交换模拟拜耳阵列的差异这个对老照片特别有效别这样写数据增强# 错误示范固定顺序的增强transformCompose([RandomFlip(),RandomRotate(),ColorJitter()])应该随机组合甚至随机决定是否应用某个增强# 正确做法每个增强独立随机ifrandom.random()0.5:imgrandom_flip(img)ifrandom.random()0.7:imgcolor_jitter(img,strengthnp.random.uniform(0.1,0.5))真实图像盲超分的实战经验训练完模型后真正头疼的是推理阶段。真实图像千奇百怪我总结了几条血泪教训1. 预处理比模型更重要输入图像先做一次简单的去噪能显著提升效果。我用的方法是先用中值滤波去掉椒盐噪声再用BM3D做一次轻度去噪。别用太强的去噪否则细节全没了。2. 多尺度测试真实图像的退化尺度不确定我习惯把输入缩放到多个尺寸0.5x、0.75x、1.0x、1.25x分别推理后融合。融合方式别用简单的平均用像素级的置信度加权——判别器输出的判别图可以作为置信度。3. 后处理别忽略Real-ESRGAN的输出往往偏锐利但真实图像不需要那么锐。我会加一个可选的轻度高斯模糊或者用guided filter做边缘保持平滑。客户反馈说这样看起来更自然。调参路上的那些坑学习率生成器1e-4判别器1e-5这个比例我试了十几次才找到。判别器学习率太高生成器会学出奇怪的纹理太低判别器又学不动。batch size别小于4否则BN层统计不准。我用的8显存不够就梯度累积。感知损失权重0.1是个好起点但要根据数据集调整。人脸图像可以降到0.05因为人脸对纹理细节更敏感风景图可以升到0.2。训练时长在4张V100上256x256的图训练10万步大概需要3天。别急着看效果前2万步都是垃圾5万步后才开始稳定。个人经验总结Real-ESRGAN不是银弹。对于严重退化的图像比如1920年的老照片它依然会翻车。我的建议是先评估退化程度轻度到中度退化用Real-ESRGAN重度退化先用GAN做修复再超分。另外别迷信论文里的默认参数。我见过太多人直接跑官方代码效果不好就怪模型。实际项目中退化模型的参数模糊核大小、噪声范围、JPEG质量区间要根据你的数据分布来调。比如处理监控视频模糊核要设大一些噪声要设小一些处理老照片JPEG质量要设低但模糊核要多样化。最后说一句超分不是目的让图像看起来“对”才是。有时候降分辨率反而比升分辨率更实用——比如把4K降成1080p去掉噪声和伪影视觉效果反而更好。别被“超分”这个词限制住了思路。