2. 噪声模型:高斯噪声、泊松噪声、椒盐噪声的数学建模与仿真
做多帧降噪,第一件事不是调算法,而是搞清楚——你面对的噪声到底是什么来路。
我刚开始接触这个方向时,踩过一个坑。当时拿到一组暗光视频,直接上了均值滤波,结果画面糊成一团。后来一分析,发现噪声根本不是高斯分布,而是泊松占主导。嗯,从那以后,我养成了一个习惯:先建模,再降噪。
这一节,我们就来聊聊三种最常见的噪声模型。我会把数学公式、物理含义、以及仿真代码都给你拆开揉碎。你想想看,搞懂了噪声的“脾气”,后面设计滤波器才能有的放矢。
2.1 高斯噪声:传感器读数的“底色”
高斯噪声,说白了就是传感器在读取信号时,电子热运动带来的随机波动。它无处不在,是所有成像系统都绕不开的底噪。
2.1.1 数学定义
高斯噪声的概率密度函数长这样:
p(x) = (1 / (σ * √(2π))) * exp(-(x - μ)² / (2σ²))
其中:
- μ 是均值,通常为0(无偏噪声)
- σ 是标准差,决定了噪声的强度
我个人习惯用 σ 来表征噪声水平,而不是方差。因为标准差和像素值的量纲一致,更直观。
2.1.2 物理来源
高斯噪声主要来自:
- 热噪声:传感器温度越高,σ越大
- 读出噪声:ADC转换过程中的量化误差
- 放大器噪声:模拟增益引入的随机波动
2.1.3 仿真代码
用Python生成高斯噪声很简单,但要注意一点——噪声要加在浮点域,而不是整数域。我曾经因为直接在uint8上加噪声,导致截断失真,花了半天才定位到问题。
import numpy as np
import cv2
def add_gaussian_noise(image, sigma=25):
"""
给图像添加高斯噪声
image: 输入图像,范围[0, 255]
sigma: 噪声标准差
"""
# 转浮点,避免截断
img_float = image.astype(np.float32)
# 生成高斯噪声
noise = np.random.normal(0, sigma, img_float.shape)
# 加噪并裁剪
noisy = np.clip(img_float + noise, 0, 255).astype(np.uint8)
return noisy
# 使用示例
img = cv2.imread('clean.png')
noisy_img = add_gaussian_noise(img, sigma=30)
2.2 泊松噪声:光子计数的“量子本性”
泊松噪声,也叫散粒噪声。它来源于光的量子特性——光子到达传感器是随机的,服从泊松分布。
你想想看,在暗光环境下,每个像素接收到的光子数很少,比如只有10个。这时候随机波动(√10 ≈ 3.16)相对于信号本身就很显著了。这就是为什么夜景照片噪点特别多的根本原因。
2.2.1 数学定义
泊松分布的概率质量函数:
P(k) = (λ^k * e^(-λ)) / k!
其中:
- λ 是期望光子数(即信号强度)
- k 是实际观测到的光子数
泊松分布有一个重要性质:均值 = 方差 = λ。这意味着噪声的强度与信号本身成正比。信号越强,噪声的绝对幅度越大,但信噪比(SNR)反而越高。
2.2.2 与高斯噪声的区别
| 特性 | 高斯噪声 | 泊松噪声 |
|---|---|---|
| 与信号的关系 | 独立 | 依赖(方差=信号) |
| 主要场景 | 亮光、高ISO | 暗光、低光子数 |
| 分布形状 | 对称 | 低λ时偏斜 |
2.2.3 仿真代码
仿真泊松噪声时,我建议用 np.random.poisson 直接生成。注意输入图像要先归一化到光子数范围。
def add_poisson_noise(image, peak=100):
"""
给图像添加泊松噪声
image: 输入图像,范围[0, 255]
peak: 峰值光子数,控制噪声强度
"""
# 归一化到光子数
img_norm = image.astype(np.float32) / 255.0 * peak
# 生成泊松噪声
noisy = np.random.poisson(img_norm)
# 归一化回[0, 255]
noisy = np.clip(noisy / peak * 255, 0, 255).astype(np.uint8)
return noisy
# 使用示例
noisy_img = add_poisson_noise(img, peak=50)
2.3 椒盐噪声:像素的“死与生”
椒盐噪声,顾名思义,就是图像上随机出现一些纯黑(椒)或纯白(盐)的点。它不像高斯和泊松那样是连续的,而是离散的、突变的。
我记得有一次在工业检测项目中,相机传输线缆屏蔽没做好,图像上全是椒盐噪声。当时我以为是算法问题,折腾了两天,最后发现是硬件干扰。嗯,从那以后,我拿到图像第一件事就是先看有没有椒盐噪声——它往往是硬件问题的信号。
2.3.1 数学定义
椒盐噪声的模型很简单:
- 每个像素以概率 p 被污染
- 被污染的像素中,一半概率变成0(椒点),一半概率变成255(盐点)
所以总的噪声密度是 p,其中椒点和盐点各占 p/2。
2.3.2 仿真代码
def add_salt_pepper_noise(image, prob=0.05):
"""
给图像添加椒盐噪声
image: 输入图像
prob: 噪声密度(0~1)
"""
noisy = image.copy()
total_pixels = image.size
# 椒点数量
num_pepper = int(total_pixels * prob / 2)
# 盐点数量
num_salt = int(total_pixels * prob / 2)
# 随机位置
coords_pepper = [np.random.randint(0, i, num_pepper) for i in image.shape]
coords_salt = [np.random.randint(0, i, num_salt) for i in image.shape]
noisy[tuple(coords_pepper)] = 0
noisy[tuple(coords_salt)] = 255
return noisy
# 使用示例
noisy_img = add_salt_pepper_noise(img, prob=0.1)
2.4 三种噪声的对比与选择
实际场景中,噪声往往是混合的。比如暗光下,泊松噪声占主导,但读出噪声(高斯)也同时存在。我一般这样判断:
- 亮光、高ISO:高斯噪声为主
- 暗光、长曝光:泊松噪声为主
- 传输干扰、坏像素:椒盐噪声为主
做仿真时,我建议把三种噪声都加上,然后调权重。这样生成的测试数据更接近真实场景。
2.5 知识体系总览
下面这张图,是我自己总结的噪声模型知识框架。你可以把它当作一个快速索引:
这张图把三种噪声的源头、参数和特点都串起来了。我个人习惯把它打印出来贴在工位上,做算法选型时扫一眼,心里就有数了。
好了,噪声模型就聊到这里。搞懂了这些,后面讲多帧降噪算法时,你就能理解为什么有些滤波器对高斯噪声有效,对椒盐噪声却无能为力了。