2. 图像噪声模型

各位同学好,我是你们的讲师。今天咱们来聊聊图像噪声。说实话,噪声这东西,做图像处理的人天天见。你想想看,只要是真实传感器拍出来的照片,就一定有噪声。我刚开始做这个方向时,总觉得噪声是“脏东西”,恨不得一键清除。后来才明白——不了解噪声的脾气,就别想做好降噪。

这一章,我们就来解剖三种最常见的噪声:高斯噪声、泊松噪声、椒盐噪声。我会从数学建模讲到代码仿真,中间穿插一些我在项目里踩过的坑。

核心观点: 噪声不是随机垃圾,它有明确的统计规律。掌握了这些规律,你才能“对症下药”。
图像噪声模型分类与特性 图像噪声 高斯噪声 泊松噪声 椒盐噪声 特性 • 加性噪声,与信号无关 • 概率密度服从正态分布 • 参数:均值 μ,标准差 σ • 常见于传感器热噪声 特性 • 信号相关噪声 • 方差 = 均值(散粒噪声) • 低光照下更明显 • 常见于光子计数场景 特性 • 非加性,随机像素替换 • 取值仅为 0 或 255 • 参数:噪声密度 d • 常见于传输错误

2.1 高斯噪声:最“标准”的噪声模型

高斯噪声,说白了就是加性白噪声。它的概率密度函数长这样:

p(x) = (1 / (σ * √(2π))) * exp(-(x - μ)² / (2σ²))

其中 μ 是均值,σ 是标准差。在图像里,我们通常假设 μ=0,σ 控制噪声强度。

我个人习惯用 np.random.normal() 来生成。但要注意——图像像素值范围是 [0, 255],加完噪声后一定要做截断处理。不然会出现像素值溢出,比如 260 这种“非法值”。

小技巧: 仿真时我一般用 σ=25 作为“中等噪声”,σ=50 作为“强噪声”。这个范围在里很常见,方便对比实验结果。

来看代码:

import cv2
import numpy as np

def add_gaussian_noise(image, mean=0, sigma=25):
    """
    给图像添加高斯噪声
    :param image: 输入图像 (uint8, [0,255])
    :param mean: 噪声均值
    :param sigma: 噪声标准差
    :return: 加噪图像
    """
    noise = np.random.normal(mean, sigma, image.shape)
    noisy = image.astype(np.float32) + noise
    noisy = np.clip(noisy, 0, 255).astype(np.uint8)
    return noisy

# 使用示例
img = cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE)
noisy_img = add_gaussian_noise(img, sigma=30)

这里有个坑——np.random.normal 生成的是 float64,直接加给 uint8 会溢出。我刚开始学的时候没注意,结果图像一片白,还以为是代码写错了。后来才反应过来,一定要先转 float 再截断。

2.2 泊松噪声:低光照下的“硬骨头”

泊松噪声,也叫散粒噪声。它跟高斯噪声最大的区别是——噪声强度跟信号强度有关。你想想看,在暗光环境下拍照,噪点是不是特别多?那就是泊松噪声在作怪。

数学上,泊松分布的概率质量函数是:

P(k) = (λ^k * e^(-λ)) / k!

在图像里,λ 就是像素的真实强度值。也就是说,像素越亮,噪声方差越大。

我在做夜间监控项目时遇到过这个问题。白天效果很好的降噪算法,到了晚上就崩了。后来分析才发现,是泊松噪声的“信号相关”特性没处理好。

注意: 泊松噪声不能用简单的“加噪声”来仿真。正确的做法是:把像素值当作泊松分布的 λ 参数,然后从该分布中采样。OpenCV 的 cv2.randn() 不适用于泊松噪声。

正确的仿真代码:

def add_poisson_noise(image):
    """
    给图像添加泊松噪声(散粒噪声)
    :param image: 输入图像 (uint8, [0,255])
    :return: 加噪图像
    """
    # 将像素值映射到光子计数范围
    # 通常需要缩放,因为泊松分布的 λ 不能太大
    scale = 0.1  # 缩放因子,控制噪声强度
    lam = image.astype(np.float32) * scale
    noisy = np.random.poisson(lam)
    # 还原到 [0,255] 范围
    noisy = (noisy / scale).astype(np.uint8)
    noisy = np.clip(noisy, 0, 255)
    return noisy

这里 scale 参数很关键。它控制着“光子计数”的缩放比例。scale 越小,噪声越明显。我一般用 0.05~0.2 来模拟不同光照条件。

2.3 椒盐噪声:最“暴力”的噪声

椒盐噪声,顾名思义,就是图像上随机出现白点(盐)和黑点(椒)。它不像前两种噪声那样“温和”,而是直接把像素值替换成极端值。

它的数学模型很简单:

  • 每个像素以概率 p/2 变成 0(黑点)
  • 每个像素以概率 p/2 变成 255(白点)
  • 每个像素以概率 1-p 保持不变

其中 p 就是噪声密度。

仿真代码:

def add_salt_pepper_noise(image, density=0.05):
    """
    给图像添加椒盐噪声
    :param image: 输入图像 (uint8, [0,255])
    :param density: 噪声密度 (0~1)
    :return: 加噪图像
    """
    noisy = image.copy()
    total_pixels = image.size
    
    # 椒噪声(黑点)
    num_pepper = int(total_pixels * density / 2)
    coords = [np.random.randint(0, i, num_pepper) for i in image.shape]
    noisy[coords[0], coords[1]] = 0
    
    # 盐噪声(白点)
    num_salt = int(total_pixels * density / 2)
    coords = [np.random.randint(0, i, num_salt) for i in image.shape]
    noisy[coords[0], coords[1]] = 255
    
    return noisy
经验值: 密度 0.01 算轻度,0.05 算中度,0.1 以上算重度。我曾经在传输链路测试中见过密度 0.3 的椒盐噪声——那画面简直没法看。

2.4 三种噪声的对比与选择

我把三种噪声的关键参数整理成了表格,方便你对照:

噪声类型 数学分布 与信号关系 关键参数 典型场景
高斯噪声 正态分布 加性,独立 均值 μ,标准差 σ 传感器热噪声、放大器噪声
泊松噪声 泊松分布 信号相关 λ(像素强度) 低光照成像、医学影像
椒盐噪声 伯努利分布 像素替换 噪声密度 d 传输错误、存储损坏

在实际项目中,我建议你这样选:

  • 做通用算法验证:用高斯噪声就够了,简单可控
  • 做低光照场景:必须加泊松噪声,否则算法在真实场景会失效
  • 做鲁棒性测试:三种噪声都加,看看算法能扛到哪种程度

2.5 仿真实验:看看噪声长什么样

我们来做一个简单的对比实验。假设有一张 256x256 的灰度图,分别添加三种噪声:

import matplotlib.pyplot as plt

# 生成测试图像(渐变灰度图)
x = np.linspace(0, 255, 256)
y = np.linspace(0, 255, 256)
X, Y = np.meshgrid(x, y)
test_img = ((X + Y) / 2).astype(np.uint8)

# 添加噪声
gaussian_img = add_gaussian_noise(test_img, sigma=30)
poisson_img = add_poisson_noise(test_img, scale=0.1)
sp_img = add_salt_pepper_noise(test_img, density=0.05)

# 显示结果
plt.figure(figsize=(12, 3))
plt.subplot(141); plt.imshow(test_img, cmap='gray'); plt.title('原图')
plt.subplot(142); plt.imshow(gaussian_img, cmap='gray'); plt.title('高斯噪声 σ=30')
plt.subplot(143); plt.imshow(poisson_img, cmap='gray'); plt.title('泊松噪声 scale=0.1')
plt.subplot(144); plt.imshow(sp_img, cmap='gray'); plt.title('椒盐噪声 d=0.05')
plt.tight_layout()
plt.show()

你会发现:高斯噪声让整张图“蒙了一层雾”;泊松噪声在亮区更明显;椒盐噪声则是零散的黑白点。每种噪声的“长相”都不一样,对应的降噪策略也完全不同。

一句话总结: 高斯噪声是“均匀的模糊”,泊松噪声是“亮区更脏”,椒盐噪声是“随机撒点”。记住这个直觉,后面学降噪算法时会轻松很多。

2.6 避坑指南:我踩过的三个坑

最后分享几个实战中容易犯的错误:

  1. 忘记截断像素值:加完噪声后像素可能超出 [0,255],不截断的话,imshow 会显示异常。我早期调试时经常被这个坑到。
  2. 泊松噪声的缩放因子:直接对 uint8 图像做泊松采样,λ 太大(比如 200)会导致分布近似高斯,失去泊松特性。一定要先缩放。
  3. 椒盐噪声的对称性:有些实现只加白点不加黑点,或者比例不对。记得保持椒盐各半,除非你有特殊需求。