图像去噪这个方向,这两年最让人头疼的不是网络结构,而是“噪声本身到底是什么”。以前我们默认噪声是已知强度的高斯白噪声,照着经典论文把网络搭起来,在合成数据上跑得飞起,一放到真实照片上就露馅——去不干净、边缘发糊、伪影频出。直到我看到VDN(Variational Denoising Network,变分去噪网络)这篇论文,才意识到把去噪问题直接升级成“盲去噪”才是正路。它最吸引我的点,是不再把噪声方差当作需要预知的固定参数,而是把它放进网络里一起推断,靠变分推断同时解决“噪声估计”和“图像重建”。整篇论文像是一个优雅的概率模型外壳,里面装的却是一个非常实用的双分支网络。
这篇文章适合谁?如果你正在做图像去噪、真实噪声去除,或者对“盲去噪”“噪声水平估计”“置信度图估计”这几个方向感兴趣,VDN值得你逐行读一遍。整篇论文的核心可以这样理解:我们不直接学习带噪图到干净图的映射,而是学习一个“带噪图到干净图+噪声方差图”的联合概率模型,训练时用变分下界把这两件事绑定在一起。这样一来,训练好的网络遇到任何未知噪声水平的输入,都能自己先估计出每像素的噪声强度,再引导去噪,这就是它叫“变分去噪网络”的原因。
1. 变分去噪网络要解决的三个痛点
1.1 真实图片噪声为什么不能用固定高斯噪声近似
经典的CNN去噪工作,比如DnCNN,走的是残差学习路线:输入带噪图,输出预测的噪声图,用干净图减掉。默认前提是噪声水平已知,或者至少固定。训练时用固定的噪声标准差生成合成样本,测试时输入的真实照片如果噪声水平不在训练范围内,效果立刻打折扣。这就像你只练过游泳姿势,却被扔进湍急的河里——那完全是另一个世界。
真实噪声根本不是均匀的高斯分布。我看过某个传感器在不同ISO下抓拍的照片,放大到像素级别你会发现,暗部区域噪声明显更大,亮部相对干净,边缘过渡区域还会出现顺着梯度方向的条纹噪声。也就是说,噪声方差实际上是空间变化的,每块区域都有自己的强度。VDN正是抓住了这一点:它把噪声方差建模成一张和原图分辨率相同的“方差图”,每个像素都有自己的噪声强度,而不是全图共享一个标量。这个设定直接推动了后面的盲去噪能力。
1.2 已有盲去噪方案的重重限制
在VDN之前,怎么处理盲去噪?主流做法是“估计-去噪”两阶段,代表是CBDNet。这类方法先训练一个噪声水平估计子网络,输出噪声方差的标量或者方差图,然后把这个估计结果作为附加输入,送进后面的去噪网络。听起来很合理,但仔细看有个硬伤:噪声估计子网络和去噪子网络通常分开训练,或者只有单向的传递关系。如果估计错了,去噪网络只能用错参数硬着头皮生成结果,没有“两者互相修正”的机制。
另一个问题是监督信号的不平衡。去噪网络输出干净图,可以用和真实干净图的L1/L2损失来监督,训练信号非常直接。但噪声估计网络没有同样直接的监督——你很难准备一张“带噪图对应的真实噪声方差图”。常见的做法是训练时用已知合成噪声的标准差做弱监督,但真实图像哪来的这种标注。于是估计网络在真实数据上就成了“盲猜”,它的误差会被下游放大。这就是“估计误差传导”的问题。
1.3 VDN的破局思路:把估计和重建放进同一个概率框架
VDN换了个思路。它把带噪图y、干净图x、噪声方差图ν三者写进一个联合概率模型,利用变分推断来同时处理。简单说,它不再显式地分别训练“估计模块”和“去噪模块”,而是设计一个双分支网络,一条分支负责输出估计的噪声方差,另一条分支负责输出去噪结果,但两者的目标是同一个变分下界。这相当于把“估计噪声”和“去除噪声”两个任务打包进同一个损失函数,互相反馈,而不是串联流水线。
尤其是网络在训练时不需要真实噪声方差图,它只依赖带噪图y、干净图x,以及推导出来的变分下界。这非常关键:因为真实噪声方差图几乎是无法标注的,VDN绕开了这个坑。它相当于告诉网络:“你猜出来的方差分布,要既能解释带噪图的产生过程,又要能指导干净图的重建。”这样,估计分支和重建分支被绑在一起,谁也不能跑偏。
2. VDN的核心设计:把噪声方差当成隐变量
2.1 从观察模型到变分下界
VDN的起点是一个很朴素的观察模型:带噪图y等于干净图x加上噪声n,噪声服从零均值的高斯分布,但方差σ²(x)是逐像素变化的,记作:
y = x + n, n ~ N(0, diag(ν))
这里的ν就是一张噪声方差图。注意这个写法已经做了重要的知识压缩:噪声的方差是x的函数也好,是局部特征也好,反正我们用ν代表它。变分推断的核心思想是:不需要精确计算后验分布p(x, ν|y),而是用一个近似分布q去逼近它。VDN把q设计成带噪图y作为输入的函数,由一个神经网络直接输出。
展开推导后,目标函数变成两项之和。第一项是重建误差,即q预测的干净图和真实干净图之间的L2距离;第二项是KL散度,用来约束预测的噪声方差分布不要偏离先验太多。有人觉得这个KL项只是正则化,但实际复现时你会发现,它对稳定训练非常关键。没有KL项,噪声估计分支就会出现极端值,因为重建损失倾向于让“方差”和“噪声残差”共同承担解释,容易出现局部最优解。
2.2 双分支网络:一条去噪,一条猜方差
VDN的网络结构看着像U-Net,但输出端是两条分支。主干是标准的编码器-解码器结构,提取多尺度特征后,在解码器最后阶段分出两个头:
- 去噪分支:输出干净图估计,通道数等于输入通道数,灰度图就是1通道,RGB就是3通道。
- 方差分支:输出噪声方差图,灰度图对应1通道,RGB对应3通道,数值上必须严格大于零。
这两个分支不是完全独立的,它们在解码器内部共享了大量特征。而且有个细节容易被忽略:去噪分支最终要利用方差分支的输出。VDN不是简单地输出两个结果就完了,它将方差分支的结果通过某种调制方式注入到去噪分支中,让去噪分支能用上“当前像素噪声多大”的信息。这种共享和调制的设计,比两阶段流水线合理得多。
2.3 用重参数化把期望损失变成可计算形式
如果只是Maximize后验概率,训练会卡在期望项没有解析解。变分自编码器领域的标准答案——重参数化技巧,在这里同样适用。VDN把噪声图的采样过程写成:
ε ~ N(0, I), n = ν^0.5 ⊙ ε
也就是把随机噪声拆分成标准正态随机变量ε乘以预测方差图的逐元素平方根。这样一来,对噪声的期望就可以用采样近似,而梯度可以通过ε回传到方差分支。这个技巧在你复现时几乎必须使用,否则方差分支的梯度会断掉。
有读者可能会问,为什么非得引入随机性?因为VDN本身是一个生成模型。干净图像x的估计虽然是确定性的,但以ν为条件的后验本质上是一个分布。随机采样让网络在训练时不再只学习“唯一正确”的干净图,而是学习“在给定噪声估计下最可能的干净图”,变分框架让这两条分支形成一个自洽的整体。
3. 训练损失与网络结构的落地细节
3.1 从ELBO到可实现的Loss
把变分下界展开,我们得到训练时实际使用的损失函数,大致可以分成三块:
- 重建块:去噪分支输出的干净图与真实干净图之间的L2距离,也就是MSE损失。这一块提供最核心的监督信号,让去噪分支学会把带噪图变成干净图。
- KL散度块:约束方差分支输出的ν接近先验分布。先验一般取标准高斯或某个固定方差的高斯,KL项会让ν的分布不要太极端,起到正则作用。
- 方差的自监督块:在推导过程中会出现一个“噪声对数似然”项,它实质上告诉方差分支:你估计的ν要能解释带噪图y和预测干净图x之间的残差。这是整个模型真正做到“盲估计”的关键。
如果你去看官方实现,会发现代码首页的三个损失项并不难写。但难的是权重分配。重建损失主导梯度方向,KL和似然项权重如果太大,方差分支会变成“什么都学不到”;太小,方差图又会出现退化。我的经验是先用小学习率预热主干,方差分支用单独的权重更新,效果会好很多。
3.2 训练数据怎么造:从合成到真实噪声
VDN训练使用的数据分两种。一是完全合成数据:从干净的Dataset里取图,加上零均值高斯噪声,但关键在于——合成的时候每个像素的方差要不同。论文里使用了空间变化的高斯噪声,让网络真正适应“每像素一个噪声强度”。二是用真实噪声数据做微调:比如用两部手机在暗光下拍摄同一场景得到的短曝光/长曝光带噪图对,虽然这个数据对不完全准确,但从真实噪声特性里学到的方差图明显更贴近实际。
这里要特别提醒,如果你只用固定方差的高斯噪声训练VDN,方差分支几乎没有用武之地,模型会退化成一个普通去噪网络。想要体现VDN的优势,数据里一定要有空间变化的噪声。论文里构造了一个噪声方差与图像内容相关的生成方式:暗部区域方差大,亮部区域方差小,这样学出来的方差分支才真正学会“内容感知的噪声估计”。
3.3 损失函数表达细节与“让方差去解释噪声”的直觉
我读论文时最拍案叫绝的是它如何处理“残差归因”。一个像素点上的(带噪图-干净图)误差,既可能来自噪声,也可能来自模型没有拟合干净图。普通训练会把两者混在一起,导致去噪结果过平滑。VDN通过变分推导,把噪声部分的解释权交给了ν,让重建分支专心解释“真实场景”。两个分支各干各的,又互相制约,从而尽量避免“为了降低MSE而拼命抹平边缘”的问题。
这种分工在实际运行中表现为:边缘区域虽然方差可能估计得大一些,但去噪分支会利用特征做保边处理,而不是狠命地平滑;平坦区域方差估计得小一些,去噪分支可以放心增大平滑力度。最终的主观效果是,真实照片能保住纹理细节,同时没有残留噪声斑点。
4. 实验结果的解读视角和论文里的关键结论
4.1 合成数据上的量化优势
论文在常见的合成高斯噪声数据集上跑了一组对比实验,和BM3D、DnCNN、CBDNet等作比较。关于PSNR/SSIM,VDN不是全面碾压,但有几个稳定的结论值得记住。第一个是,当测试噪声强度与训练时最常用的范围接近且是空间一致噪声时,DnCNN级别的专用去噪器仍然很强,VDN的量化指标不会差太多。真正拉开差距的是在测试时使用空间变化噪声,这时VDN的优势明显:因为它能估计出逐像素方差,相当于每一步去噪都用了“局部最优强度”。
另一个结论是,如果把测试噪声方差调得很大,比如训练时没见过的强度,传统方法通常会崩,VDN因为方差分支能自适应调整,PSNR下降的曲线要平滑得多。这说明它的盲估计能力不是泛泛而谈,而是实实在在提升了模型的跨噪声强度泛化性能。我自己复现时也发现,用同一个VDN权重去处理σ=15和σ=75的噪声,不需要重新训练也能保持可接受的效果。
4.2 真实照片上的方差图可视化
论文最让我信服的,是它在真实噪声照片上输出了噪声方差图的可视化。不看这个图,你很难相信“空间变化噪声”假设有多正确。那些方差图并不是一张均匀的灰色图,而是在暗部区域明显更亮、边缘和纹理附近有细节起伏,这和图像传感器增益噪声的物理特性完全对得上。这说明VDN没有走捷径,它确实学到了噪声的空间分布。
这个可视化还有一个额外用途:噪声方差图可以作为“置信度图”供下游任务使用。比如做RAW域去噪、HDR合成或深度估计时,你本来就需要知道哪些像素点更可靠。VDN免费提供了一个每像素的噪声强度估计,这对很多系统来说反而是比PSNR提升更值钱的副产品。
4.3 消融实验透露出来的设计智慧
论文的消融实验部分,建议大家重点看三个设计点的取舍。
- 去掉KL散度项:方差估计会变得极不稳定,训练后期出现噪声方差图剧烈震荡,整体PSNR下降约0.3~0.5dB。这说明了正则约束的重要性。
- 去掉方差分支对去噪分支的调制:相当于把双分支变成两个独立任务,去噪分支不再感知噪声强度,真实噪声场景下的表现回落明显,证明“方差信息引导去噪”这个设计不是装饰。
- 用固定噪声方差替代空间变化噪声训练:模型虽然也能收敛,但输出方差图基本退化为常数,失去盲去噪能力。这直接验证了训练数据设计对模型能力的决定作用。
5. 复现VDN时的实践要点与避坑清单
5.1 搭建网络时最容易踩的通道坑
VDN的双分支设计听起来直观,但自己在代码里写时很容易搞错通道数。灰度图输入是1通道,主干编码器的第一层输出是32或64通道,解码器最终恢复到输入尺寸时,需要分出两个头:去噪头的输出通道是1,方差头的输出通道也是1。如果你把方差头的输出通道误写成3,然后在RGB图上直接当作1通道处理,模型会照常训练但方差图的语义就错了。
RGB输入时更重要,方差图应该是三通道逐像素方差,还是单通道共享方差?按论文设定,三通道各自估计更好,理论上R/G/B通道的噪声强度不完全相同。但实践下来,因为三个通道高度相关,用单通道方差换算成三通道也可以,训练还更快。如果你的目标只是复现论文指标,第一版可以先用RGB三通道方差,以保险为主。
5.2 方差分支的活化函数和数值保护
方差必须严格大于0,这是从概率模型里带出来的硬约束。输出层常选择Softplus或Sigmoid乘上一个大尺度。我自己复现时偏好Softplus,因为它导数平滑,没有Sigmoid那类饱和区,训练初期方差不会卡死在0附近。还需要在KL散度计算时对方差取log时加epsilon,一般取1e-6到1e-8,否则log(0)会让loss直接变成NaN。
这里说一个我踩过的坑:方差图是模型自己估计的,不代表你可以在反向传播时随意停掉它的梯度。刚开始我觉得方差头训练不稳定,就在KL散度那一支用了detach(),结果方差分支完全不更新了,去噪分支虽然照样收敛,但真实噪声测试时敏感度极差。原因是重参数化路径本来就靠噪声图采样传播梯度,你再detach就把这条路切断了。
5.3 训练策略的细节:学习率、batch size与评估时机
VDN的收敛速度并不算快,我测试时用2块显卡训练512×512的patch,batch size设成4,大约需要跑140个epoch才能看到稳定的效果。建议不要一开始就追求大batch size,因为方差分支的稳定性对batch size非常敏感,batch太小,方差估计的统计噪声会干扰KL散度,导致loss曲线毛刺很多。梯度累积是个折中方案,但要注意BN层的统计量更新会受影响,尽量使用GroupNorm或InstanceNorm。
学习率方面,我给两个分支设了不同的初始学习率。主干和各分支共享主干权重,所以主干的学习率用1e-4,方差分支额外乘一个0.5的系数,因为方差分支的梯度方差比较大,更大的学习率容易让方差图出现椒盐噪声般的离群值。等主干loss稳定后,再把方差分支的学习率调回相同水平,这样训练过程更稳。
5.4 推理时的“方差图”要不要后处理
训练好的VDN推理时,同时输出干净图和方差图。这里有个很多人没注意的点:实际应用时,可以直接把方差图的低通滤波版本作为后处理置信度。因为白噪声方差图通常局部波动较大,直接用于下游任务不稳定。我通常对方差图做一次均值模糊或引导滤波,再将其用于加权融合或其他任务。但请注意,这不应该作用于去噪分支的输出,那是网络自己特征调制的,你修改它反而会破坏去噪结果。
5.5 官方代码之外:从Torch到你自己的框架迁移
很多复现者习惯把官方代码搬到自己项目里。迁移时最容易遗漏的是“训练噪声方差图”的采样过程。官方网站里的噪声图生成方法通常先随机生成一个低频的方差场,再乘以均匀噪声,做完缩放。迁移时一定要复制这个过程,因为它决定了训练数据中空间变化噪声的分布。
另外,在训练过程中,要对噪声水平范围做一定控制。论文里的方差图取值范围大概在[0, 50]这个量级(以像素值0~255计算)。如果你设置的范围过大,网络会把大量精力放在估计极端噪声上;过小,则体现不出盲估计能力。我的建议是先用窄范围训练主力模型,再在真实数据上微调时扩大范围。
6. 我眼里的VDN:适用场景和后续演进
6.1 什么时候首选VDN,什么时候不选
如果你的任务是在已知噪声水平的合成数据集上追求最高PSNR,VDN不是最优选择,专一训练的DnCNN或Restormer会更强。但如果你面对的是真实拍摄的无序噪声,不知道噪声强弱,甚至希望在同一个网络里处理多种设备、多档ISO,VDN的优势就体现出来了:它不需要预先知道噪声强度,还可以顺带输出方差图供系统使用。
我实际在一个夜间视频降噪项目里用过VDN的思想。视频帧的噪声随时变,固定方差模型完全扛不住,我借用了VDN的“逐帧方差估计”思路,把方差分支的估计结果作为时序滤波的权重,效果比直接套固定阈值好非常多。这说明VDN的模型价值超出了图像去噪本身,它可以嵌入任何需要置信度信息的视觉系统。
6.2 VDN和Noise2Noise、AP-BSN等无监督流派的关系
无监督去噪方法里,Noise2Noise比较出名,核心是把配对关系从“干净-带噪”变成“带噪-带噪”。VDN表面上看是有监督方法,需要干净图,但它的变分框架和无监督方法关系很近。如果你只想解决真实噪声问题,却实在搞不到干净参考图,可以考虑VDN思路的无监督变体——把重建目标从“重建干净图”改成“重建另一张同噪声分布的带噪图”,方差分支同样可以工作。
近两年有些工作在此基础上把VDN的方差先验换成可学习的空间先验,或者引入扩散模型的迭代去噪思路,本质上都是把“估计噪声场”和“重建图像”解耦再耦合。VDN算是在这个交叉点上比较早且完整的尝试。
6.3 改进方向:非高斯噪声、结构化噪声与多任务扩展
VDN最大的局限在于高斯噪声假设。真实传感器噪声中的坏点、条纹、量化噪声都不是高斯噪声。我自己尝试把VDN中的高斯似然换成拉普拉斯分布,效果有改善但KL散度推导变得复杂。另一个改进方向是把方差图推广为协方差矩阵,从而处理通道间噪声相关性,这会带来计算量暴涨,但对RAW域去噪很有意义。
还有一个很实用的扩展:把VDN的方差分支单独拿出来,作为图像质量评估的预测器。它输出的空间噪声方差可以聚合出一张图片的“噪度分”,这个东西在无参考图像质量评估里很好用,我的一个实验就是基于它做的失真类型判别,准确率还不错。所以就算你不需要去噪,VDN的变分结构也值得研究。
6.4 复现VDN的一个心态建议
我第一次跑VDN时,没有细读变分推导,直接用三个loss叠加,结果效果和普通U-Net差不多。后来把推导逐行顺了一遍,才明白KL、重建、噪声似然三者谁是主角、谁是配角。建议准备复现的人先花两个小时把ELBO从公式推导到代码接口,再动手写网络。过程虽然枯燥,但会让你知道哪一步该加梯度停止、哪一步该指数运算。这个准备时间绝对值得。
我还有一个很实用的测试方法:训练到一半时,喂一张完全干净的图进网络,此时理想的方差图输出应该接近于零,去噪输出应该几乎等于输入。如果方差图输出有明显纹理或数值偏大,说明你的方差分支没有正确学到“什么是有噪声”,这时需要检查训练数据的噪声采样范围,多半是因为合成噪声最小值太大了。
最后再分享一个实操细节
在把VDN应用到真实场景时,我最喜欢用的一个技巧是:先让模型输出方差图,再利用方差图生成一张“带噪程度引导掩膜”,把去噪分支的结果和原始输入按像素加权融合。在方差图估计值很高的区域,完全相信去噪结果;在方差图估计值很低的区域,保留原始图像细节。这样能在主观画质上比直接全图使用去噪结果更自然,尤其是面对近乎无噪声的输入时,避免无谓的模糊。
这个技巧并不是论文内容,完全来自我多次真实测试的经验,但它能解决VDN实际落地中最常见的一个抱怨:明明已经去噪了,为什么细节反而丢了?答案往往不是去噪能力不足,而是你忽略了“这块区域本来噪声就不大”这个事实。VDN的方差图恰好给了你一把尺子,量完再决定动刀轻重,效果自然就上来了。
如果你也是被真实噪声折磨过的人,一定要把VDN的“变分估计”思路吃透。哪怕最终不用它的网络结构,这种“把未知量建模成隐变量,再用推断求解”的思维方式,也会在你会遇到的很多视觉问题里帮上大忙。