1. 多帧降噪基础

多帧降噪基础:平均法降噪 学习卡片

任务目标

拍摄多帧同一静态场景的图像,每帧均含独立随机噪声。通过对齐后的多帧图像逐像素取平均,利用噪声的随机性使其在平均过程中相互抵消,从而得到更接近真实场景的降噪结果。本案例使用合成噪声图像模拟多帧采集,实现平均法降噪,并定量评估降噪效果。

实现原理

设真实场景为 \(I(x,y)\),第 \(k\) 帧观测为 \(I_k(x,y)=I(x,y)+N_k(x,y)\),其中 \(N_k\) 为均值为0、方差为 \(\sigma^2\) 的独立随机噪声。对 \(K\) 帧取平均:

\[ \bar{I}(x,y)=\frac{1}{K}\sum_{k=1}^{K} I_k(x,y)=I(x,y)+\frac{1}{K}\sum_{k=1}^{K}N_k(x,y) \]

平均后噪声方差降为 \(\sigma^2/K\),标准差降为 \(\sigma/\sqrt{K}\)。帧数越多,降噪越明显,但需保证帧间场景静止且已对齐。

环境依赖

需要 Python 3.8+,以及 numpy、opencv-python、matplotlib 库。安装命令:

pip install numpy opencv-python matplotlib

完整代码


import numpy as np
import cv2
import matplotlib.pyplot as plt

def generate_noisy_frames(clean_img, num_frames=8, noise_sigma=25):
    """生成多帧带高斯噪声的图像"""
    frames = []
    for _ in range(num_frames):
        noise = np.random.normal(0, noise_sigma, clean_img.shape).astype(np.float32)
        noisy = clean_img.astype(np.float32) + noise
        frames.append(np.clip(noisy, 0, 255))
    return frames

def average_denoise(frames):
    """多帧平均降噪"""
    stacked = np.stack(frames, axis=0)
    return np.mean(stacked, axis=0)

def compute_psnr(img1, img2):
    """计算PSNR(峰值信噪比)"""
    mse = np.mean((img1.astype(np.float32) - img2.astype(np.float32)) ** 2)
    if mse == 0:
        return float('inf')
    return 10 * np.log10(255.0**2 / mse)

def main():
    # 生成干净图像(模拟真实场景)
    clean = cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE)
    if clean is None:
        # 若图片不存在,生成合成测试图
        x = np.linspace(0, 2*np.pi, 256)
        y = np.linspace(0, 2*np.pi, 256)
        X, Y = np.meshgrid(x, y)
        clean = (128 + 100*np.sin(X) * np.cos(Y)).astype(np.uint8)
    
    # 生成8帧噪声图像
    frames = generate_noisy_frames(clean, num_frames=8, noise_sigma=25)
    
    # 平均降噪
    denoised = average_denoise(frames)
    denoised_uint8 = np.clip(denoised, 0, 255).astype(np.uint8)
    
    # 评估:单帧噪声图 vs 平均结果
    single_noisy = frames[0].astype(np.uint8)
    psnr_single = compute_psnr(single_noisy, clean)
    psnr_avg = compute_psnr(denoised_uint8, clean)
    
    print(f"单帧噪声图 PSNR: {psnr_single:.2f} dB")
    print(f"8帧平均后 PSNR: {psnr_avg:.2f} dB")
    print(f"PSNR 提升: {psnr_avg - psnr_single:.2f} dB")
    
    # 可视化对比
    plt.figure(figsize=(12, 4))
    plt.subplot(1, 3, 1)
    plt.imshow(clean, cmap='gray')
    plt.title('原始图像')
    plt.axis('off')
    
    plt.subplot(1, 3, 2)
    plt.imshow(single_noisy, cmap='gray')
    plt.title('单帧噪声图')
    plt.axis('off')
    
    plt.subplot(1, 3, 3)
    plt.imshow(denoised_uint8, cmap='gray')
    plt.title('8帧平均结果')
    plt.axis('off')
    
    plt.tight_layout()
    plt.savefig('denoise_result.png', dpi=150)
    plt.show()

if __name__ == '__main__':
    main()

运行方式

python average_denoise.py

若当前目录存在 lena.png 则使用该图,否则自动生成合成测试图。运行后终端输出 PSNR 指标,并弹出对比图,同时保存为 denoise_result.png。

验证方法与判定标准

  • PSNR 提升:8帧平均后的 PSNR 应比单帧噪声图高约 \(20 \log_{10}\sqrt{8} \approx 9.03\) dB(理论值)。实际因噪声随机性,提升值在 8.5~9.5 dB 范围内即符合预期。
  • 视觉检查:平均结果应明显比单帧噪声图平滑,纹理细节保留,无重影或模糊伪影。
  • 帧数影响:将 num_frames 改为 4 或 16 重新运行,PSNR 提升应分别接近 6 dB 和 12 dB,验证 \(\sqrt{K}\) 关系。

常见问题

  • 图像出现模糊:若帧间存在轻微位移,需先做配准对齐,否则平均会引入模糊。本案例假设帧间完全静止。
  • PSNR 提升低于理论值:噪声需为独立同分布随机噪声;若噪声含固定模式(如条纹噪声),平均法无法消除。
  • 内存占用:帧数过多时 np.stack 会占用大量内存,可改用累加方式:acc = np.zeros_like(frames[0]); for f in frames: acc += f; result = acc / len(frames)。
平均法降噪原理:噪声标准差随帧数增加按 1/√K 衰减 噪声标准差 σ(K) 帧数 K 0 4 8 12 16 20 24 0 5 10 15 20 K=2 K=4 K=8 K=12 K=16 K=20 K=24 K=1: σ=25 σ(K) = σ₀ / √K σ₀ = 25(单帧噪声) 帧数翻倍 → 噪声降至 原来的 1/√2 ≈ 0.707 倍