2. 多帧降噪进阶
任务目标
本案例在案例1(多帧平均法)基础上,实现基于中值法的多帧降噪:对同一场景连续拍摄的N帧图像,在逐像素位置上取N个灰度值的中位数作为输出像素值,并与平均法结果进行差异对比。中值法对椒盐噪声、瞬态高亮噪声(如行人闪光、反光闪烁)具有更好的抑制能力,但计算量高于平均法。
环境与依赖
Python 3.8+,仅需NumPy和OpenCV(读取/保存图像用)。若无需读写文件,仅NumPy即可完成核心计算。
pip install numpy opencv-python
完整实现
import numpy as np
import cv2
def generate_noisy_frames(clean, num_frames=9, salt_prob=0.05, gauss_sigma=10):
"""生成带椒盐+高斯噪声的多帧图像序列(模拟真实拍摄噪声)"""
frames = []
h, w = clean.shape[:2]
for _ in range(num_frames):
frame = clean.copy().astype(np.float32)
# 高斯噪声
frame += np.random.normal(0, gauss_sigma, frame.shape)
# 椒盐噪声
salt_mask = np.random.random(frame.shape[:2]) < salt_prob
pepper_mask = np.random.random(frame.shape[:2]) < salt_prob
if len(frame.shape) == 3:
frame[salt_mask] = 255
frame[pepper_mask] = 0
else:
frame[salt_mask] = 255
frame[pepper_mask] = 0
frames.append(np.clip(frame, 0, 255).astype(np.uint8))
return frames
def median_denoise(frames):
"""中值法:逐像素取多帧中位数"""
stack = np.stack(frames, axis=0).astype(np.float32) # (N,H,W,C)或(N,H,W)
return np.median(stack, axis=0).astype(np.uint8)
def average_denoise(frames):
"""平均法:逐像素取多帧均值"""
stack = np.stack(frames, axis=0).astype(np.float32)
return np.mean(stack, axis=0).astype(np.uint8)
def psnr(img1, img2):
"""峰值信噪比(dB),越高越接近参考图"""
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)
# ========== 主流程 ==========
if __name__ == "__main__":
np.random.seed(42) # 固定随机种子保证可复现
# 1. 生成干净参考图(用OpenCV内置图像或自建简单图案)
clean_img = np.zeros((128, 128, 3), dtype=np.uint8)
clean_img[:] = (100, 150, 200) # 均匀底色
cv2.rectangle(clean_img, (30, 30), (90, 90), (255, 255, 255), -1) # 白色方块
cv2.circle(clean_img, (64, 64), 20, (0, 0, 0), 2) # 黑色圆环
# 2. 生成9帧带噪图像
noisy_frames = generate_noisy_frames(clean_img, num_frames=9)
# 3. 两种方法降噪
median_result = median_denoise(noisy_frames)
average_result = average_denoise(noisy_frames)
# 4. 定量对比(以干净图为参考)
psnr_median = psnr(median_result, clean_img)
psnr_average = psnr(average_result, clean_img)
print(f"中值法 PSNR: {psnr_median:.2f} dB")
print(f"平均法 PSNR: {psnr_average:.2f} dB")
# 5. 保存结果供目视检查
cv2.imwrite("median_result.png", median_result)
cv2.imwrite("average_result.png", average_result)
cv2.imwrite("clean_reference.png", clean_img)
cv2.imwrite("first_noisy_frame.png", noisy_frames[0])
运行与验证
运行方式:将上述代码保存为 median_denoise.py,在命令行执行:
python median_denoise.py
验证方法:
- 定量判定:观察控制台输出的PSNR值。由于椒盐噪声占比5%,中值法的PSNR应显著高于平均法(通常高5~15 dB)。若随机种子固定为42,两次运行结果完全一致。
- 目视判定:打开生成的
median_result.png与average_result.png。中值法结果中不应残留黑白椒盐噪点,边缘(方块与圆环轮廓)保持清晰;平均法结果中可能仍有少量椒盐噪点残留(表现为暗/亮细点),且边缘略模糊。 - 差异对比:将
first_noisy_frame.png(单帧带噪图)与两张结果图并列查看,可直观看到中值法对极值噪声的彻底清除效果。
关键说明
中值法对椒盐噪声的抑制原理是:椒盐噪声将像素置为极值(0或255),而中位数对极值不敏感,因此能完全滤除孤立椒盐点。平均法会将极值拉入均值,导致噪点以“灰影”形式残留。本实现中噪声模型同时包含高斯噪声和椒盐噪声,更接近真实拍摄场景;若仅存在高斯噪声,两种方法PSNR差异会缩小,中值法略低于平均法——这正是两者适用场景的边界所在。