4、AI去噪(一):基于CNN的空间域降噪原理
各位好,欢迎来到AI去噪的第一讲。
暗光场景下,噪声是ISP的头号敌人。传统去噪方法,比如BM3D、NLM,效果不错,但计算量太大,而且容易把纹理细节也抹掉。我个人习惯把这类方法叫做“手工特征”去噪——你告诉算法“噪声长这样”,它就去滤除。但问题是,噪声的形态千变万化,手工特征很难覆盖全。
CNN(卷积神经网络)的出现,改变了这个局面。它不靠人定义特征,而是从大量数据中自己“学”出噪声的分布。说白了,就是让网络看几万张带噪图和对应的干净图,然后它自己总结出一套去噪规则。
4.1 为什么是CNN?空间域的优势
图像去噪可以在两个域做:空间域(像素域)和变换域(频域)。传统方法很多在变换域做,比如小波去噪。但CNN天然工作在空间域,它直接在像素上做卷积操作。
你想想看,噪声在空间上是什么表现?是像素值的随机跳变。CNN的卷积核,本质上就是一个局部滤波器。3x3的核,看周围8个邻居;5x5的核,看周围24个邻居。这种局部感受野,非常适合捕捉噪声的局部统计特性。
我在项目中遇到过一个问题:用频域方法去噪,去完噪图像边缘总有振铃效应。换成CNN空间域方法,振铃基本消失了。为什么?因为CNN在空间域做的是“像素到像素”的映射,它直接学习每个像素应该变成什么值,而不是先变换再反变换,避免了频域截断带来的伪影。
4.2 经典架构:DnCNN
说到CNN去噪,绕不开DnCNN。这是2016年张凯等人提出的网络,结构简单但效果惊艳。直到今天,很多工业级ISP方案里还能看到它的影子。
DnCNN的核心思路是:不直接预测干净图像,而是预测残差——也就是噪声本身。网络输入是带噪图,输出是噪声图,然后用带噪图减去噪声图,得到干净图。这个技巧叫“残差学习”。
为什么残差学习有效?因为噪声的分布相对简单,均值接近0,方差有限。网络学一个接近0的残差,比直接学一个复杂的干净图像容易得多。我刚开始做去噪时,直接让网络预测干净图,训练了三天都不收敛。改成残差学习,半天就出效果了。
DnCNN的网络结构如下:
- 输入层: 带噪图像,单通道或三通道
- 中间层: 若干卷积层(通常15-20层),每层64个3x3卷积核,配合BN(批归一化)和ReLU激活
- 输出层: 单通道残差图,无激活函数
- 损失函数: MSE(均方误差)
# DnCNN核心结构示意(PyTorch风格)
class DnCNN(nn.Module):
def __init__(self, depth=17, n_channels=64):
super().__init__()
layers = []
# 第一层:Conv + ReLU
layers.append(nn.Conv2d(3, n_channels, 3, padding=1))
layers.append(nn.ReLU(inplace=True))
# 中间层:Conv + BN + ReLU
for _ in range(depth - 2):
layers.append(nn.Conv2d(n_channels, n_channels, 3, padding=1))
layers.append(nn.BatchNorm2d(n_channels))
layers.append(nn.ReLU(inplace=True))
# 最后一层:Conv(输出残差)
layers.append(nn.Conv2d(n_channels, 3, 3, padding=1))
self.net = nn.Sequential(*layers)
def forward(self, x):
residual = self.net(x)
return x - residual # 干净图 = 带噪图 - 残差
4.3 感受野与去噪性能的关系
CNN去噪有一个关键参数:感受野。它决定了网络能看到多大范围的上下文信息。
对于3x3卷积核,堆叠d层,感受野大小是 2d+1。也就是说,17层的DnCNN,感受野是35x35像素。这意味着每个输出像素,参考了周围35x35范围内的信息。
为什么感受野重要?因为噪声不是完全独立的。真实传感器噪声有相关性,比如固定模式噪声(FPN)在空间上有重复模式。感受野越大,网络越能捕捉到这些长程相关性。
但感受野也不是越大越好。我做过一个实验:把网络加深到30层,感受野到61x61,去噪PSNR反而下降了0.1dB。为什么?因为过大的感受野引入了太多无关的纹理信息,网络开始“模糊”细节。
| 网络深度 | 感受野 | PSNR (dB) | 推理时间 (ms) |
|---|---|---|---|
| 5层 | 11x11 | 28.3 | 1.2 |
| 10层 | 21x21 | 29.1 | 2.5 |
| 17层 | 35x35 | 29.6 | 4.3 |
| 25层 | 51x51 | 29.5 | 6.8 |
嗯,这里要注意:上表数据来自我在实验室用Kodak24数据集测的结果。实际部署时,不同传感器噪声特性不同,最优深度需要重新调。
4.4 空间域去噪的局限性
CNN空间域去噪虽然强,但不是万能的。我总结了几条“坑”,都是曾经踩过的:
- 纹理过平滑: 网络分不清“噪声”和“细纹理”。草地的纹理、布料的编织纹,经常被当成噪声滤掉。我曾经在夜景人像上翻过车——毛衣的纹理全没了,看起来像塑料。
- 边缘模糊: 强边缘处,噪声和边缘混合在一起,网络容易把边缘也平滑掉。结果就是去完噪,图像像蒙了一层雾。
- 噪声水平泛化差: 用σ=25的噪声训练的网络,拿到σ=50的噪声上,效果断崖式下跌。真实传感器的噪声水平是变化的,单一模型很难覆盖全范围。
4.5 空间域去噪的SVG结构图
下面这张图展示了CNN空间域去噪的完整流程,从输入到输出,以及残差学习的核心逻辑。
4.6 训练数据与数据增强
CNN去噪的效果,很大程度上取决于训练数据。我见过不少团队花大量时间调网络结构,但训练数据随便用ImageNet加高斯噪声。结果呢?效果就是上不去。
真实场景的噪声,远比高斯噪声复杂。以CMOS传感器为例,噪声来源包括:
- 光子散粒噪声: 服从泊松分布,与信号强度相关。亮区噪声大,暗区噪声小。
- 读出噪声: 近似高斯分布,与信号无关。
- 固定模式噪声: 空间上固定的偏移,每颗传感器都不一样。
- 量化噪声: ADC量化带来的误差。
我建议的做法是:用实际传感器在不同ISO、不同曝光时间下采集的raw图,标定出噪声模型,然后用这个模型生成训练数据。如果条件不允许,至少要用泊松-高斯混合模型,而不是纯高斯。
数据增强方面,除了常规的旋转、翻转、裁剪,我特别推荐加一个操作:亮度抖动。因为暗光场景的亮度变化范围很大,网络需要适应不同亮度水平下的噪声强度。
4.7 空间域去噪的评估指标
评估去噪效果,不能只看PSNR。PSNR高不代表视觉效果好。我见过PSNR 32dB的图像,看起来比30dB的还差——因为PSNR对纹理细节不敏感。
我常用的评估组合:
| 指标 | 关注点 | 说明 |
|---|---|---|
| PSNR | 像素级误差 | 数值越高越好,但与人眼感知不完全一致 |
| SSIM | 结构相似性 | 更关注亮度、对比度、结构,比PSNR更贴近人眼 |
| LPIPS | 感知相似性 | 用预训练网络提取特征,计算特征距离,最接近人眼判断 |
| NIQE | 无参考质量 | 不需要干净图,适合评估真实场景去噪效果 |
我个人习惯:实验室阶段看PSNR+SSIM,产品化阶段看LPIPS+NIQE。因为最终用户不关心像素误差,他们只关心“看起来干不干净”。