真实世界中拍出来的视频,噪声和模糊往往不是教科书里描述的那种理想模型。暗光环境下提高ISO带来的信号放大噪声、传感器读出噪声、压缩失真,再叠加手持抖动产生的运动模糊与对焦不准带来的散焦模糊,这些问题纠缠在一起,让基于合成数据训练的去噪去模糊模型在真实素材上表现大打折扣。本文围绕退化建模与盲复原两条主线,梳理真实视频噪声与模糊的成因、建模方法以及实用的复原策略。

一、真实视频退化的成因分析
要解决问题,先得理解问题从哪来。真实视频中的噪声主要来源于三个方面:光子散粒噪声、读出噪声和量化噪声。光子散粒噪声服从泊松分布,光子到达传感器的数量本身就有随机涨落,环境越暗、增益越高,这个噪声占比越大;读出噪声则是电路在模数转换过程中引入的,近似服从高斯分布;量化噪声来自ADC位深限制。三者叠加后的统计特性既不是纯高斯也不是纯泊松,而是混合分布,并且和像素亮度、ISO设置、相机型号都强相关。
模糊的来源同样复杂。手持拍摄时的抖动产生空间可变的运动模糊,快门时间越长模糊越明显;镜头光圈和焦距设置不当带来散焦模糊;此外,很多视频在采集后还会经过去马赛克、锐化、视频编码等多级处理链,这些操作会改变噪声的频谱分布,产生块效应和振铃,让原本的退化模型更加偏离理想假设。
一个关键的认识是:真实退化是信号相关的、空时非平稳的。同一帧内亮区和暗区的噪声水平不同,不同帧之间的模糊核也可能随场景运动而变化。这就是为什么简单给干净视频加高斯白噪声训练出来的模型,面对手机直出的夜景视频时效果会明显打折。
二、退化建模:从加性噪声假设到真实物理模型
传统去噪算法大多假设观测模型为 y = x + n,其中n是方差恒定的高斯噪声。这个假设在真实场景下站不住脚。更贴近物理的建模采用泊松-高斯混合模型,可以表示为暗电流偏置、泊松散粒噪声与高斯读出噪声的组合。围绕这个模型,常用的处理思路是方差稳定变换(VST),例如Anscombe变换,把混合噪声近似转化为方差恒定的高斯噪声,处理完再反变换回去。
import numpy as np
def anscombe_transform(data):
# 泊松-高斯混合噪声的方差稳定变换
return 2.0 * np.sqrt(np.maximum(data + 0.375, 0.0))
def inverse_anscombe(data):
# 逆变换的解析近似
return (data / 2.0) ** 2 - 0.375
noisy = img.astype(np.float64) / 255.0
stabilized = anscombe_transform(noisy * sigma_scale)
# 在稳定域中执行去噪,再反变换回原始域
restored = inverse_anscombe(denoise(stabilized))
模糊的建模则分为盲与非盲两类。非盲去模糊假设模糊核已知,问题退化为一个病态的卷积反演;盲去模糊则需要同时估计模糊核和清晰图像,经典方法如Fergus等人的混合高斯先验、Krishnan的归一化稀疏先验,都是在估计模糊核的稀疏性和图像的边缘结构之间做平衡。对视频而言,还要引入时域一致性约束,避免相邻帧估计出的模糊核跳变导致复原结果闪烁。
近年来合成退化数据的思路也在升级。与其只加高斯噪声,不如模拟完整的相机ISP流程:先生成RAW域的泊松-高斯噪声,再依次模拟去马赛克、伽马校正、降噪、锐化和JPEG/H264压缩。这样的合成数据虽然不能完全复刻真实相机,但能显著缩小训练域与测试域之间的差距,让模型的泛化能力有肉眼可见的提升。
三、盲复原:不依赖先验知识的视频修复
盲复原的核心诉求是:在不知道噪声水平、不知道模糊核的情况下,直接从退化输入恢复干净结果。深度学习时代的盲复原大致有三条路线。
第一条是噪声水平估计加非盲复原的组合。先用统计方法(如中值绝对差、块状方差估计)从输入帧估计噪声参数,再把估计值作为条件输入到网络中。这类方法可解释性强,估计模块和复原模块解耦,便于针对不同模块单独优化,但误差会逐级传递,噪声估计偏差会直接影响复原质量。
第二条是端到端的盲复原网络。代表思路包括以DnCNN为代表的不确定噪声水平训练(训练时每个patch随机采样不同噪声强度),以及以DeblurNet、DBN等为代表的动态模糊核生成机制——网络根据输入内容自适应生成卷积核参数,实现空间可变的去模糊。对于视频任务,还要在网络中引入光流对齐或3D卷积,利用时域冗余信息,同时用递归结构或帧间一致性损失抑制时序抖动。
import torch
import torch.nn as nn
class BlindVideoDenoiseNet(nn.Module):
def __init__(self, channels=64):
super().__init__()
# 噪声水平估计分支,输出每帧的噪声强度图
self.noise_head = nn.Sequential(
nn.Conv2d(3, channels, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(channels, 1, 3, padding=1),
nn.Sigmoid()
)
# 主干复原分支,拼接原图与噪声图作为输入
self.body = nn.Sequential(
nn.Conv2d(4, channels, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(channels, channels, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(channels, 3, 3, padding=1)
)
def forward(self, x):
noise_map = self.noise_head(x)
out = self.body(torch.cat([x, noise_map], dim=1))
# 残差学习:网络学的是噪声而非干净图
return x - out
第三条是自监督路线,其中最知名的是Noise2Noise和Noise2Void。Noise2Noise利用同一场景的多张含噪观测做监督,无需干净图;Noise2Void通过盲点网络阻止网络看到中心像素,从而把去噪任务转化为预测邻域统计的问题。对于视频来说,时域上有天然的免费噪声样本——相邻帧中静止区域就是同一场景的多次含噪观测,这让Noise2Noise的思想在视频域特别容易落地。
四、工程落地建议与常见坑
实际做真实视频复原项目时,数据准备往往比模型选型更关键。建议优先收集真实配对数据:用固定机位拍摄同一场景,通过调整曝光参数获取含噪帧与长曝光的相对干净参考帧。如果条件有限,也可以用高帧率视频的连续帧做近似的Noise2Noise配对,成本要低得多。
训练策略上有几个细节值得注意。第一,patch采样时保留真实的噪声空间相关性,不要做过度的随机裁剪和翻转破坏噪声结构;第二,损失函数上,除了常用的Charbonnier损失,建议加入时域一致性损失,惩罚相邻输出帧之间的不必要变化,否则复原结果逐帧播放时会有明显的闪烁感;第三,评估不要只看PSNR和SSIM,真实退化下这些指标与主观质量的相关性较弱,建议补充LPIPS等感知指标并做用户主观对比实验。
还有一个常见误区是把去噪和去拆糊当成两个独立任务串行处理。实际中两个退化是耦合的——模糊会削弱噪声的可见度,去噪又会改变模糊核估计的输入分布。如果算力允许,联合建模的方案通常优于串行流水线,尤其是运动模糊明显的手持视频中,联合复原在边缘细节上的优势相当明显。部署阶段还需要注意帧间延迟和显存占用,可优先考虑滑窗式的时域注意力或者轻量递归结构,在移动端则建议量化加剪枝,把模型压到实时可跑的规模。
总结来看,真实视频噪声与模糊的复原,难点不在网络结构,而在于对退化过程的准确理解和建模。从物理成因出发构造贴近现实的退化模型,再结合盲复原策略让网络自适应地处理未知退化参数,是目前经过大量实践验证的可行路线。把合成数据、真实配对数据和自监督三股力量结合起来用,往往比押注单一数据源取得的效果更稳定。