ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

真实图像去噪实战:密集连接分层网络DCHN结构拆解

真实图像去噪实战:密集连接分层网络DCHN结构拆解

这篇论文是我在调研真实图像去噪方向时反复读到的一篇 CVPRW2019 工作:Densely Connected Hierarchical Network for Image Denoising。当时它的定位很明确——不针对合成高斯噪声,而是直接面向真实拍摄环境下产生的噪声。这个方向在当年其实挺吃亏的,因为大多数公开数据集和评价体系都建立在“干净图 + 已知方差噪声”的假设上,真实噪声因为空间变化、与信号相关、传感器链路复杂等问题,一直不太受主流研究待见。但这篇论文选择正面硬刚真实噪声,并且在 DND、SIDD 这类真实噪声数据集上拿到了当时很有竞争力的结果,所以即使发表在 Workshop,也值得认真读完、读透。

这篇文章适合谁看?如果你是做底层图像恢复的,尤其是对真实场景降噪感兴趣的研究生或工程师,这篇论文的阅读价值很高。它没有特别炫技的模块,核心就是在“怎么把多尺度特征用得更充分”这件事上做文章,属于那种结构不难、但设计思路很扎实的工作。我会从背景、网络结构、关键模块、实验观察和读后反思几个方面拆一遍,顺便聊一些我自己的理解。

1. 问题背景与论文动机

1.1 为什么“真实噪声”和“合成噪声”差这么远

讨论这篇论文之前,得先把真实噪声和合成噪声的差异说清楚。很多去噪论文的训练方式是:拿一张干净图,人为加上高斯噪声,然后让网络学习“噪声图 → 干净图”的映射。这种方案的优点是数据集构造极其便宜,一张干净图可以生成几万张带噪图,而且噪声水平(sigma)可以精确控制。但问题也很实际——真实相机拍摄的噪声根本不是简单的高斯白噪声。

真实噪声至少包含三个层次的复杂性。第一是噪声水平随亮度变化,暗部噪声更明显,亮部噪声相对较弱,这是由传感器光电转换过程决定的,通常更接近泊松-高斯混合模型。第二是传感器和 ISP(图像信号处理)链路的非线性,比如去马赛克、白平衡、色调映射、伽马校正,这些操作都会把原本的相对简单噪声扭曲成与图像内容相关的复杂噪声。第三是空间上的不均匀性,比如边缘、纹理区域和光滑区域对噪声的感知完全不同,网络如果只在均匀高斯噪声上训练,它在真实照片上经常会输出过度平滑或者残留伪影的结果。

所以说,真实噪声去噪不是一个“把 sigma 从 30 降到 20”的简单任务,而是要面对噪声分布未知、水平空间可变、并且和图像内容耦合的复杂问题。这篇论文瞄准的就是这个场景,这也是它值得读的出发点。

1.2 这篇论文想解决的具体问题

论文的题目里有两个关键词值得留意:Densely Connected(密集连接)和 Hierarchical Network(分层网络)。为什么要强调这两点?因为在真实噪声场景下,单一尺度的感受野处理不了所有问题。大尺寸卷积核能获取更大上下文,但参数量高、容易丢失细节;小尺寸卷积核能保留精细纹理,但感受野有限,无法有效估计大范围的噪声起伏。更麻烦的是,网络层数加深之后,梯度传递和特征复用都会出问题,训练一个几十层的去噪网络并不像想象中那么轻松。

作者团队提出的方案用一个密集连接的分层结构同时缓解这两个问题。他们把不同感受野的特征提取分支组合起来,让网络每一层都能直接看到前面所有层的特征输出。这样既扩大了有效感受野,又减少了信息在层与层之间传递时的损耗。用一句话概括这篇文章的核心:通过密集连接的分层结构,把多尺度特征揉在一起,让网络在真实噪声场景下同时兼顾噪声抑制能力和细节保持能力。

1.3 和同期其他方法的对比定位

把这篇论文放到当时的坐标里看,会更有感觉。2019 年前后,去噪领域的主流思路可以分为三派。

一派是继续在合成噪声上去优化,代表是 DnCNN 这类残差学习网络,训练简单、结构清晰,但在真实噪声上表现一般。另一派是提出新的训练策略或网络结构,比如 CBDNet,它用噪声水平子网络来估计噪声强度,再把估计结果作为条件让主网络去噪,这种“先估噪、再去噪”的思路在当时已经算很贴近真实场景了。还有一派是用生成对抗网络或者结合感知损失来做视觉上更自然的重建,但对 PSNR 这类数值指标并不友好。

这篇 DCHN 不属于上面任何一派,它不太依赖外部噪声估计,也不搞对抗训练,就是实打实地在特征表达上下功夫。它更像一个“特征工程派”的代表:把注意力从损失函数和训练策略转移到网络内部结构本身。这种路线其实挺难得的,因为在深度学习方法成熟之后,大家更多地倾向通过数据增强、噪声建模、后处理等方式提升真实去噪性能,反而在基础网络结构上的创新变少了。

2. DCHN 网络结构深度拆解

2.1 整体设计思路:分层到底分在哪儿

这篇论文的网络结构,如果只看顶层逻辑,可以概括为“一个多分支的特征提取器 + 一个逐步融合的分层处理单元 + 一个图像重建模块”。

图像输入之后,先经过一个初始卷积层做浅层特征提取,这一步主要目的是把图像从像素空间映射到特征空间,相当于给网络一个共同的起点。之后进入核心部分——分层网络。这里的分层不是简单地把网络分成几个阶段然后串起来,而是构造了多个不同尺度的特征流,每个流在不同大小的感受野下工作,然后通过密集连接让这些特征互相补充。

我的理解是,这种设计受到了图像金字塔思想的启发。图像金字塔是将同一张图按不同分辨率多次采样,以获得不同层次的语义信息。DCHN 做的是在特征域里做了类似的事情:不同分支用不同尺寸的卷积核,等价于在不同感受野下观察同一个图像内容,从而得到从高分辨率细节到低分辨率结构的多层次表达。

作者特意选择不同大小的卷积核来制造层级差异,而不是像很多网络那样通过下采样产生多分辨率。这样做的优势在于不用额外维护多分辨率图像之间的对齐问题,所有分支都在同一分辨率下运行,通过 padding 保持特征图尺寸一致,从而可以直接把特征相加或拼接。缺点是虽然感受野变大了,但网络对空间分辨率的处理方式相对单一,这一点在后面具体消融实验里也能看出来。

2.2 密集连接的核心作用

DenseNet 在分类任务上的成功是这篇文章的重要灵感来源。传统的卷积网络每一层只和上一层的输出相连,信息流是串行的,层一多,早期特征在后期几乎被“稀释”掉了。密集连接的做法是:每一层都把前面所有层的输出作为自己的输入,相当于为每层建立了一条直达的高速通道。

放到去噪任务里,密集连接的价值就更加直接了。去噪需要网络同时保留两种信息:一个是局部的细节纹理,比如毛发、织物纹路、树叶边缘;另一个是更大范围的平滑区域,比如天空、墙面、皮肤。深层特征对语义内容更敏感,浅层特征对像素级细节更敏感,如果只是简单堆叠卷积层,浅层特征传到最后时可能已经被大量非线性变换破坏,导致网络输出丢失细节。

而通过密集连接,每一层都能同时拿到浅层的高频细节和深层的语义信息,然后在不同阶段动态决定哪些特征更重要。作者在论文中提到,这样设计还能缓解梯度消失问题,特别适用于训练比较深的网络。实际体验中,训练密集连接结构确实比普通残差网络更容易收敛,对学习率的敏感性也会低一些,这在实现里是一个相当大的隐性优势。

2.3 分层特征如何在网络中流动

既然叫 Hierarchical Network,那么“层次”这个概念要怎么体现在数据流上?从论文的模块设计来看,每个层级并不是完全独立的子网络,而是带有“递进传递”的关系。

一个很直观的理解方式是:假设网络有 L 个层级,第一层级接收初始卷积输出的基础特征,然后将自身输出传递给第二层级。第二层级同时接收初始特征、第一层级特征以及自身经过卷积处理后的特征,同样将融合结果传递给第三层级。以此类推,第 k 层级会看到前 k-1 层所有特征的聚合结果。每一层内部还使用了不同大小的卷积核来提取多尺度信息,从而形成了“层级之间密集连接 + 层级内部多尺度卷积”的双重结构。

这种结构有一个非常明显的好处:网络可以从不同层级的特征里自适应地选择需要的信息。比如说处理一片纹理丰富的区域时,网络可能会更多依赖靠前的层级特征,因为那里保留了最完整的像素级细节;而处理大块均匀区域时,网络可能更依赖靠后的层级特征,因为那里的感受野足够大,能更准确地估计全局的噪声分布。这种动态选择能力在真实噪声场景下特别有用,因为真实照片往往同时包含高纹理区域和低纹理区域,固定的网络结构很难同时兼顾两者,但密集连接的分层结构理论上具备这种自适应的潜力。

3. 从工程角度理解关键模块实现

3.1 多尺度卷积分支的搭建思路

虽然论文原文不会手把手教你写代码,但复现起来其实不复杂。核心的多尺度分支,按我的理解,大致逻辑如下:给定输入特征 X,先用 3x3、5x5、7x7 三个不同大小的卷积核分别做卷积,得到三个特征输出。

import torch import torch.nn as nn class MultiScaleBranch(nn.Module): def __init__(self, in_channels, out_channels): super().__init__() self.branch3 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1) self.branch5 = nn.Conv2d(in_channels, out_channels, kernel_size=5, padding=2) self.branch7 = nn.Conv2d(in_channels, out_channels, kernel_size=7, padding=3) self.relu = nn.ReLU(inplace=True) def forward(self, x): x3 = self.relu(self.branch3(x)) x5 = self.relu(self.branch5(x)) x7 = self.relu(self.branch7(x)) return torch.cat([x3, x5, x7], dim=1)

三个分支的输出通道可以保持一致,最后在通道维度上拼接,然后送入后续层。这种做法的好处是简单直接,而且把多尺度信息保存在不同的通道组里,后续的密集连接可以通过通道注意力机制或卷积层自动决定该侧重哪一组特征。当然,代价也很明显:5x5 和 7x7 卷积的参数量明显比 3x3 大,这在后面提到的训练显存问题上会暴露出来。

3.2 分层密集连接的抽象实现

如果把整个 DCHN 简化为一个类,实际上每一层都维护两个输入:当前层的输入特征和一个全局累积特征列表。每次处理时,把当前输入和之前所有层的输出拼接作为卷积的输入,再将本层输出追加到累积特征列表。

为了表述方便,下面用伪代码描述这个过程:

class HierarchicalDenseBlock(nn.Module): def __init__(self, num_layers, growth_rate): super().__init__() self.layers = nn.ModuleList() for i in range(num_layers): in_channels = 初始通道 + growth_rate * i self.layers.append(ConvBlock(in_channels, growth_rate)) def forward(self, x): features = [x] for layer in self.layers: cat_feat = torch.cat(features, dim=1) out = layer(cat_feat) features.append(out) return torch.cat(features[1:], dim=1)

这个结构在实现层面并不复杂,但有一个工程细节必须注意:随着层数增加,特征拼接的通道数会线性增长,显存占用也跟着水涨船高。DenseNet 在分类任务里可以借助内存优化技术来减少显存消耗,但在图像去噪这种高分辨率输入的任务里,密集连接的存储开销对身体考验很大。作者在实验时应该也考虑到了这一点,所以在网络整体层数上做了克制,保证了在常见训练配置下能够跑通。

3.3 残差学习与训练细节

去噪网络有个老规矩:不直接回归干净图像,而是回归噪声图。回到论文的设置也是同理,网络输出的是带噪图像和干净图像之间的残差,也就是噪声估计。训练时,输入噪声图 X,目标值为真实噪声 N = X - Y,其中 Y 是干净参考图。损失函数用的是最简单的 L2 损失:

loss = torch.mean((pred_noise - target_noise) ** 2)

这种做法的好处在去噪任务里几乎是被反复验证过的。预测噪声图比预测干净图像更容易,因为噪声图本身是相对高频且幅度较小的内容,网络不需要花精力去拟合图像的主要结构信息,只需要集中注意力在噪声模式上。另一个好处是输出结果可以直接用输入减去噪声估计得到,这就保证了输出图像和输入图像在整体结构上天然一致,不容易出现整体亮度偏移或者色调失真。

在训练配置方面,论文的实验主要使用随机裁剪的小块进行训练,配合水平翻转和旋转来做数据增强。优化器选择的细节论文里没有特别强调,但从当时主流训练的惯例来看,Adam 是默认选择,初始学习率设置在 1e-3 附近,经过固定轮数后下降一个数量级。这类细节其实对复现很重要,因为真实噪声数据集样本量并不大,如果增强策略不够充分,很容易出现过拟合。

4. 实验评估与结果观察

4.1 真实噪声数据集的使用

论文实验最值得关注的部分,就是把 DND 和 SIDD 作为主要评价基准。这两个数据集都有相似的特点:图像来自真实拍摄场景,而且每张图都有对应的干净参考图,只不过获取方式不同。

DND 的数据是通过在不同 ISO 下拍摄同一静态场景,然后利用多帧平均等技术获得近似干净的真值。SIDD 则主要使用智能手机拍摄大量场景,通过连拍和后期处理来生成参考图像。无论哪个数据集,它们的共性都是噪声模式非常贴近真实使用场景,从暗光环境的高 ISO 噪声到正常光照下的轻微噪声都有覆盖。论文在这样的数据集上验证,本身就比只在合成高斯噪声上加噪验槽要更有说服力。

4.2 定量结果的阅读方法

对这篇论文的实验结果,我的建议是不要只看最后的 PSNR 排名,更要关注对比对象和相对增益。当时真实噪声去噪方向的主要竞争对手还不算多,除了 BM3D 这种经典传统方法,就是 CBDNet、FFDNet 这类 CNN 方法。论文的实验显示,所提方法在 DND 和 SIDD 上的 PSNR 都能达到当时比较靠前的位置,特别是考虑到它没有额外使用噪声估计子网络或者其他辅助模块,这个成绩更能说明网络自身结构的有效性。

另外一点值得注意,PSNR 的提升幅度看起来可能只有 0.2 dB 到 0.5 dB,但对于去噪任务来说,这个级别的提升已经代表肉眼可见的差异。PSNR 是一个对数尺度指标,0.3 dB 的差距往往意味着平滑区域的噪点大幅度减少,纹理部分也更干净。不过我也建议读实验部分时留一个心眼:真实噪声数据集的参考图本身可能有残留噪声,所以指标之间的微小差异不一定完全代表视觉质量的优劣,有条件的话还是应该看论文附录里的可视化对比图。

4.3 消融实验传达的设计逻辑

作者在消融实验里做了不少工作,核心目的只有一个:证明密集连接和分层结构缺一不可。

如果去掉密集连接,只保留多个分支的并行结构,网络依然有一定的多尺度信息,但因为缺少跨层信息通道,浅层特征无法有效传递到深层,导致纹理细节保持能力明显下降。如果去掉分层结构,只剩单纯的密集连接网络,虽然训练收敛依旧稳定,但感受野增长过于依赖层数堆叠,在同样的参数量下,处理真实噪声时缺乏对不同尺度噪声的适配能力,效果不如完整版。

这种消融结果其实说明了论文设计中的一个平衡原则:密集连接提供的是信息的丰富度,分层结构提供的是信息的组织方式。如果没有前者,网络内部的信息流断裂,多尺度无从融合;如果没有后者,多尺度的信息没有合理的接收和分发机制,网络只能堆叠更多层数来弥补感受野不足。两个组件互相配合,才构成了一个有竞争力的完整方案。

5. 一些值得借鉴的设计经验

5.1 这篇论文让我最欣赏的三个点

第一个是它对“真实噪声”这个问题的定义非常清醒。很多论文嘴上说解决真实噪声,实际上实验还是在合成数据上做的,只是把噪声模型改得更复杂一点。这篇论文从数据集的选取到评价方式都明确指向真实场景,没有绕弯子。

第二个是结构设计不花哨但很实用。多尺度卷积、密集连接、残差学习,这三个组件单独拿出来都不是新东西,但作者把它们组合在一个分层架构里之后,能够明确地看到每个组件解决的具体问题。这种“老模块新组合”的做法在科研里其实是很扎实的路线,尤其适合资源有限、又需要快速验证效果的团队。

第三个是实验设计讲究叙事逻辑。通过消融实验把每个模块的贡献量化出来,让读者不仅看到“这个网络有效”,还能理解“为什么有效”,这种透明度在论文里并不多见。

5.2 我觉得可以改进的方向

现在我以做图像去噪的实际经验来提几个不足。

高频细节的保持还是依赖相对简单的多尺度卷积。相比后来的一些基于 Transformer 的方法,或者引入频域损失的方法,这篇论文在细纹理上的表现还有提升空间。尤其在 SIDD 这种包含大量文字、细小纹理的数据集上,偶尔能看到轻微的结构性失真。

网络参数量偏大。7x7 卷积分支加上密集连接,在 512x512 的高分辨率输入下训练,显存消耗相当可观。自己在复现时,如果不做梯度累积或者混合精度,单卡 24GB 显存可能都会很紧张。作者可能是在相对高配的硬件上做的实验,但对一般研究者来说,这个结构有点奢侈。

训练策略比较简单。论文没有使用噪声图作为额外输入,也没有考虑无监督或自监督策略,这使得它在噪声极强的极暗光场景下会有些吃力。如果在训练时加入噪声水平图,或者采用更强的数据增强策略,或许能在不做结构改动的前提下进一步提升性能。

5.3 复现中容易踩到的工程坑

第一,初始化方式很重要。多尺度分支里的 7x7 卷积如果有不合理的初始化,训练初期会让整个特征分布方差变得很大,导致后期网络崩溃或收敛极慢。建议对所有卷积层统一采用 He 初始化,并在前几个 iteration 观察特征图的方差变化。

第二,学习率不能太大。密集连接结构对输入特征的变化较为敏感,如果学习率设置太高,很容易出现 loss 震荡。实践中建议从 1e-3 开始,如果发现训练曲线在早期就出现明显波动,最好先降到 5e-4。

第三,数据集预处理要特别小心。DND 数据集提供的是未经过色调映射的线性 sRGB 图像,训练时如果直接用 0-255 范围归一化,会和测试阶段评估时的图像范围不一致,导致结果偏低。建议严格按照数据集官方提供的预处理脚本处理,确保训练和评估处于同一图像分布下。

第四,Batch Size 的选择。密集连接带来的显存开销不是线性的,所以 Batch Size 受限于层数。实践时,如果显存不足,优先考虑减小输入 patch 尺寸而不是减小 batch,因为 patch 尺寸对去噪效果的影响更直接。

6. 读完之后我在想什么

这篇论文对我在真实噪声去噪上的理解影响挺大的。最关键的一点是:它让我意识到,与其在损失函数上反复琢磨,不如把网络结构本身设计得更适合这个任务的先验。真实噪声去噪本质上是一个“既要看得广,又要看得细”的问题,而密集连接的分层结构正好通过特征复用与多尺度融合,为网络提供了同时满足这两个条件的基础能力。

后来我自己再做相关项目时,也会在特征融合部分刻意考虑层级之间的信息流动是否充分,而不是简单地把几个不同尺度的输出拼在一起就完事。去噪任务里没有银弹,但好的结构至少能让你在面对复杂的真实噪声时,有一个更从容的起点。

如果你也准备复现这篇论文,我最后一个建议是:先从消融实验里的简单版本开始跑通,再逐步加模块。直接复现完整版 DCHN,一旦训练出现异常,你很难判断是哪个环节出了问题。先跑一个去掉密集连接的基础版本,确认数据流水线和损失函数没问题,再一层一层把密集连接加上去。这个过程虽然慢,但能帮你把论文里的每一个设计决策都验证一遍,这种理解深度是直接跑通完整代码得不到的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进