RealESRGAN退化代码拆解:高质量超分训练数据合成实战指南
发布时间:2026/9/8 11:38:02 作者:尧图编辑部 阅读量:1,286

简介面向图像超分与深度学习开发者这份退化代码包用于模拟真实图像老化与质量下降过程帮助使用者构造低分辨率训练数据是复现和训练RealESRGAN模型的关键预处理工具。压缩包共20个文件以9个py源码为主辅以对应pyc编译文件和2张png示例图整体仅70KB结构清晰源码涵盖退化核心流程、DiffJPEG、图像工具、变换与日志等模块便于按需阅读和二次开发。该资源已有860人学习。内容完整呈现下采样、噪声、模糊、JPEG压缩等退化操作并涉及GAN对抗训练、超分辨率重建、损失函数设计、优化算法等核心知识点通过Degradations类可灵活组合多种退化方式模拟真实世界的复杂降质过程。研读后可快速搭建训练数据生成流程支撑模型复现、改进及面向特定图像恢复任务的迁移应用。 你要是用过 RealESRGAN八成把注意力都放在那个生成器网络、感知损失、对抗训练上。但我自己把这个项目前前后后吃透之后最大的感触是让 RealESRGAN 真正“Real”的不是网络结构而是那套听起来不起眼的退化代码。不少人管它叫“RealESRGAN 退化代码”说得直白一点它就是训练数据合成管道把高清图一步步“弄脏”模拟真实世界里模糊、噪声、压缩痕迹混在一起的低质量图像。这篇博文就围绕这套退化代码展开。我会讲清楚它为什么是超分模型的地基模糊核、下采样、噪声、JPEG 压缩、二阶退化这些模块分别做了什么怎么把退化代码单独摘出来给你的模型造训练集以及我在实际调试过程中踩过的坑。适合正在做图像超分、复原、去噪、去压缩伪影的研究生和工程师也适合那些想知道 RealESRGAN 为什么效果好、但又被源码劝退的初学者。1. RealESRGAN 最被低估的部分其实是数据合成1.1 为什么双三次下采样撑不起“真实”超分学术界早期做超分训练数据基本就是“高清图 双三次下采样”这一条路。这么干的好处是干净、可控坏处也直接模型只在双三次退化上见过世面遇到真实世界的低清图就原形毕露。真实照片的低清不是一次双三次插值造成的而是镜头散焦、手抖、传感器噪声、压缩算法、传输损耗层层叠加的结果而且不同设备、不同场景的退化程度天差地别。RealESRGAN 的做法很朴素模型结构不搞花活那就从数据入手把退化学空间做得足够大、足够接近真实。退化代码存在的意义就是让训练时的低清图分布尽量覆盖真实世界遇到的情况。模型只要在训练时见过足够多样的“脏图”推理时面对陌生低清图才知道怎么把细节补回来。1.2 把单次退化改成高阶退化效果有什么不同RealESRGAN 之前BSRGAN 已经验证了“随机退化”的收益。RealESRGAN 在此基础上又往前走了一步明确提出了高阶退化模型。所谓高阶退化不是把模糊、噪声、压缩各做一遍就收工而是把整套退化流程随机重复两轮每轮的模糊核、噪声强度、压缩质量全部重新随机。我一开始也觉得重复一遍有点多此一举后来看了论文里的对比实验才明白真实照片的退化往往是复合的比如先有光学模糊再有传感器噪声然后是 ISP 处理留下的伪影最后又经过一次压缩。这种“退化中的退化”用单轮模拟很难覆盖。高阶退化像两层滤镜叠加第一遍把清晰图打乱第二遍让乱的程度更接近真实世界。训练时二阶退化产生的低清图质量分布明显更贴合真实照片。1.3 顺着退化代码往深了挖还会发现什么把退化代码单独抽出来看你会发现它就是一个通用的“清晰图 → 低清图”生成器。它不绑死超分任务拿来训练去噪网络、去压缩伪影网络、视频修复模型甚至做数据增强都没问题。这一点我觉得是 RealESRGAN 给整个低层视觉领域留下的最大财富网络结构会过时但一套精心设计的退化流程可以反复复用。我后来自己训练轻量级超分模型时就直接把 RealESRGAN 的退化流程迁移了过来省掉了大量造数据的麻烦。所以这篇博文不只讲原理更想给你一套能搬去自己项目的工具箱。2. 退化 Pipeline 拆到函数级每个环节到底在干什么2.1 模糊核生成广义高斯核和 sinc 核的搭配模糊是真实退化里最常见的成分。RealESRGAN 的退化代码里模糊核主要分两类一类是广义高斯核另一类是 sinc 核。广义高斯核说白了就是普通高斯核的“变体”通过控制方差和形状参数可以生成从细长条到圆胖型的各种核。标准差越大图像被抹得越厉害形状参数偏离 2 时核会变得不那么圆能模拟镜头像差、轻微运动模糊这类各向异性的情况。代码里通常会从 0.6 到 12 这个范围随机取方差覆盖从轻微发虚到严重失焦的区间。sinc 核模拟的是散焦和光学衍射它在频域上表现为一个低通滤波器作用在图像上会产生轻微的振铃效应。真实照片在镜头没对准时边缘确实会有这种“光圈感”。退化代码里生成 sinc 核后会按一定概率和广义高斯核做混合这样得到的模糊效果比单一核丰富得多。实际实现中大尺寸模糊核直接做卷积会很慢退化代码一般会把核 pad 到和图像同尺寸在频域里做逐元素相乘再反变换回来也就是 FFT 加速卷积。这个技巧在处理 21×21 甚至更大的核时非常管用训练时能省下不少时间。import torch def apply_blur_fast(img, kernel): # img: [B, C, H, W] 归一化到 [0, 1]kernel: [1, 1, K, K] B, C, H, W img.shape k torch.zeros(B, 1, H, W, deviceimg.device) kh, kw kernel.shape[-2:] k[:, :, :kh, :kw] kernel.to(img.device) # 把核中心平移到左上角才能与 FFT 的相位对齐 k torch.roll(k, shifts(-kh // 2, -kw // 2), dims(-2, -1)) img_f torch.fft.rfft2(img) k_f torch.fft.rfft2(k) return torch.fft.irfft2(img_f * k_f, s(H, W))2.2 下采样随机插值方式而不是固定双三次有了模糊核接下来就是降分辨率。退化代码不会固定用双三次而是在最近邻、双线性和双三次之间随机选一种。不同插值方式产生的锯齿、平滑程度差异很大模型在训练时见过各种下采样痕迹才不会对某一种插值产生过拟合。这里有个容易忽略的细节RealESRGAN 的下采样不一定一步到位。代码里有时候会先下采样一部分再继续下采样到目标尺寸中间还可能做一次轻微模糊。这种“分步缩图”更接近真实图像在不同分辨率间被反复缩放的情况。实现时一般直接调用 PyTorch 的F.interpolate指定scale_factor1/scale然后配合align_cornersFalse保证和推理时的坐标对齐习惯一致。2.3 噪声注入高斯噪声和泊松噪声的取舍传感器噪声和低光环境引入的噪声是真实低清图的常客。退化代码里噪声分成两类高斯噪声负责模拟传感器的加性噪声泊松噪声负责模拟光子计数波动带来的信号相关噪声。泊松噪声的特点是亮区噪声更明显暗区相对干净这比单纯的高斯噪声更贴近真实物理过程。具体实现时高斯噪声会在给定范围内随机取标准差常见区间是 1 到 15基于 255 尺度。泊松噪声则是先把像素值换算成光子计数再做一次采样。实际训练时退化代码会在高斯和泊松之间随机选一种而不是同时叠加这样能保证每种噪声类型都被模型充分“见过”。一个小提醒噪声强度如果拉满低清图会糊到连轮廓都看不清训练出来的模型会趋向于拼命降噪导致五官、纹理全被抹平。噪声范围要根据你的目标场景来定如果是老照片修复噪声可以弱一点重点保留模糊和压缩痕迹。2.4 JPEG 压缩无损的梦碎在有损编码网络图片、聊天传输、老照片扫描件几乎都逃不过 JPEG 压缩。JPEG 的块效应和振铃是超分模型特别容易翻车的点因为它在压缩时会把图像分成 8×8 的块做 DCT 变换产生明显的网格状伪影。退化代码里会对图像做一次随机质量因子的 JPEG 压缩质量因子通常在 30 到 95 之间随机抽。实现上一般借助 PIL 或 OpenCV。先把 PyTorch 张量转成uint8的 numpy 数组用Image.fromarray转成 PIL 图像保存到内存缓冲区再重新读回来。整个过程在 CPU 上做训练时如果每次迭代都走这一步会成为性能瓶颈。我自己的做法是开多个 worker 进程并行做数据加载让这批图像的压缩和下一批图像的读取重叠起来。2.5 二阶退化整体再来一遍的魔力二阶退化听起来复杂实现上就是把上面四个环节按随机顺序完整跑两遍但两遍之间的概率是独立的。第一遍可能先用小核模糊加轻度 JPEG第二遍再上一个较大的 sinc 核叠加泊松噪声再压一次质量很低的 JPEG。这样低清图里既有第一层的模糊能量又有第二层叠加出来的块效应和噪点层次感瞬间就出来了。我刚开始训练的时候偷懒只用了一阶退化模型在公开测试集上还行一到自己拍的手机照片就崩细节全部虚掉。后来把二阶退化打开肉眼可见地稳了。原因不难理解单轮退化产生的低清图太“纯”模型学会的是“从一种模糊里恢复”而不是“从一堆未知退化里恢复”。二阶退化强制模型见招拆招泛化能力才真正提上来。需要说明的是二阶退化不是每个样本都启用代码里一般会设一个概率比如百分之三四十的样本走二阶其余走一阶。这样训练集里既有温和的退化也有激烈的复合退化模型不会全部朝着“重度修复”的方向跑偏。3. 实战把退化代码单独拎出来造自己的训练数据3.1 准备环境和最小依赖退化代码并不依赖完整的 RealESRGAN 训练框架。最核心的东西就是 PyTorch、NumPy、Pillow、OpenCV。如果你要跑完整版退化流程建议直接把basicsr仓库里data/transforms.py相关的函数抽出来或者直接看官方realesrgan仓库里的数据增强部分。我习惯复制到自己的项目里再改这样不会被仓库整体更新影响。环境配置很简单装 PyTorch 之后再pip install opencv-python pillow numpy就够了。JPEG 压缩那块用 Pillow 的save和open即可没必要为了几个指标引入重型图像库。3.2 一个能直接跑的简化版退化脚本下面这份代码是我把 RealESRGAN 退化流程精简后的版本保留了模糊、下采样、噪声、JPEG、二阶退化这几个核心环节方便你理解主干逻辑。如果你想直接复现官方效果还是建议切到官方仓库跑我这里做的是概念演示但流程和真实代码是一致的。import random import numpy as np import torch import torch.nn.functional as F from PIL import Image def gaussian_kernel(k_size21, varNone): if var is None: var np.exp(np.random.uniform(np.log(0.6), np.log(12.0))) coords np.arange(k_size) - k_size // 2 x, y np.meshgrid(coords, coords) kernel np.exp(-(x ** 2 y ** 2) / (2 * var)) return kernel / kernel.sum() def sinc_kernel(k_size21, omega3.3): coords np.arange(k_size) - k_size // 2 x, y np.meshgrid(coords, coords) r np.sqrt(x ** 2 y ** 2) 1e-8 kernel np.sinc(r * omega / np.pi) kernel[r k_size // 2] 0 return kernel / (kernel.sum() 1e-8) def pil_jpeg(img_tensor, qualityNone): # img_tensor: [0,1] 的 4D 或 3D tensor arr (img_tensor.detach().cpu().numpy() * 255.0).clip(0, 255).astype(np.uint8) if arr.ndim 3: arr np.transpose(arr, (1, 2, 0)) if quality is None: quality random.randint(30, 95) im Image.fromarray(arr) buf io.BytesIO() im.save(buf, formatJPEG, qualityquality) buf.seek(0) out Image.open(buf).convert(RGB) out np.array(out).astype(np.float32) / 255.0 if out.ndim 3: out np.transpose(out, (2, 0, 1)) return torch.from_numpy(out) def degrade_high_order(img, scale4): # img: [C,H,W] tensor, [0,1] img img.unsqueeze(0) def one_order(x): kernel gaussian_kernel(21) if random.random() 0.3: kernel sinc_kernel(21) # FFT 模糊 k torch.zeros_like(x) k[:, :, :kernel.shape[0], :kernel.shape[1]] torch.from_numpy(kernel).float() k torch.roll(k, shifts(-kernel.shape[0] // 2, -kernel.shape[1] // 2), dims(-2, -1)) x_f torch.fft.rfft2(x) k_f torch.fft.rfft2(k) x torch.fft.irfft2(x_f * k_f, s(x.shape[-2], x.shape[-1])) mode random.choice([nearest, bilinear, bicubic]) h, w x.shape[-2:] x F.interpolate(x, scale_factor1 / scale, modemode, align_cornersFalse if mode ! nearest else None) # 噪声 if random.random() 0.5: sigma random.uniform(1, 15) / 255.0 x x torch.randn_like(x) * sigma # JPEG if random.random() 0.8: x pil_jpeg(x.squeeze(0), random.randint(30, 95)).unsqueeze(0) return x lq one_order(img) if random.random() 0.4: lq one_order(lq) return lq.squeeze(0)这段代码里的核心逻辑和官方思路是一致的第一遍先随机模糊核随机下采样随机加噪声随机压 JPEG。第二次退化时所有参数重新随机相当于在已经劣化的图上再施一遍暴。如果你觉得流程里少了颜色抖动、水平翻转这些增强没关系那部分不属于退化放在训练前的 transform 里做就行。3.3 生成完怎么检查数据、怎么调参数拿到一张低清图先别急着丢给网络。我一般会做三件事第一把低清图和原图拼在一起放大到 200% 左右观察边缘确认模糊是柔和过渡而不是生硬的马赛克第二看噪声形态高斯噪声是细密的颗粒泊松噪声在亮部更明显如果整张图像蒙了一层灰说明噪声强度过高第三检查 JPEG 块效应如果 8×8 网格清晰可见质量因子可能抽得太低。参数调优没有绝对标准但有个通用原则你希望模型在真实场景里表现好退化谱就要拉宽你希望模型在特定任务上表现稳退化谱就要收窄。比如你只做老照片修复模糊核尺寸可以固定在 15 到 25 之间质量因子控制在 40 到 85二阶退化概率提高到一半以上。如果你做监控画面清晰化噪声偏大、压缩偏重是常态那高斯噪声的标准差区间可以上调。4. 退化和训练中的坑还有排查思路4.1 常见问题速查表现象可能原因排查与解决低清图糊成一片完全没细节模糊核方差太大或核尺寸过大把max_var从 12 调到 8核尺寸从 21 降到 15 试试边缘出现一圈圈振铃sinc 核的omega设置过高降低omega或降低 sinc 核的出现概率图像像蒙了一层雾噪声强度过高或 JPEG 质量太低把高斯噪声 sigma 区间下调到 1-10质量因子下限抬到 45训练速度特别慢FFT 模糊没生效或 JPEG 压缩在 CPU 单线程跑检查是否真的用了rfft2JPEG 部分用多 worker 数据加载模型输出“蜡像脸”退化谱太强模型倾向极端去模糊降低二阶退化概率适度收窄模糊核噪声范围低清图颜色偏色严重PIL 转张量时 RGB 和 BGR 弄混统一用 PIL 的 RGB 通道不要和 OpenCV 混用4.2 容易被忽略的四个细节第一个细节是像素值范围。退化流程中 FFT 模糊要求输入在 [0,1] 浮点范围JPEG 压缩则要求 [0,255] 整数范围来回转换时很容易引入误差。我遇到过生成数据整体偏暗的情况排查半天发现是uint8截断时把零星溢出值处理错了。建议所有模块统一约定模型输入输出为 [0,1] 浮点张量只有在 JPEG 压缩的瞬间才对齐到 [0,255]。第二个细节是随机种子。退化代码里大量用了random和numpy.random多进程数据加载时如果不给每个 worker 设置独立的随机种子你会发现同一轮 epoch 里出现大量重复的低清图。最简单的办法是在每个 worker 的初始化函数里调用一次random.seed(worker_id epoch)。第三个细节是模糊核的归一化。核必须保证所有权重之和为 1否则模糊之后图像整体亮度会发生偏移模型会把这种亮度变化当成特征学进去。如果发现生成的低清图忽亮忽暗第一反应就查核的归一化。第四个细节是退化流程的顺序。官方实现里模糊一定在下采样之前这样更符合物理直觉先有光学模糊再经过采样导致分辨率降低。如果把下采样放前面图像已经变得稀疏再补模糊只会让边缘发虚效果完全不一样。你可以在自己的代码里加一句注释防止未来回看时改乱顺序。5. 关于退化代码我最后想说的几句话这套退化代码我前后用了大半年一开始只是照着官方脚本跑后来慢慢拆成自己的工具函数。我的体会是超分模型的瓶颈往往不在网络结构而在训练数据的退化分布。你花一周调模型结构可能不如花一周把退化参数重新梳理一遍后者带来的提升往往更明显。最后再分享一个小技巧你可以把退化代码和 diffusion 采样结合在一起做测试先随机生成一批低清图用你的模型跑一遍然后把那些最不适用的退化参数打出来看看是模糊核过大还是 JPEG 太过。这个做法相当于用“失败样本”反向指导调参比盯着平均 PSNR 有效得多。希望这篇博文能让你绕开我踩过的坑把这套代码真正变成自己的武器。本文还有配套的精品资源点击获取