059、RealBasicVSR 实战:真实场景视频超分的退化建模与优化技巧 059、RealBasicVSR 实战真实场景视频超分的退化建模与优化技巧去年有个项目让我头疼了整整两周——客户给了一段监控视频要我把里面车牌从模糊到能看清。我试了EDVR、BasicVSR效果都像隔着一层毛玻璃。后来翻到RealBasicVSR的论文才意识到问题出在哪我们一直在用理想化的双三次下采样做训练但真实世界的退化是复杂的、未知的、非均匀的。今天这篇笔记就是我当时踩坑后整理出来的实战经验。退化建模别再假设“干净下采样”了真实视频的退化过程用公式写出来大概是这样的低分辨率帧 (高分辨率帧 * 模糊核) ↓ 下采样 噪声 压缩伪影但问题在于模糊核的形状、噪声的分布、压缩的强度我们全不知道。BasicVSR那套假设双三次下采样的做法在真实场景下基本就是自欺欺人。RealBasicVSR的做法很聪明——它把退化建模成一个可学习的模块。具体来说它用了一个“退化编码器”来从输入的低分辨率帧中提取退化特征然后把这些特征注入到超分网络里。这里有个关键点退化编码器不是单独训练的而是和超分网络一起端到端优化。我踩过的坑是一开始我试图手动设计退化参数比如固定高斯核大小、固定噪声方差结果模型在测试集上泛化极差。后来改成让网络自己学退化表征效果直接翻倍。代码里这样写# 别这样写手动指定退化参数# blur_kernel GaussianBlur(kernel_size21, sigma2.0)# 正确做法让网络自己学self.degradation_encoderDegradationEncoder(in_channels3,out_channels64,num_frames5# 用连续帧来估计退化)# 这里踩过坑帧数太少退化估计不准帧数太多计算量爆炸5帧是个平衡点时序一致性别让帧之间“跳来跳去”视频超分和单图超分最大的区别就是时序一致性。你单帧做得很清晰但帧与帧之间闪烁、抖动用户一看就说“这不行”。RealBasicVSR用了两个技巧来解决这个问题第一个是时序传播模块。它把前一帧的超分结果和当前帧的低分辨率特征做对齐然后用一个ConvLSTM来传播时序信息。注意这里的对齐不是简单的光流而是用了一个可变形卷积DCN来做自适应对齐。DCN的好处是能处理大位移和遮挡但坏处是训练不稳定。我调试时发现DCN的offset学习率要调小默认的1e-4会导致训练初期震荡。建议设成1e-5等loss降下来再恢复。第二个是双向传播。BasicVSR用的是单向传播从前往后但RealBasicVSR改成了双向——先反向传播一次再正向传播一次。这样做的原因是真实视频中有些区域在后续帧中才会出现比如物体移出画面又移回来单向传播会丢失这些信息。代码实现时要注意内存管理# 双向传播时中间特征要缓存# 这里踩过坑如果不做梯度检查点16G显存根本跑不动forward_features[]foriinrange(num_frames):featpropagation_module(feat,aligned_feat)forward_features.append(feat)# 别这样写直接存所有帧的特征# 正确做法只存当前需要的用checkpointingifi%20:forward_features[i]checkpoint.checkpoint(forward_features[i])优化技巧从loss到学习率真实场景的退化复杂所以loss设计也要跟着变。RealBasicVSR用了三个loss的加权组合Charbonnier loss比L1更鲁棒对异常值不敏感。真实视频里经常有运动模糊、噪声L1会被这些异常值带偏。感知loss用VGG的特征层做对比保证纹理细节。但注意感知loss的权重不能太大否则会引入伪影。我试过1e-2和1e-3后者更稳。时序一致性loss计算相邻帧超分结果的光流误差强制帧间变化平滑。这个loss的权重建议从0.1开始调太大容易导致画面过于平滑像慢动作。学习率策略上我推荐用余弦退火warmup。warmup阶段前5个epoch学习率从0线性升到1e-4然后余弦衰减到1e-6。别用固定学习率真实场景的数据分布复杂固定学习率很容易陷入局部最优。还有一个容易被忽略的点数据增强。真实视频的退化是多样的所以训练时要做随机退化增强——随机模糊、随机噪声、随机JPEG压缩。我写了个增强函数defrandom_degradation(lr_frames):# 随机选择模糊核kernel_sizerandom.choice([7,11,15,21])blur_typerandom.choice([gaussian,motion,defocus])# 这里踩过坑运动模糊的方向要随机否则模型会过拟合到特定方向anglerandom.uniform(0,180)ifblur_typemotionelseNone# 随机噪声noise_typerandom.choice([gaussian,poisson,speckle])noise_levelrandom.uniform(0,0.05)# 别超过0.05否则训练不稳定# 随机压缩qualityrandom.randint(30,95)# JPEG质量returnapply_degradation(lr_frames,kernel_size,blur_type,angle,noise_type,noise_level,quality)实战中的那些坑第一个坑显存爆炸。视频超分要处理连续帧显存消耗是单图的N倍。我试过8帧输入batch size设为2结果24G显存直接爆了。解决方案用梯度累积batch size设1累积4步再更新参数。另外输入帧的分辨率不要太大128x128就够反正超分后要上采样。第二个坑训练时间太长。RealBasicVSR的模型参数量大约12M比BasicVSR的6.7M大了近一倍。在单卡V100上训练100个epoch需要大约3天。建议先用小数据集比如REDS的4个片段做预训练再在完整数据集上微调。第三个坑评估指标不靠谱。PSNR和SSIM在真实场景下经常失效——有时候PSNR高但视觉质量差有时候PSNR低但人眼看更清晰。我后来改用LPIPS和NIQE做辅助评估更贴近人眼感知。个人经验性建议如果你现在要做一个真实场景的视频超分项目我的建议是先别急着上RealBasicVSR。先用BasicVSR跑个baseline看看你的数据到底有多“真实”。如果BasicVSR的效果已经很好了比如PSNR30那说明你的退化并不复杂没必要上更复杂的模型。如果BasicVSR效果差比如PSNR25那就要考虑退化建模了。这时候RealBasicVSR是首选但要注意它的退化编码器需要足够多的帧来估计退化如果你的视频只有几帧效果会大打折扣。另外别迷信论文里的超参数。每个数据集都有自己的特性我建议你花一周时间做超参数搜索——用Optuna或者手动调都行重点调三个参数退化编码器的输出维度、时序一致性loss的权重、DCN的offset学习率。最后真实场景的视频超分60%的工作量在数据预处理上。花时间清洗数据、做退化增强、设计合理的验证集比调模型结构更有效。我见过太多人把时间花在改网络结构上结果数据一塌糊涂模型再好也没用。记住真实场景的退化是未知的但你的模型必须学会适应它而不是假设它。