图像噪声基础:噪声类型与数学表示
做图像处理这些年,我有个很深的体会——不懂噪声,就别谈降噪。就像医生看病,连病原体长什么样都不知道,开什么药都是瞎蒙。
这一章,咱们就把图像噪声的底裤扒干净。我会从最常见的三种噪声讲起,再给出它们的数学模型。嗯,这部分可能有点数学味,但别怕,我会用项目里的实际例子帮你理解。
什么是图像噪声?
说白了,噪声就是图像里不该出现的随机信号。你拍一张纯色的墙,放大一看,像素值不是完全均匀的,有的亮一点,有的暗一点——这就是噪声。
我刚开始做图像处理时,总觉得噪声是硬件的问题,跟我算法没关系。后来被现实狠狠教育了一顿:噪声无处不在,而且每种噪声的脾气都不一样。
核心观点:噪声不是bug,是图像采集和传输过程中不可避免的物理现象。我们的目标不是消灭噪声(不可能),而是把它控制在人眼或后续算法能接受的范围内。
三种最常见的噪声类型
实际项目中,90%以上的噪声都可以归为以下三类。我按遇到的频率排个序:
| 噪声类型 | 产生原因 | 典型场景 | 我的踩坑记录 |
|---|---|---|---|
| 高斯噪声 | 传感器热噪声、电子电路抖动 | 暗光拍摄、长曝光 | 监控摄像头在夜晚几乎全是高斯噪声 |
| 椒盐噪声 | 传输错误、存储介质坏道 | 老旧摄像头、无线传输 | 无人机图传经常出现黑白点 |
| 泊松噪声 | 光子计数统计特性 | 低光照、医学成像 | 显微镜图像在弱光下特别明显 |
高斯噪声:最普遍的噪声
高斯噪声是图像处理界的「常客」。它的特点是:每个像素的噪声值服从正态分布。
数学上长这样:
概率密度函数:p(x) = (1 / (σ * √(2π))) * exp(-(x - μ)² / (2σ²))
其中:
- μ = 均值(通常为0)
- σ = 标准差(控制噪声强度)
我个人习惯把高斯噪声想象成「每个像素上叠加了一个随机小扰动」。扰动的大小由σ决定,σ越大,图像越花。
实战经验:我在做手机夜景模式时,发现ISO越高,高斯噪声的σ值越大。ISO 800以下基本不用管,ISO 3200以上就得下猛药了。
给图像加高斯噪声的代码很简单:
import numpy as np
import cv2
def add_gaussian_noise(image, sigma=25):
noise = np.random.normal(0, sigma, image.shape)
noisy = image + noise
return np.clip(noisy, 0, 255).astype(np.uint8)
# 用法示例
img = cv2.imread('test.jpg', 0)
noisy_img = add_gaussian_noise(img, sigma=30)
椒盐噪声:最显眼的噪声
椒盐噪声,说白了就是图像上随机出现的一些纯白或纯黑的点。白的叫「盐」,黑的叫「椒」。
为什么叫这个名字?你想想看,往一盘菜里撒点盐和胡椒,是不是就这种效果?
数学模型更直接:
对于每个像素位置 (i, j):
- 以概率 p/2 设为 0(黑点,椒)
- 以概率 p/2 设为 255(白点,盐)
- 以概率 1-p 保持原值
其中 p 是噪声密度,范围 0~1
我曾经接手过一个项目,客户说他们的监控画面「全是雪花点」。我一看,好家伙,椒盐噪声密度高达0.3,也就是30%的像素都坏了。后来发现是传输线路的屏蔽层老化导致的。
注意:椒盐噪声虽然看起来吓人,但其实比高斯噪声好处理。中值滤波几行代码就能搞定。但要注意,噪声密度超过0.5时,中值滤波的效果会明显下降。
生成椒盐噪声的代码:
def add_salt_pepper_noise(image, prob=0.05):
output = image.copy()
# 椒噪声(黑点)
mask_pepper = np.random.random(image.shape) < prob/2
output[mask_pepper] = 0
# 盐噪声(白点)
mask_salt = np.random.random(image.shape) < prob/2
output[mask_salt] = 255
return output
泊松噪声:最「物理」的噪声
泊松噪声跟前面两种不太一样。它来源于光的量子特性——光子到达传感器的数量本身就是一个随机过程。
数学上,泊松噪声的模型是:
观测到的像素值 ~ Poisson(λ)
其中 λ = 真实的光子数(也就是无噪声时的像素值)
泊松分布的概率质量函数:
P(k) = (λ^k * e^(-λ)) / k!
k = 观测到的光子数
λ = 期望光子数
这里有个有意思的特性:泊松噪声的方差等于均值。也就是说,信号越强的地方,噪声也越强。这跟高斯噪声完全不同——高斯噪声的强度跟信号无关。
我在做医学X光图像处理时,对这个体会特别深。X光成像本身就是光子计数过程,低剂量拍摄时,图像暗部的噪声特别明显,就是因为泊松噪声在作怪。
实用技巧:处理泊松噪声时,我建议先做方差稳定化变换(比如Anscombe变换),把泊松噪声转换成近似的高斯噪声,再用高斯噪声的方法处理。这样效果会好很多。
模拟泊松噪声的代码:
def add_poisson_noise(image):
# 泊松噪声的强度与像素值成正比
# 需要先把像素值缩放到合适的范围
scaled = image / 255.0 * 100 # 缩放因子可调
noisy = np.random.poisson(scaled)
return np.clip(noisy / 100 * 255, 0, 255).astype(np.uint8)
三种噪声的对比总结
为了让你一眼看明白,我画了张对比表:
| 特性 | 高斯噪声 | 椒盐噪声 | 泊松噪声 |
|---|---|---|---|
| 分布形态 | 连续、对称 | 离散、二值 | 离散、偏态 |
| 与信号关系 | 独立 | 独立 | 相关(方差=均值) |
| 视觉表现 | 颗粒感、模糊 | 黑白点 | 暗部颗粒明显 |
| 常用降噪方法 | 高斯滤波、双边滤波 | 中值滤波 | Anscombe变换+高斯滤波 |
| 参数控制 | σ(标准差) | p(密度) | λ(均值) |
噪声模型的知识体系
下面这张图,是我自己梳理的噪声知识框架。每次带新人时,我都会先让他们看这个:
为什么必须搞懂噪声类型?
你可能会问:知道这些数学公式有什么用?我直接调个滤波函数不就行了?
嗯,这里有个坑。我曾经接手过一个项目,同事用高斯滤波去处理椒盐噪声,结果越滤越花。为什么?因为高斯滤波对椒盐噪声基本无效,反而会把噪声扩散到周围像素。
选错降噪方法,比不降噪还糟糕。
所以我的建议是:拿到一张带噪图像,先花30秒判断噪声类型。怎么判断?
- 看直方图:高斯噪声的直方图是平滑的钟形,椒盐噪声会在0和255处出现尖峰
- 看局部区域:放大暗部区域,如果颗粒感均匀,大概率是高斯噪声;如果出现孤立黑白点,就是椒盐噪声
- 看亮度关系:如果暗部噪声明显比亮部严重,很可能是泊松噪声
重要提醒:实际图像往往是多种噪声的混合体。比如手机夜景照片,既有传感器的高斯噪声,又有光子计数的泊松噪声。这时候就需要组合策略了——后面的章节我会详细讲。