019、BSRGAN盲超分:退化空间学习与无监督域适应的理论突破
019、BSRGAN盲超分:退化空间学习与无监督域适应的理论突破
从一张模糊到怀疑人生的照片说起
去年帮朋友处理一批老照片,他拿来的是一堆用诺基亚N95拍的、2008年北京奥运会的现场图。我心想,不就是超分嘛,SRGAN、EDSR、RCAN轮番上阵,结果出来的效果让我差点把键盘摔了——纹理全是伪影,人脸像蜡像,天空出现莫名其妙的彩色条纹。问题出在哪?这些模型训练时用的退化模型是双三次下采样,而真实照片的退化过程复杂得多:镜头像差、传感器噪声、压缩伪影、运动模糊、散焦模糊……这些因素叠加在一起,根本不是一个简单的下采样能模拟的。
这就是盲超分(Blind Super-Resolution)要解决的问题——在不知道退化核的情况下,把低分辨率图像恢复成高分辨率。BSRGAN是2020年由Kai Zhang等人提出的,它没有用花哨的网络结构,而是在退化建模上做了两件极其聪明的事:退化空间学习和无监督域适应。这篇文章我会把这两块掰开揉碎,结合我踩过的坑,给你讲明白。
退化空间学习:别再用单一退化核骗自己了
传统超分模型为什么在真实图像上翻车?因为它们训练时只用了一种退化方式,比如双三次下采样。这相当于你只学会了在游泳池里游泳,突然被扔进大海,不淹死才怪。BSRGAN的思路是:既然真实退化是多样的,那我就模拟一个足够大的退化空间,让模型见过各种“风浪”。
具体怎么做?BSRGAN的退化模型包含两个核心组件:
模糊核的随机采样。代码里是这样写的:
# 这里踩过坑:模糊核大小不能固定,要随机kernel_size=random.choice([7,9,11,13,15])# 别写死成11kernel=generate_gaussian_kernel(kernel_size,sigma_range=(0.2,4.0))# sigma范围要够宽,不然模型学不到大模糊的情况注意,这里的模糊核不是单一的,而是从各向同性高斯、各向异性高斯、甚至广义高斯分布中随机采样。每个batch的模糊核都不同,模型被迫学会适应各种模糊程度。我一开始偷懒,只用了各向同性高斯,结果模型对运动模糊(各向异性)完全没反应——这就是典型的“训练集偏差”。
噪声的复合建模。真实噪声不是简单的高斯白噪声,而是传感器噪声、量化噪声、压缩噪声的混合。BSRGAN用了一个噪声生成器:
# 别这样写:noise = np.random.randn(*img.shape) * sigma# 真实噪声是信号相关的,亮度越高噪声越大noise_level=np.random.uniform(1,30)# 噪声强度随机noise=generate_poisson_gaussian_noise(img,noise_level)# 这里有个细节:先加泊松噪声(模拟光子计数),再加高斯噪声(模拟读出噪声)这个复合噪声模型让BSRGAN在JPEG压缩图像上表现特别好。我试过用纯高斯噪声训练的模型处理微信压缩过的图片,结果全是块效应——BSRGAN就不会,因为它见过类似的压缩噪声。
无监督域适应:让模型自己学会“翻译”
退化空间学习解决了“见过各种退化”的问题,但还有一个更棘手的情况:真实世界的退化可能完全不在你模拟的退化空间里。比如老照片的褪色、胶片颗粒、扫描仪的光学畸变——这些很难用数学公式精确建模。
BSRGAN的解决方案是无监督域适应,核心思想是:让模型在真实低分辨率图像上做“自我校正”。具体分两步:
第一步:退化核估计。给定一张真实低分辨率图像,BSRGAN用一个轻量级的核估计网络(KernelGAN)来推断它的退化核。这个网络很小,只有几层卷积,但训练方式很巧妙——它用真实图像自己生成低分辨率版本,然后比较两者的统计特性。
# 这里踩过坑:核估计网络不能太大,否则过拟合classKernelEstimator(nn.Module):def__init__(self):super().__init__()# 只用4层卷积,别加太多self.conv1=nn.Conv2d(3,64,3,padding=1)self.conv2=nn.Conv2d(64,64,3,padding=1)self.conv3=nn.Conv2d(64,64,3,padding=1)self.conv4=nn.Conv2d(64,kernel_size*kernel_size,3,padding=1)# 最后输出是模糊核的权重,要保证和为1这个网络训练时用的损失函数是“判别器损失”——让估计的退化核生成的图像和真实图像在分布上无法区分。说白了,就是让模型猜“这张图是怎么变模糊的”。
第二步:退化感知的微调。有了估计的退化核,BSRGAN不是直接用它去反卷积,而是用这个核去重新生成训练数据。具体来说,从高分辨率图像库中取一张图,用估计的退化核做下采样,再加上估计的噪声水平,生成一对新的训练样本。然后用这对样本去微调超分网络。
# 别这样写:直接用双三次下采样# 正确的做法:用估计的核做卷积,再下采样estimated_kernel=kernel_estimator(lr_image)hr_patch=crop_random_patch(hr_database)lr_simulated=convolve_and_downsample(hr_patch,estimated_kernel)# 加上估计的噪声noise_level=estimate_noise_level(lr_image)lr_simulated+=generate_noise(lr_simulated,noise_level)# 用这对数据微调超分网络finetune_step(sr_model,lr_simulated,hr_patch)这个微调过程是迭代的:每处理一批真实图像,就更新一次退化核估计,再用新的核生成训练数据。模型在真实图像上的表现会越来越好,因为它在不断适应目标域的退化特性。
训练技巧:那些论文里没写但你必须知道的事
BSRGAN的训练过程有很多细节,我踩过的坑列出来,你直接避雷:
学习率策略。BSRGAN用了余弦退火,但初始学习率不能太大。我一开始设成2e-4,结果loss直接炸了。后来改成1e-4,配合warmup(前500步线性增加到1e-4),稳定多了。
# 推荐的学习率设置optimizer=Adam(sr_model.parameters(),lr=1e-4)scheduler=CosineAnnealingLR(optimizer,T_max=500000,eta_min=1e-7)# eta_min不能太小,否则后期学不动数据增强。BSRGAN在训练时用了随机旋转和翻转,但注意不要用颜色抖动——因为退化核估计对颜色敏感,颜色抖动会干扰核的估计。
Batch Size。BSRGAN的batch size建议设成16,太小了核估计不稳定,太大了显存不够。我用的是RTX 3090,16刚好。
损失函数权重。BSRGAN用了L1损失、感知损失和GAN损失的组合。权重比例是L1:感知:GAN = 1:0.1:0.01。感知损失权重不能太大,否则纹理过于平滑;GAN损失权重不能太小,否则细节不够。
实战效果:从翻车到真香
我拿BSRGAN处理了那批诺基亚老照片,效果让我震惊。之前用SRGAN处理的人脸像打了马赛克,BSRGAN恢复出了皮肤纹理和头发丝。天空的彩色条纹消失了,取而代之的是自然的渐变。建筑物的边缘不再有锯齿,而是清晰的直线。
但BSRGAN也不是万能的。对于严重模糊的图像(比如运动模糊超过20个像素),它还是会翻车。这时候需要先做去模糊,再做超分。另外,BSRGAN对低分辨率图像的尺寸有要求——最好是64x64以上,太小了核估计不准。
个人经验性建议
如果你要在自己的项目里用BSRGAN,我给你三条建议:
第一,别盲目照搬退化模型。BSRGAN的退化模型是针对自然图像的,如果你处理的是医学影像或卫星图像,需要重新设计退化空间。比如医学CT图像的噪声模型和自然图像完全不同,你得先分析目标域的退化特性。
第二,核估计网络要轻量。很多人觉得核估计网络越复杂越好,其实不是。核估计的本质是统计推断,网络太深反而会过拟合到训练集的退化模式上。我试过把KernelGAN从4层加到8层,结果在真实图像上的估计反而更差了。
第三,微调时要控制迭代次数。无监督域适应不是迭代越多越好。我观察到,微调超过5000步后,模型开始遗忘之前学到的通用超分能力,只在当前数据集上过拟合。建议每处理1000张真实图像就做一次验证,如果PSNR不再提升就停止。
BSRGAN的核心贡献不是网络结构,而是对退化建模的深刻理解。它告诉我们:在真实世界中,退化是复杂且多样的,与其设计更复杂的网络,不如先搞清楚数据是怎么变差的。这个思路不仅适用于超分,也适用于去噪、去模糊、去雨等所有底层视觉任务。
下次你遇到真实图像超分的问题,别急着调网络结构,先问问自己:我理解这个图像的退化过程吗?如果答案是否定的,BSRGAN的退化空间学习和无监督域适应,就是你最好的起点。
