在数字图像处理中,噪声问题是时常发生的,在这篇文章中将介绍有关噪声抑制的内容,具体包括有关噪声的介绍、抑制的算法等。
一、噪声
首先,我们知道图像噪声是指在拍摄或传输时图像所受到的随机干扰。而常见的噪声一般有两种,分别是高斯噪声与椒盐噪声,具体来说,高斯噪声出现的地点是一定的,即在每一个点上,但幅值却不一定;椒盐噪声则与之相反,它出现的位置是随机的,但幅值却基本相同。高斯噪声与椒盐噪声分别如下图具体如下图:
此时,如果我们放大图像详细去看可以得到如下图:
因此可以得出结论:观察到高斯噪声导致图像整体模糊,细节被随机分布的噪声点所掩盖,颜色失真显著;而椒盐噪声则在图像中引入了大量的白色(盐)和黑色(椒)斑点,虽然保留了部分原始图像的结构,但细节受到明显干扰。(在本文中噪声的添加是人工模拟添加的,添加代码在文章末尾)
在图像噪声中除了上述两种噪声外,还有量子噪声、泊松噪声、脉冲噪声与条纹噪声等,在此不一一细说。
二、噪声抑制
常见的噪声抑制一般有三种,分别是均值滤波、中值滤波以及边界保持类滤波,具体如下:
2.1 均值滤波
所谓均值滤波就是对于待处理图像给定一个模板,取这个模板中所有数值的均值然后代替模板中要处理的点的值。但是因为使用均值的缘故,会导致图像整体变得模糊,因此可以通过对于模板加权的手段来改善。其中常见的加权滤波器如下:
未加权的均值滤波代码如下:
def mean_filter(image, kernel_size=3):# 计算填充大小pad_size = kernel_size // 2# 获取图像尺寸和通道数height, width, channels = image.shapefiltered_image = np.zeros_like(image, dtype=float)# 边缘部分padded_image = np.pad(image, ((pad_size, pad_size), (pad_size, pad_size), (0, 0)), mode='edge')for m in range(height):for n in range(width):for c in range(channels):window = padded_image[m:m + kernel_size, n:n + kernel_size, c]filtered_image[m, n, c] = np.mean(window)return np.clip(filtered_image, 0, 255).astype(np.uint8)
我们对于刚才噪声模拟后的图像进行上述代码的均值滤波,我们会发现噪声点确实有所减少,但图像却变得模糊了,其中处理结果如下:
2.2 中值滤波
中值滤波的处理方法与均值滤波的类似,但它采用的是中值来代替,而不是均值。关于中值滤波的代码如下:
def median_filter(image, kernel_size=3):pad_size = kernel_size // 2height, width, channels = image.shapefiltered_image = np.zeros_like(image, dtype=np.uint8)padded_image = np.pad(image, ((pad_size, pad_size), (pad_size, pad_size), (0, 0)), mode='edge')for m in range(height):for n in range(width):for c in range(channels):window = padded_image[m:m + kernel_size, n:n + kernel_size, c]filtered_image[m, n, c] = int(np.median(window))return filtered_image
得到处理结果如下:
根据均值滤波与中值滤波的原理以及高斯噪声与椒盐噪声的特性进行对比可知:处理高斯噪声时,采用均值滤波更为适宜;而处理椒盐噪声时,则中值滤波更加有效。这是因为高斯噪声的幅值近似服从正态分布,并且均匀地分布在每个像素点上。因此,均值滤波可以将高斯噪声的所有误差影响通过平均化处理,从而有效地减小噪声的影响。然而,对于椒盐噪声而言,它表现为随机分布的极值点(即黑色胡椒噪声和白色盐噪声),这些极值点的存在会使得均值滤波的效果大打折扣。因为均值滤波在计算邻域内的平均值时,会被这些极端值显著影响,导致图像细节模糊,甚至引入新的噪声。
相比之下,中值滤波则更适合处理椒盐噪声。中值滤波通过选取邻域内所有像素值的中值来替换中心像素值,这样可以有效地去除那些随机出现的极值点,同时保留图像的边缘和其他重要特征。即使存在一些未被噪声影响的像素,在经过中值滤波后也不会受到其他噪声点的影响,从而保持了图像的整体清晰度。此外,由于中值滤波不依赖于平均值计算,而是基于排序选择中值,因此它对极端值具有更高的鲁棒性,能够更好地保护图像中的细节信息免受噪声干扰。
2.3 边界保持类滤波器
在一幅图像中,之所以可以清晰辨别出物体是因为边界的存在,而平滑滤波器(上述两种滤波器)则会模糊边界,所以为了减少图像边界的模糊性我们需要一些方法来保持边界。
首先,我们先分析为什么刚才的两种平滑处理器使得最终的结果变得模糊,是因为边界点与噪声点都具有灰度阶跃的特性,而平滑处理器则同时将两种点的灰度阶跃特性一起降低了,因此变得模糊,因此,只要保持这种灰度阶跃特性不变那么图像就不会变得模糊。所以,我们在对于存在灰度阶跃的点进行处理前要先判断它是否属于边界点,如果是则不处理,反之则处理。
K近邻滤波器
具体方法可以使用KNN的思想,在一个合适大小的模板中,对于每一个待处理的点,选择该点所在模板中与之相近的若干点进行均值处理,而对于模板中与之差距较大的点则不处理。那么就有了K近邻滤波器,其代码如下:
def knn_filter(image, k=5):img_array = np.array(image)h, w = img_array.shape[:2]filtered_img = np.zeros_like(img_array)for i in range(h):for j in range(w):# 获取当前像素及其邻域neighbors = []for x in range(max(0, i-1), min(h, i+2)):for y in range(max(0, j-1), min(w, j+2)):if (x, y) != (i, j):neighbors.append((img_array[x, y], (x, y)))# 计算距离并排序distances = [(distance.euclidean(neighbors[n][0], img_array[i, j]), n) for n in range(len(neighbors))]sorted_neighbors = sorted(distances, key=lambda x: x[0])# 选择最近的K个邻居closest_k = [neighbors[sorted_neighbors[n][1]] for n in range(k)]# 计算均值avg_value = np.mean([neighbor[0] for neighbor in closest_k])# 更新过滤后的图像filtered_img[i, j] = avg_valuereturn Image.fromarray(filtered_img.astype('uint8'), 'L')
Sigma 平滑滤波器
如果我们考虑将概率统计的知识引入噪声抑制的话,那么存在一个这样的原理,即属于同一类别的元素的置信区间落在均值附近2范围之内。所以Sigma滤波器的原理就是构造一个模板,计算模板的标准差,置信区间为当前像素值的范围。并将模板中落在置信范围内的像素的均值替换原来的像素值。具体代码如下:
def sigma_filter(image, kernel_size=3, sigma_threshold=0.25):img_array = np.array(image).astype(float)h, w = img_array.shape[:2]# 计算邻域内的均值和标准差mean_img = uniform_filter(img_array, size=kernel_size, mode='reflect')std_img = np.sqrt(uniform_filter(img_array**2, size=kernel_size, mode='reflect') - mean_img**2)# 初始化输出图像filtered_img = np.zeros_like(img_array)pad_size = kernel_size // 2for i in range(pad_size, h - pad_size):for j in range(pad_size, w - pad_size):window = img_array[i-pad_size:i+pad_size+1, j-pad_size:j+pad_size+1]center_pixel = img_array[i, j]local_std = std_img[i, j]# 筛选相似像素similar_pixels = [pixel for pixel in window.flatten() if abs(pixel - center_pixel) <= sigma_threshold * local_std]# 如果没有找到相似像素,默认取当前像素值if len(similar_pixels) > 0:filtered_img[i, j] = np.mean(similar_pixels)else:filtered_img[i, j] = center_pixelreturn Image.fromarray(filtered_img.astype('uint8'), 'L')
关于高斯噪声与椒盐噪声模拟添加的代码如下:
import numpy as np
import cv2
import matplotlib.pyplot as plt
import random# 读取数据
image = cv2.imread(r"C:\Users\20349\Desktop\picture\gaussian.jpg")
image_copy_gauss = image.copy()
image_copy_salt = image.copy()
# 检查图像是否成功加载
if image is None:raise ValueError("图像未找到或无法加载")
# 将BGR图像转换为RGB格式以便正确显示
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 高斯噪声模拟
mean = 0
sigma = 150
gauss = np.random.normal(mean, sigma, image.shape).astype('float32') # 使用浮点数来避免溢出
# 将图像转换为浮点数类型,以便可以安全地添加噪声
image_float = image_copy_gauss.astype('float32')
# 将噪声添加到图像上,并剪裁结果以确保它仍然在 [0, 255] 范围内
noisy_image = np.clip(image_float + gauss, 0, 255).astype('uint8')
# 再次转换回RGB用于显示
gauss_rgb = cv2.cvtColor(noisy_image, cv2.COLOR_BGR2RGB)# 椒盐噪声模拟
salt_papper = [0, 255]
for i in range(10000000):m = np.random.randint(0,image.shape[0])n = np.random.randint(0,image.shape[1])choose = random.choice(salt_papper)image_copy_salt[m,n,:] = [choose, choose, choose]salt_papper_rgb = cv2.cvtColor(image_copy_salt, cv2.COLOR_BGR2RGB)# 图像展示
plt.figure()
plt.subplot(1, 3, 1)
plt.imshow(image_rgb)
plt.title('Original Image')
plt.axis('off')plt.subplot(1, 3, 2)
plt.imshow(gauss_rgb)
plt.title('Gaussian Noisy')
plt.axis('off')plt.subplot(1, 3, 3)
plt.imshow(salt_papper_rgb)
plt.title('Salt and Pepper Noisy')
plt.axis('off')plt.show()
此上