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)。