MMagic 中的 RealBasicVSR 实战:真实世界视频超分辨率的网络原理、两阶段训练与推理指南
【免费下载链接】mmagic
OpenMMLab Multimodal Advanced, Generative, and Intelligent Creation Toolbox. Unlock the magic 🪄: Generative-AI (AIGC), easy-to-use APIs, awsome model zoo, diffusion models, for text-to-image generation, image/video restoration/enhancement, etc.
本指南以 OpenMMLab MMagic 仓库中的 RealBasicVSR(CVPR'2022)实现为主线,系统讲解真实世界视频超分辨率(Real-World Video Super-Resolution)所面临的两大权衡(长期传播带来的伪影放大、训练代价带来的速度/性能权衡),并完整拆解仓库内两套官方配置:无 GAN 的第一阶段训练配置与带感知/对抗损失的第二阶段微调配置。读完本文,你将掌握 RealBasicVSR 的网络结构(图像清洗模块 + BasicVSR 传播主干)、随机退化(stochastic degradation)数据增强流水线的每个环节、tools/train.py / tools/test.py 的 CPU/单卡/多卡训练与测试方法,以及如何在 VideoLQ、REDS、UDM10 数据集上复现官方结果。
算法背景:为什么要为真实世界 VSR 引入"图像预清洗"
RealBasicVSR 论文《RealBasicVSR: Investigating Tradeoffs in Real-World Video Super-Resolution》发表于 CVPR'2022,其核心观察是:真实世界的视频退化(degradation)具有极大的多样性与复杂性,给训练和推理都带来了不小的挑战(见 configs/real_basicvsr/README.md 的 Abstract)。
从源码结构看,这个观察直接映射到了网络设计上:在 real_basicvsr_net.py 中,RealBasicVSRNet 由两个子模块串联组成——
- 图像清洗模块(image cleaning module):
ResidualBlocksWithInputConv(3, mid_channels, num_cleaning_blocks)再接一个nn.Conv2d(mid_channels, 3, 3, 1, 1),作用是在传播之前先行降低噪声与伪影; - BasicVSR 传播主干:直接复用
BasicVSRNet(mid_channels, num_propagation_blocks, spynet_pretrained),其中 SPyNet 光流网络权重被固定(requires_grad_(False)),仅作为运动估计的先验(见 basicvsr_net.py 中的ResidualBlocksWithInputConv与SPyNet定义)。
论文的核心论点在配置和源码中均有体现:
- 长期传播的两面性:轻度退化下,长期传播能显著提升性能;但面对严重的真实世界退化,传播会放大伪影、损害输出质量。因此必须在传播之前加一个预清洗阶段来抑制噪声与伪影——这正是
image_cleaning模块存在的原因。 - 训练代价问题:为了提升泛化性,真实世界 VSR 模型常用多样化退化来训练,需要增大 batch size 以获得稳定梯度,从而带来 1) 速度-性能权衡、2) 序列长度-批量大小权衡。论文提出随机退化方案(stochastic degradation scheme),可减少最多 40% 的训练时间而不牺牲性能;并建议训练时使用更长的序列而非更大的 batch,以更有效地利用时序信息、获得更稳定的推理表现。
- VideoLQ 数据集:为公平比较,论文提出包含大量真实世界低质量视频序列的 VideoLQ 数据集,本仓库的测试配置即默认在该数据集上评估(见下文数据准备小节)。
两阶段训练策略:先无 GAN 训练,再感知/对抗微调
仓库内 configs/real_basicvsr/ 目录下提供两套配置,对应官方推荐的两阶段训练流程(见 README_zh-CN.md):
- 第一阶段(无 GAN):使用 realbasicvsr_wogan-c64b20-2x30x8_8xb2-lr1e-4-300k_reds.py 训练一个不含感知损失和对抗性损失的模型(仅像素损失 + 清洗损失),共 300k iterations;
- 第二阶段(GAN 微调):以第一阶段权重为起点,使用 realbasicvsr_c64b20-1x30x8_8xb1-lr5e-5-150k_reds.py 加入感知损失与对抗性损失进行微调,共 150k iterations。
从配置继承关系可以清晰看到这一点:第二阶段的配置文件首行即为 _base_ = './realbasicvsr_wogan-c64b20-2x30x8_8xb2-lr1e-4-300k_reds.py',整体继承第一阶段的网络、数据流水线与数据加载器,仅在模型损失、优化器和训练轮数上做增量覆盖;文件内还保留了注释掉的 load_from 字段,指向第一阶段训练出的官方 checkpoint,用于作为微调起点(使用者取消注释并填入本地路径即可)。
此外,README_zh-CN.md 特别提示:可以先把图像裁剪成子图像以加快 IO,具体做法参见 preprocess_reds_dataset.py——这也是 train_dataloader 中 data_prefix=dict(img='train_sharp_sub', gt='train_sharp_sub') 使用 _sub 子图目录的原因。
配置文件深度解析(一):网络结构与损失设计
以第二阶段配置 realbasicvsr_c64b20-1x30x8_8xb1-lr5e-5-150k_reds.py 为例,model 字段完整定义了生成器、判别器与全部损失:
model = dict(
type='RealBasicVSR',
generator=dict(
type='RealBasicVSRNet',
mid_channels=64,
num_propagation_blocks=20,
num_cleaning_blocks=20,
dynamic_refine_thres=255, # change to 5 for test
spynet_pretrained='https://download.openmmlab.com/mmediting/restorers/'
'basicvsr/spynet_20210409-c6c1bd09.pth',
is_fix_cleaning=False,
is_sequential_cleaning=False),
discriminator=dict(
type='UNetDiscriminatorWithSpectralNorm',
in_channels=3,
mid_channels=64,
skip_connection=True),
pixel_loss=dict(type='L1Loss', loss_weight=1.0, reduction='mean'),
cleaning_loss=dict(type='L1Loss', loss_weight=1.0, reduction='mean'),
perceptual_loss=dict(
type='PerceptualLoss',
layer_weights={
'2': 0.1,
'7': 0.1,
'16': 1.0,
'25': 1.0,
'34': 1.0,
},
vgg_type='vgg19',
perceptual_weight=1.0,
style_weight=0,
norm_img=False),
gan_loss=dict(
type='GANLoss',
gan_type='vanilla',
loss_weight=5e-2,
real_label_val=1.0,
fake_label_val=0),
is_use_sharpened_gt_in_pixel=True,
is_use_sharpened_gt_in_percep=True,
is_use_sharpened_gt_in_gan=False,
is_use_ema=True,
data_preprocessor=dict(
type='DataPreprocessor',
mean=[0., 0., 0.],
std=[255., 255., 255.],
))
各关键字段的作用如下:
| 字段 | 取值 | 含义 |
|---|---|---|
type | RealBasicVSR | 顶层模型类型,注册于 mmagic/models/editors/real_basicvsr/real_basicvsr.py,继承自 RealESRGAN |
mid_channels | 64 | 中间特征通道数,清洗模块与传播模块共享 |
num_propagation_blocks | 20 | BasicVSR 每个传播分支的残差块数量 |
num_cleaning_blocks | 20 | 图像清洗模块中残差块数量 |
dynamic_refine_thres | 255(训练)/ 5 或 1.5(测试) | 动态清洗停止阈值,源码中会除以 255 归一化;配置注释明确提示"测试时改为 5"(第一阶段配置注释为"改为 1.5"),见 real_basicvsr_net.py |
is_fix_cleaning | False | 是否固定清洗模块权重不参与训练 |
is_sequential_cleaning | False | 是否逐帧顺序清洗(节省显存但稍慢) |
pixel_loss | L1Loss,权重 1.0 | 像素级重建损失 |
cleaning_loss | L1Loss,权重 1.0 | 图像清洗模块的监督损失 |
perceptual_loss | PerceptualLoss(vgg19) | 感知损失,指定 vgg19 的 2/7/16/25/34 层权重,style_weight=0 即关闭风格项 |
gan_loss | GANLoss(vanilla) | 对抗损失,损失权重 5e-2,仅作用于生成器 |
is_use_sharpened_gt_in_* | True/True/False | 是否用反锐化掩膜(unsharp masking)后的 GT 分别作为像素、感知、对抗损失的监督目标 |
is_use_ema | True | 使用指数移动平均维护生成器 EMA 权重 |
data_preprocessor | mean=0,std=255 | 输入归一化方式,等价于除以 255 缩放到 [0,1] |
优化器与训练循环
第二阶段配置使用 MultiOptimWrapperConstructor 分别管理生成器与判别器的优化器,这也是 GAN 训练的标准做法:
optim_wrapper = dict(
_delete_=True,
constructor='MultiOptimWrapperConstructor',
generator=dict(
type='OptimWrapper',
optimizer=dict(type='Adam', lr=5e-5, betas=(0.9, 0.99))),
discriminator=dict(
type='OptimWrapper',
optimizer=dict(type='Adam', lr=1e-4, betas=(0.9, 0.99))),
)
train_cfg = dict(
type='IterBasedTrainLoop', max_iters=150_000, val_interval=5000)
生成器学习率 5e-5、判别器学习率 1e-4,二者均使用 Adam(beta=(0.9, 0.99));训练按 iteration 计数,共 150k 步,每 5k 步验证一次。第一阶段配置则只优化生成器(Adam, lr=1e-4),且显式注释"NO learning policy",即全程不使用学习率调度。
EMA 与检查点
第一阶段配置(被第二阶段继承)在 default_hooks 中定义了检查点保存策略:每 5000 iteration 保存一次、save_best='PSNR' 且 rule='greater';同时通过 custom_hooks 注册 ExponentialMovingAverageHook(module_keys=('generator_ema'),momentum=0.001,每步更新),并在 model_wrapper_cfg 中使用 MMSeparateDistributedDataParallel 包装模型(broadcast_buffers=False、find_unused_parameters=False),适配生成器/判别器分离优化的分布式训练场景。
配置文件深度解析(二):随机退化数据增强流水线
真实世界 VSR 训练的关键在于模拟真实退化。第一阶段配置的 train_pipeline 完整实现了论文提出的随机退化方案,全过程如下:
- 序列采样与加载:
GenerateSegmentIndices(interval_list=[1])生成连续帧索引;LoadImageFromFile以 RGB 顺序加载 GT 帧。 - 空间增强:
FixedCrop裁剪到 256×256;水平/垂直Flip(各 0.5 概率);RandomTransposeHW转置 H/W(0.5 概率);MirrorSequence镜像序列以增广时序。 - 反锐化掩膜生成 GT:
UnsharpMasking(kernel_size=51,sigma=0,weight=0.5,threshold=10)生成gt_unsharp,再由CopyValues将其复制为img——即把锐化后的图像当作后续退化的输入,这与模型配置中is_use_sharpened_gt_in_pixel=True相呼应。 - 第一轮随机退化(核心退化强度大):
RandomBlur:核尺寸 7~21,核类型含 iso/aniso/generalized_iso/generalized_aniso/plateau_iso/plateau_aniso/sinc 共 7 种,核概率[0.405, 0.225, 0.108, 0.027, 0.108, 0.027, 0.1],sigma_x/sigma_y 范围[0.2, 3];RandomResize:resize_mode_prob=[0.2, 0.7, 0.1](放大/缩小/保持),缩放因子[0.15, 1.5],插值方式 bilinear/area/bicubic 等概率;RandomNoise:高斯噪声 sigma[1, 30]与泊松噪声 scale[0.05, 3]各 0.5 概率,灰度噪声概率 0.4;RandomJPEGCompression:质量因子[30, 95],步长 3;RandomVideoCompression:libx264/h264/mpeg4 三种编码器等概率,码率[1e4, 1e5]。
- 第二轮随机退化(强度稍低):重复一轮模糊(prob=0.8、sigma
[0.2, 1.5])、缩放([0.3, 1.2])、噪声(sigma[1, 25])、JPEG 压缩,模拟"二次退化"的真实场景。 - 退化打乱与极端组合:
DegradationsWithShuffle将视频压缩与另一组退化(缩放到 64×64 + sinc 核模糊)以随机顺序/随机选择的方式组合,增加退化多样性。 - 收尾:
Clip将像素裁剪到合法范围,PackInputs打包成模型输入。
从源码角度看,这套流水线的设计意图与论文"用随机退化提升泛化性"的主张完全一致:每一轮退化都从宽泛的参数区间内随机采样,且两轮退化的强度分布刻意错开,使网络见到从轻微到极端的连续退化谱。
验证与测试流水线
val_pipeline:在 UDM10 上验证,filename_tmpl='{:04d}.png',直接加载 LQ(BIx4)与 GT;test_pipeline:在 VideoLQ 上测试,filename_tmpl='{:08d}.png';demo_pipeline:推理演示用,仅需img键。
验证与测试的评估器不同:val_evaluator 使用 PSNR/SSIM(监督指标),test_evaluator 使用 NIQE(无参考指标,input_order='CHW'、convert_to='Y'),对应 README 中"在 Y 通道上评估"的说明。
网络实现原理:清洗模块与动态清洗阈值
深入 real_basicvsr_net.py 可以看到 RealBasicVSRNet 的关键实现逻辑:
- 构造时
self.dynamic_refine_thres = dynamic_refine_thres / 255.,把配置中的整数阈值归一化到 [0,1] 区间; forward中最多执行 3 轮清洗(for _ in range(0, 3),论文经验值),每轮把残差加回 LQ 图像(lqs = lqs + residues),并用torch.mean(torch.abs(residues)) < self.dynamic_refine_thres判断是否提前停止——当残差足够小时说明清洗已收敛;- 清洗支持两种模式:
is_sequential_cleaning=True时逐帧顺序清洗(省显存、略慢);默认 False 时先把(n, t, c, h, w)展平为(n*t, c, h, w)一次性批量清洗(更快); - 清洗完成后再送入
self.basicvsr(lqs)做 4 倍超分(源码注释明确"Support only x4 upsampling")。
顶层模型 RealBasicVSR(见 real_basicvsr.py)继承自 RealESRGAN,并额外引入 cleaning_loss:训练时生成器以 return_lqs=True 前向,同时输出超分结果与清洗后的 LQ;g_step 中除常规像素/感知/对抗损失外,还计算 cleaning_loss(fake_g_lq, gt_clean)。其中 gt_clean 由 extract_gt_data 通过 F.interpolate(scale_factor=0.25, mode='area') 对 GT 做 4 倍面积下采样得到——即用 GT 的 1/4 分辨率版本监督清洗模块,语义上与"清洗到与退化前一致"的目标对齐。
仓库还配套了两个单元测试:test_real_basicvsr.py 与 test_real_basicvsr_net.py,分别验证顶层模型的训练/推理流程与网络前向/清洗逻辑,可作为理解实现细节与自行修改后的回归测试参考。
数据准备
训练与评估共涉及三个数据集(见 realbasicvsr_wogan-c64b20-2x30x8_8xb2-lr1e-4-300k_reds.py 中的 data_root = 'data'):
| 数据集 | 路径 | 用途 | 配置要点 |
|---|---|---|---|
| REDS | data/REDS | 训练 | train_sharp_sub 子图目录,num_input_frames=15,batch_size=2(第一阶段)/1(第二阶段),InfiniteSampler 无限采样 |
| UDM10 | data/UDM10 | 验证 | data_prefix=dict(img='BIx4', gt='GT'),评估 PSNR/SSIM |
| VideoLQ | data/VideoLQ | 测试 | 论文提出的真实低质量视频集,评估 NIQE |
BasicFramesDataset 的 metainfo 分别标注 dataset_type='reds'/'udm10'/'video_lq' 与 task_name='vsr'。训练数据默认期望已经预处理成子图(REDS 的 _sub 目录),预处理脚本为 preprocess_reds_dataset.py;如需了解数据目录的通用准备步骤,可参考文档 docs/en/user_guides/train_test.md 或中文版 docs/zh_cn/user_guides/train_test.md。
结果与模型
官方模型在 Y 通道上评估;其中 NRQM、NIQE、PI 使用 PIRM2018 提供的代码计算,BRISQUE 使用 MATLAB 官方代码计算(详见 configs/real_basicvsr/README.md)。
| 模型 | 数据集 | NRQM (Y) | NIQE (Y) | PI (Y) | BRISQUE (Y) | 训练资源 |
|---|---|---|---|---|---|---|
| realbasicvsr_c64b20_1x30x8_lr5e-5_150k_reds | REDS | 6.0477 | 3.7662 | 3.8593 | 29.030 | 8×Tesla V100-SXM2-32GB |
| realbasicvsr_wogan-c64b20-2x30x8_8xb2-lr1e-4-300k_reds | REDS | - | - | - | - | 8×Tesla V100-SXM2-32GB |
完整模型与训练日志的下载链接见上述 README 文档中的表格(模型 checkpoint 文件名分别为 realbasicvsr_c64b20_1x30x8_lr5e-5_150k_reds_20211104-52f77c2c.pth 与 realbasicvsr_wogan_c64b20_2x30x8_lr1e-4_300k_reds_20211027-0e2ff207.pth,在测试命令中可直接作为权重参数传入)。注意:第二阶段模型正是第一阶段模型的微调产物,其指标(NRQM/NIQE/PI/BRISQUE)即为 README 表格中的完整结果。
快速开始:训练与测试
训练
以下命令训练第二阶段 GAN 模型(第一阶段训练把命令中的配置换成 realbasicvsr_wogan-c64b20-2x30x8_8xb2-lr1e-4-300k_reds.py 即可):
# CPU 训练
CUDA_VISIBLE_DEVICES=-1 python tools/train.py configs/real_basicvsr/realbasicvsr_c64b20-1x30x8_8xb1-lr5e-5-150k_reds.py
# 单 GPU 训练
python tools/train.py configs/real_basicvsr/realbasicvsr_c64b20-1x30x8_8xb1-lr5e-5-150k_reds.py
# 多 GPU 训练(8 卡)
./tools/dist_train.sh configs/real_basicvsr/realbasicvsr_c64b20-1x30x8_8xb1-lr5e-5-150k_reds.py 8
训练入口为 tools/train.py,多卡脚本为 tools/dist_train.sh。更详细的训练配置说明(如数据集准备、batch size 调整、断点续训等)可参考 docs/en/user_guides/train_test.md 或 docs/zh_cn/user_guides/train_test.md。
测试
测试命令需要传入配置与预训练权重(将 <模型权重路径> 替换为上文提到的官方 checkpoint 文件或本地训练权重):
# CPU 测试
CUDA_VISIBLE_DEVICES=-1 python tools/test.py configs/real_basicvsr/realbasicvsr_c64b20-1x30x8_8xb1-lr5e-5-150k_reds.py <模型权重路径>
# 单 GPU 测试
python tools/test.py configs/real_basicvsr/realbasicvsr_c64b20-1x30x8_8xb1-lr5e-5-150k_reds.py <模型权重路径>
# 多 GPU 测试(8 卡)
./tools/dist_test.sh configs/real_basicvsr/realbasicvsr_c64b20-1x30x8_8xb1-lr5e-5-150k_reds.py <模型权重路径> 8
测试入口为 tools/test.py,多卡脚本为 tools/dist_test.sh。测试前请务必按配置注释将 generator.dynamic_refine_thres 从训练值(255)调整为测试值(配置注释分别给出 5 与 1.5 两个参考值),否则动态清洗的停止条件在推理时会与训练语义不一致。
引用
若你的工作使用了 RealBasicVSR 或其 MMagic 实现,请按如下方式引用原论文:
@InProceedings{chan2022investigating,
author = {Chan, Kelvin C.K. and Zhou, Shangchen and Xu, Xiangyu and Loy, Chen Change},
title = {RealBasicVSR: Investigating Tradeoffs in Real-World Video Super-Resolution},
booktitle = {Proceedings of the IEEE conference on computer vision and pattern recognition},
year = {2022}
}
小结:RealBasicVSR 在 MMagic 中的落地可以概括为一条清晰的链路——先用随机退化流水线在 REDS 上训练"像素损失 + 清洗损失"的基础模型,再用感知/对抗损失微调出最终模型;推理时,图像清洗模块以动态阈值决定清洗轮数,随后由 BasicVSR 主干完成 4 倍超分。理解这一链路(两阶段配置的继承关系、退化流水线的参数区间、dynamic_refine_thres 的训练/测试差异),即可在真实世界的低质量视频上复现官方结果,并进一步改造网络或流水线以满足自定义场景。
【免费下载链接】mmagic
OpenMMLab Multimodal Advanced, Generative, and Intelligent Creation Toolbox. Unlock the magic 🪄: Generative-AI (AIGC), easy-to-use APIs, awsome model zoo, diffusion models, for text-to-image generation, image/video restoration/enhancement, etc.
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)