4. 噪声评估指标:PSNR、SSIM、LPIPS、噪声曲线(NPC)、信噪比(SNR)计算

做降噪这么多年,我最大的体会是:没有完美的指标,只有合适的场景。你辛辛苦苦调了一版降噪参数,肉眼看着挺干净,结果一跑PSNR反而降了——这种事我遇到过太多次了。

这一章,咱们就把几个主流评估指标掰开揉碎讲清楚。哪些指标看像素误差,哪些指标看人眼感知,哪些指标专门用来分析噪声本身。搞懂了这些,你才能知道自己的算法到底好在哪里、差在哪里。

核心观点:评估指标不是用来“证明”算法好坏的,而是用来“诊断”算法问题的。每个指标都有自己的盲区,组合使用才是正道。
噪声评估指标体系 降噪质量评估 全参考指标 PSNR SSIM LPIPS MSE 无参考指标 噪声曲线 SNR BRISQUE NIQE 💡 使用建议 有ground truth时:PSNR + SSIM + LPIPS 组合使用 无ground truth时:噪声曲线 + SNR + 无参考IQA

4.1 PSNR —— 最经典的像素级指标

PSNR全称是峰值信噪比(Peak Signal-to-Noise Ratio)。说白了,它就是算两幅图像之间像素差了多少。数值越高,说明像素越接近。

公式长这样:

MSE = (1/m*n) * Σ(I - K)²
PSNR = 10 * log10(MAX² / MSE)

其中MAX是像素最大值,8bit图像就是255。

我个人习惯:PSNR低于30dB的降噪结果,基本不能看。30-35dB算及格,35-40dB算不错,40dB以上就非常好了。但注意——这只是经验值,不同场景差异很大。

避坑指南:我曾经在低照度场景下吃过亏。暗光图像本身噪声大,PSNR天然就低。你强行把PSNR拉高,结果图像被磨成“塑料感”——细节全没了。所以PSNR高不代表视觉好,它只代表像素误差小。

4.2 SSIM —— 结构相似性,更接近人眼

SSIM(Structural Similarity Index)比PSNR聪明一点。它不光看像素差,还看亮度、对比度和结构三个维度的一致性。

公式拆开看:

SSIM(x,y) = [l(x,y)]^α * [c(x,y)]^β * [s(x,y)]^γ

其中:
l(x,y) = (2μxμy + C1) / (μx² + μy² + C1)  // 亮度
c(x,y) = (2σxσy + C2) / (σx² + σy² + C2)  // 对比度
s(x,y) = (σxy + C3) / (σxσy + C3)          // 结构

SSIM的取值范围是0到1,越接近1越好。0.95以上说明结构保留得很好。

我在项目中遇到过这样的情况:PSNR涨了2dB,但SSIM反而掉了。一查原因,降噪算法把纹理细节磨平了,像素误差小了,但结构信息丢了。这时候SSIM比PSNR更诚实。

指标 关注点 取值范围 典型阈值
PSNR 像素级误差 0~∞ (dB) >35dB 良好
SSIM 结构相似性 0~1 >0.9 良好
LPIPS 感知相似性 0~∞ <0.1 良好

4.3 LPIPS —— 用深度学习“看”图像质量

LPIPS(Learned Perceptual Image Patch Similarity)是近几年火起来的指标。它用预训练的CNN(比如AlexNet、VGG)提取特征,然后在特征空间里算距离。

为什么需要它?因为PSNR和SSIM在某些场景下会“骗人”。比如一张图稍微平移了一个像素,PSNR会暴跌,但人眼根本看不出来。LPIPS对这种微小位移更鲁棒。

代码实现很简单:

import lpips
import torch

# 加载预训练模型
loss_fn = lpips.LPIPS(net='alex')

# 假设img0和img1是归一化到[-1,1]的tensor
d = loss_fn(img0, img1)
print(f'LPIPS distance: {d.item():.4f}')
注意:LPIPS的值越小越好,0表示完全一样。但不同网络(AlexNet vs VGG)算出来的值不能直接比较。我建议固定用AlexNet版本,它在降噪任务上表现最稳定。

4.4 噪声曲线(NPC)—— 无参考场景下的利器

前面几个指标都需要ground truth。但实际项目中,你哪有那么多干净图?这时候就需要无参考指标了。

噪声曲线(Noise Profile Curve, NPC)是我在工业项目中用得最多的工具。它的思路很简单:把图像分成均匀区域,统计这些区域的方差随亮度的变化。

具体步骤:

  1. 将图像分割成N×N的小块(我一般用32×32)
  2. 计算每个块的均值和方差
  3. 按均值亮度分bin(比如0-255分成16个bin)
  4. 每个bin内取方差的中位数或均值
  5. 绘制亮度-方差曲线

噪声曲线能告诉你三件事:

  • 噪声水平:曲线越高,噪声越大
  • 噪声类型:曲线随亮度线性增长→高斯噪声;曲线在暗部翘起→读出噪声或暗电流噪声
  • 降噪效果:降噪后的曲线应该整体压低,且不能出现异常尖峰
实战经验:我曾经用噪声曲线发现了一个降噪算法的bug。算法在中等亮度区域(100-150)降噪过度,导致曲线出现“凹陷”——这其实是纹理被磨平了。光看PSNR根本发现不了。

4.5 信噪比(SNR)计算

SNR在信号处理领域很基础,但在图像降噪里,计算方式有点讲究。

标准公式:

SNR = 10 * log10(信号功率 / 噪声功率)

问题来了:图像里的“信号”和“噪声”怎么分开?

我常用的两种方法:

  • 均匀区域法:找一块均匀区域(比如灰卡),均值当信号,方差当噪声。适合实验室环境。
  • 差分法:拍两张完全相同的场景,相减得到噪声图。适合有固定场景的测试。

实际代码:

import numpy as np

def compute_snr(image, noise_region):
    """
    image: 整张图像
    noise_region: 均匀区域的mask (bool数组)
    """
    signal = np.mean(image[~noise_region])
    noise_std = np.std(image[noise_region])
    snr = 20 * np.log10(signal / noise_std)
    return snr
小技巧:SNR和PSNR的区别要搞清楚。PSNR是用峰值信号(255)做分母,SNR是用实际信号均值做分母。所以同一张图,PSNR通常比SNR高10-20dB。别搞混了。

4.6 指标组合使用策略

说了这么多,到底怎么用?我总结了一个组合策略:

场景 推荐组合 原因
有ground truth PSNR + SSIM + LPIPS 像素级+结构级+感知级全覆盖
无ground truth 噪声曲线 + SNR + BRISQUE 无参考场景下的黄金组合
算法调参 噪声曲线 + PSNR 噪声曲线看噪声形态,PSNR看整体误差
最终验收 主观评测 + LPIPS 人眼说了算,LPIPS辅助量化

嗯,指标这东西,用多了就有感觉了。刚开始你可能觉得PSNR就是一切,后来你会发现SSIM更靠谱,再后来你会意识到——真正的好算法,是让所有指标都“不差”,同时让肉眼觉得“舒服”。