3、传统去噪方法(一):均值滤波、高斯滤波的原理与Python实现
说到图像去噪,很多新手一上来就想着上深度学习。但我得说一句大实话:传统方法虽然“传统”,但在很多实际场景里,它比AI更稳、更快、更可控。尤其是均值滤波和高斯滤波,这两个可以说是去噪界的“基本功”。
我个人习惯把传统去噪方法比作“磨皮”。你想想看,皮肤上的痘痘和毛孔(噪声)怎么处理?最简单的办法就是把它周围的皮肤颜色“抹匀”。均值滤波和高斯滤波,干的就是这个活。
3.1 均值滤波:简单粗暴的“平均主义”
均值滤波的原理,说白了就是一句话:用邻域像素的平均值代替当前像素值。
举个例子。假设你有一张图,某个像素点因为传感器噪声变成了一个“白点”。这个白点跟周围格格不入,怎么办?我们把它周围3x3区域内的所有像素值加起来,除以9,得到平均值,然后把这个平均值赋给中心点。这样一来,那个突兀的白点就被“拉回”到正常水平了。
核心公式:
g(x, y) = (1 / (m × n)) × Σ f(i, j)
其中 m×n 是滤波核的大小,f(i,j) 是邻域内的像素值。
我在项目中遇到过一件事:有一次处理工业相机拍摄的金属表面图像,噪声是那种均匀的椒盐噪声。我试了各种高级算法,最后发现最简单的均值滤波,配合一个合适的核大小,效果反而最好。嗯,有时候别把事情想复杂了。
Python实现均值滤波
OpenCV里直接提供了现成的函数,但我建议你至少手写一次,这样才能真正理解它的工作原理。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像(假设是暗光拍摄的噪声图)
img = cv2.imread('noisy_dark.jpg', cv2.IMREAD_GRAYSCALE)
# 方法一:OpenCV自带函数
blurred_cv = cv2.blur(img, (5, 5)) # 5x5的滤波核
# 方法二:手写均值滤波(加深理解)
def my_mean_filter(image, kernel_size=5):
h, w = image.shape
pad = kernel_size // 2
# 边界填充,防止边缘处理不了
padded = np.pad(image, pad, mode='reflect')
result = np.zeros_like(image)
for i in range(h):
for j in range(w):
# 提取邻域
region = padded[i:i+kernel_size, j:j+kernel_size]
# 计算均值
result[i, j] = np.mean(region)
return result.astype(np.uint8)
blurred_manual = my_mean_filter(img, 5)
# 显示结果
plt.figure(figsize=(12, 4))
plt.subplot(131), plt.imshow(img, cmap='gray'), plt.title('原始噪声图')
plt.subplot(132), plt.imshow(blurred_cv, cmap='gray'), plt.title('OpenCV均值滤波')
plt.subplot(133), plt.imshow(blurred_manual, cmap='gray'), plt.title('手写均值滤波')
plt.show()
避坑指南:我曾经犯过一个低级错误——滤波核选得太大。比如用15x15的核去处理一张人脸照片,结果人脸变成了“马赛克”,五官都糊在一起了。均值滤波的核大小一般建议3x3到7x7之间,再大就要考虑高斯滤波了。
3.2 高斯滤波:带权重的“温柔平均”
均值滤波有个明显的缺点:它把邻域内所有像素一视同仁。你想想看,离中心点远的像素和近的像素,对中心点的影响应该一样吗?显然不应该。
高斯滤波就解决了这个问题。它给每个邻域像素分配一个权重,距离中心越近,权重越大;距离越远,权重越小。这个权重分布服从高斯函数(也就是正态分布)。
高斯核的生成公式:
G(x, y) = (1 / (2πσ²)) × exp(-(x² + y²) / (2σ²))
其中 σ 是标准差,控制着权重的衰减速度。
说白了,高斯滤波就是“加权平均”。它比均值滤波更聪明,因为它知道“远亲不如近邻”这个道理。
Python实现高斯滤波
# 方法一:OpenCV自带函数
gaussian_cv = cv2.GaussianBlur(img, (5, 5), sigmaX=1.5)
# 方法二:手写高斯滤波
def my_gaussian_filter(image, kernel_size=5, sigma=1.5):
h, w = image.shape
pad = kernel_size // 2
# 生成高斯核
kernel = np.zeros((kernel_size, kernel_size))
center = kernel_size // 2
for i in range(kernel_size):
for j in range(kernel_size):
x, y = i - center, j - center
kernel[i, j] = np.exp(-(x**2 + y**2) / (2 * sigma**2))
# 归一化,让所有权重之和为1
kernel /= np.sum(kernel)
# 边界填充
padded = np.pad(image, pad, mode='reflect')
result = np.zeros_like(image)
for i in range(h):
for j in range(w):
region = padded[i:i+kernel_size, j:j+kernel_size]
# 加权求和
result[i, j] = np.sum(region * kernel)
return result.astype(np.uint8)
gaussian_manual = my_gaussian_filter(img, 5, sigma=1.5)
# 对比显示
plt.figure(figsize=(12, 4))
plt.subplot(131), plt.imshow(img, cmap='gray'), plt.title('原始噪声图')
plt.subplot(132), plt.imshow(gaussian_cv, cmap='gray'), plt.title('OpenCV高斯滤波')
plt.subplot(133), plt.imshow(gaussian_manual, cmap='gray'), plt.title('手写高斯滤波')
plt.show()
个人经验:σ(sigma)这个参数很关键。σ越大,高斯核越“扁平”,滤波效果越接近均值滤波;σ越小,中心像素的权重越大,去噪效果越弱但细节保留得越好。我一般从σ=1.0开始试,根据噪声强度往上调。
3.3 均值滤波 vs 高斯滤波:怎么选?
很多同学会问:到底用哪个?我直接给结论:
| 对比维度 | 均值滤波 | 高斯滤波 |
|---|---|---|
| 去噪能力 | 均匀噪声效果好 | 高斯噪声效果更好 |
| 细节保留 | 较差,容易模糊边缘 | 较好,边缘保留更自然 |
| 计算速度 | 更快 | 稍慢(需要计算权重) |
| 适用场景 | 实时性要求高、噪声均匀 | 需要保留细节、噪声分布随机 |
我个人习惯:能上高斯就别用均值。除非你的项目对实时性要求极高,比如每秒处理上百帧视频,那均值滤波确实更快。否则,高斯滤波多出来的那点计算量,换来的画质提升是完全值得的。
3.4 本章知识体系
下面这张图帮你理清本章的核心逻辑:
避坑指南:我曾经在处理一张暗光拍摄的星空照片时,直接用均值滤波去噪,结果星星全被抹掉了。后来换成高斯滤波,把σ调小到0.8,星星保住了,背景噪声也去掉了。记住:细节越重要,越要用高斯滤波。
好了,均值滤波和高斯滤波就讲到这里。这两个方法虽然简单,但它们是所有去噪算法的基础。你想想看,后面要讲的非局部均值、双边滤波,甚至深度学习去噪,本质上都是在“加权平均”这个思路上做文章。把今天的内容吃透,后面的路就好走了。
公众号:蓝海资料掘金营,微信deep3321