一、空间滤波机理
空间滤波是图像处理领域的一种基本技术,它涉及对图像中的每个像素及其周围邻域像素的直接操作。空间滤波的核心是使用一个称为滤波器或核(kernel)的小窗口,在图像上滑动这个窗口,并用窗口中的像素值与滤波器的权重相乘后求和,来更新中心像素的值。这个过程可以用于图像的平滑、锐化、边缘增强等多种目的。
空间滤波通过对图像每个像素及其邻域像素进行操作来更新中心像素的值。需要注意的是,这种处理是在图像的空间域内进行的,并不涉及到将图像转换到其他数学域(如频率域)的操作。
像素值的更新究竟是如何实现的呢?我们可以通过下面的示意图学习空间滤波的具体操作。
模板(滤波器核)在原图像上滑动,覆盖每个像素及其周围的邻域像素。通过将滤波器中的权重与其下的像素值相乘并求和,计算出新的中心像素值。
二、空间相关与卷积
在进行线性空间滤波操作时,必须清楚地理解两个相近的概念。一个是相关(correlation),另一个是卷积(Convolution)。
在介绍相关和卷积前,我们先介绍一个重要概念——离散单位冲激(discrete unit impulse),一个包含单个1其余都是0的函数。
(一)相关
空间相关使用的公式:
相关,即平移滤波器模板,计算每个位置乘积之和。接下来我们将举个详细的例子。
①给出滤波器w和一个一维函数f
②将滤波器模板与函数f开始位置对齐,准备平移滤波器模板
③将未覆盖的位置补0,使计算更直观
④平移滤波器模板,依次计算每个平移位置下,二者两列相乘得到的乘积
⑤得到相关结果并进行适当裁剪
需要注意的是,②中将滤波器与函数f的开始位置对齐,是因为左移二者乘积将恒为为0,无意义。
可以发现,滤波器 w与离散单位冲激函数相关,得到的结果就是 w的一个拷贝,但旋转了180°(即数字序列倒转)。因此一个函数与离散单位冲激相关,在该冲激位置产生这个函数的一个翻转的版本。
(二)卷积
空间卷积使用的公式:
空间卷积与空间相关原理相似,区别在于滤波器需要旋转180°,同样,我们将给出详细例子。
根据公式可知,卷积的基本特性是某个函数与某个单位冲激卷积,得到一个在该冲激处的这个函数的拷贝。
三、几种空间滤波器
(一)平滑空间滤波器
1.均值滤波器
(1)实现原理
均值滤波器通过替换图像中的每个像素值为其邻域像素值的算术平均值来工作。这种滤波器通常使用一个方形窗口,例如3x3或5x5,其中所有的权重都是相等的。
均值滤波使用到的滤波器核公式如下:
(2)实现代码
# 均值滤波器函数
def mean_kernel(image):
# 定义均值
mean = 9
# 获取图像的宽度、高度和通道数
width, height, channels = image.shape
# 创建一个与原始图像大小相同的备份图像
backup = np.zeros((width, height, channels), dtype=np.uint8)
# 遍历每个像素点,对其进行滤波操作
for i in range(1, width - 1):
for j in range(1, height - 1):
for c in range(channels):
# 获取当前像素点及其周围八个像素的RGB值
px000 = int(image[i - 1][j - 1][c])
px001 = int(image[i][j - 1][c])
px002 = int(image[i + 1][j - 1][c])
px010 = int(image[i - 1][j][c])
px011 = int(image[i][j][c])
px012 = int(image[i + 1][j][c])
px100 = int(image[i - 1][j + 1][c])
px101 = int(image[i][j + 1][c])
px102 = int(image[i + 1][j + 1][c])
# 计算这九个像素的RGB值之和
sigma = px000 + px001 + px002 +\
px010 + px011 + px012 +\
px100 + px101 + px102
# 将求得的RGB值之和除以均值,得到当前像素的滤波后的RGB值,并保存到backup图像中
backup[i][j][c] = round(sigma / mean)
# 返回输出结果
return backup
(3)效果展示
原图和处理后的图片如下:
可以看出,原图比处理后得到的图片更清晰,而且当ksize
(卷积核的大小)过大时,可能会导致图像的颜色变暗或变黑。这是因为均值滤波器将卷积核覆盖的像素的颜色值取平均,而较大的卷积核会覆盖更多的像素,从而导致颜色值的平均值变低。
2.加权线性滤波器
(1)实现原理
加权线性滤波器实质上是在均值滤波的基础上给予像素不同的权重。
常见的权重设计方式有:
-
线性权重:在滑动窗口中,最早的样本权重最小,最新的样本权重最大。这种权重设计假设信号的最新样本更重要,而旧样本的重要性逐渐减小。
-
高斯权重:使用高斯分布函数来定义权重,使得距离当前样本较近的样本具有较高的权重,而距离较远的样本具有较低的权重。
(2)实现代码
下面以高斯权重为例,给出对应的实现代码:
# 导入需要用到的库函数
import cv2
import numpy as np
# 加权线性滤波器函数
def gaussian_filter(img, K_size, sigma):
H, W, C = img.shape
# 计算填充大小
pad = K_size // 2
# 创建填充后的图像
out = np.zeros((H + pad * 2, W + pad * 2, C), dtype=float)
# 将原始图像复制到填充图像中心
out[pad: pad + H, pad: pad + W] = img.copy().astype(float)
## prepare Kernel
# 创建高斯核矩阵
K = np.zeros((K_size, K_size), dtype=float)
for x in range(-pad, -pad + K_size):
for y in range(-pad, -pad + K_size):
# 计算高斯核中每个元素的值
K[y + pad, x + pad] = np.exp(-(x ** 2 + y ** 2) / (2 * (sigma ** 2)))
# 归一化高斯核
K /= (2 * np.pi * sigma * sigma)
K /= K.sum()
# 创建临时变量保存填充后的图像
tmp = out.copy()
# 对每个像素进行加权计算
for y in range(H):
for x in range(W):
for c in range(C):
# 使用高斯核对邻域像素进行加权计算
out[pad + y, pad + x, c] = np.sum(K * tmp[y: y + K_size, x: x + K_size, c])
# 去除填充部分,恢复图像大小,并转换为无符号8位整型
out = out[pad: pad + H, pad: pad + W].astype(np.uint8)
return out
(3)效果展示
经过对比可以看出,处理后的图片相对于原图更模糊,且随着ksize
(卷积核的大小)的变大,高斯滤波后的图片将变得更加模糊。但不同于均值滤波器,这里的图片颜色不会变黑。
3.平滑空间滤波器的横向对比
均值滤波器 | 加权线性滤波器 | |
优点 | 1、实现简单 2、计算效率高,适合实时处理。 3、对于去除随机噪声非常有效。 | 1、可产生更自然、更平滑的模糊效果。 2、保留更多的图像结构和边缘信息。 3、减少了边缘模糊的程度 |
缺点 | 1、可能会导致图像边缘和细节的模糊。 2、不适合保留图像中的结构信息。 3、可能使图片颜色变黑或加深 | 对于边缘附近的噪声去除不如均值滤波器彻底。 |
应用场景 |
|
|
(二)统计排序滤波器
1.中值滤波器
(1)实现原理
中值滤波器将每个像素的值替换为其邻域内像素值的中位数。这种滤波器不依赖于平均值,因此能够去除噪声而不会模糊图像的边缘。
如下图所示,中值滤波器就是将原像素点及其邻域的像素值按顺序排列后,取中位数赋值给中心像素点。
(2)实现代码
# 导入需要用到的库函数
import cv2
import numpy as np
# 中值滤波器函数
def median_filter(image, ksize):
# 计算边界填充的大小
pad_size = ksize // 2
# 对图像进行边界填充
padded_image = np.pad(image, ((pad_size, pad_size), (pad_size, pad_size), (0, 0)), 'edge')
# 准备输出图像,初始化为零
median_filtered_image = np.zeros_like(image)
# 对于图像中的每个像素进行操作
for i in range(pad_size, padded_image.shape[0] - pad_size):
for j in range(pad_size, padded_image.shape[1] - pad_size):
# 获取当前窗口
window = padded_image[i-pad_size:i+pad_size+1, j-pad_size:j+pad_size+1]
# 计算窗口中所有像素的中值,并将其赋值给输出图像的当前像素位置
median_filtered_image[i-pad_size, j-pad_size] = np.median(window, axis=(0, 1))
return median_filtered_image
(3)效果展示
中值滤波器处理对于去除椒盐噪声效果明显,以下为使用中值滤波器的前后对比图:
需要注意的是,中值滤波函数中,参数ksize的选取会影响图像的质量!
设置不同的ksize,观察生成的图像效果:
让我们尝试一个极端的例子——将ksize直接调到100!
这个时候,虽然椒盐噪声的确被去除了,但明显可以看到图像变得非常模糊,这是因为ksize过大了,图像中有用的信息也被用中值替代了,导致误差逐渐显现了出来。
总结:增大ksize可以更有效地消除噪声,但是会使边界更模糊,因此对ksize的选择需要适当,过小会使得椒盐噪声消除效果不佳,较大会导致图像模糊,直接影响图片的质量。
2.最大值/最小值滤波器
(1)实现原理
-
最大值/最小值滤波器将每个像素替换为邻域内的最大值/最小值。最大值滤波器可以突出图像中的亮区域,最小值滤波器可以突出图像中的暗区域。
(2)实现代码
(这里以最大值滤波器为例)
import cv2
import numpy as np
# 最大值滤波器的实现
def max_filter(image, ksize):
# 计算边界填充的大小
pad_size = ksize // 2
# 对图像进行边界填充
padded_image = np.pad(image, ((pad_size, pad_size), (pad_size, pad_size), (0, 0)), 'edge')
# 准备输出图像,初始化为零
max_filtered_image = np.zeros_like(image)
# 对于图像中的每个像素进行处理
for i in range(pad_size, padded_image.shape[0] - pad_size):
for j in range(pad_size, padded_image.shape[1] - pad_size):
# 获取当前窗口
window = padded_image[i-pad_size:i+pad_size+1, j-pad_size:j+pad_size+1]
# 计算窗口中所有像素的最大值,并将其赋值给输出图像的当前像素位置
# 若要实现最小值滤波器,将np.max转换成np.min即可
max_filtered_image[i-pad_size, j-pad_size] = np.max(window, axis=(0, 1))
return max_filtered_image
(3)效果展示
对比图像可发现,因为最大值滤波器会选取每个窗口中最亮的像素,所以经过最大值滤波器处理后的图片会更突出亮区域,使得图像的对比度增加。同理可知,使用最小值滤波器会使得图像的暗区域被增强,而且可以去除亮噪点。
3.统计排序滤波器的横向对比:
中值滤波器 | 最大值/最小值滤波器 | |
优点 | 1、在去除椒盐噪声(随机出现的白点和黑点)方面非常有效。 2、能够保护图像边缘,因为它不会产生像素值的人为增减。 | 1、最大值滤波器可以用来增强图像中的亮区域。 |
缺点 | 1、对于大面积均匀区域的噪声去除不如均值滤波器彻底。 2、计算量比线性滤波器要大,因为涉及到排序操作。 | 1、会改变图像的总体亮度分布,可能会丢失大量信息。 2、可能会导致图像细节的损失。 |
应用场景 |
|
|
(三)锐化滤波器
1.拉普拉斯滤波器
(1)实现原理
拉普拉斯滤波器基于图像的二阶导数,用于突出图像的边缘。它的基本思想是当邻域的中心像素灰度低于它所在邻域内的其他像素的平均灰度时,此中心像素的灰度应该进一步降低;当高于时进一步提高中心像素的灰度,从而实现图像锐化处理。
在算法实现过程中,通过对邻域中心像素的四方向或八方向求梯度,并将梯度和相加来判断中心像素灰度与邻域内其他像素灰度的关系,并用梯度运算的结果对像素灰度进行调整。
一个连续的二元函数f(x,y),其拉普拉斯运算定义为:
(2)实现代码
# 拉普拉斯滤波器的实现
def laplacian(img, K_size=3):
# 如果图像通道数为3,则获取高度、宽度和通道数;否则,扩展维度并获取高度、宽度和通道数
if len(img.shape) == 3:
H, W, C = img.shape
else:
img = np.expand_dims(img, axis=-1)
H, W, C = img.shape
# Padding
# 计算填充大小
pad = K_size // 2
# 创建填充后的图像
out = np.zeros((H + pad*2, W + pad*2, C), dtype=float)
out[pad:pad+H, pad:pad+W] = img.copy().astype(float)
# Laplacian kernel
# 定义拉普拉斯核
K = [[0, 1, 0],
[1, -4, 1],
[0, 1, 0]]
tmp = out.copy()
# Filter
# 对每个像素进行滤波操作
for y in range(H):
for x in range(W):
for c in range(C):
# 使用拉普拉斯核对邻域像素进行滤波计算
out[pad+y, pad+x, c] = np.sum(K * tmp[y:y+K_size, x:x+K_size, c])
# 将输出结果限制在0到255之间
out = np.clip(out, 0, 255)
# 去除填充部分,恢复图像大小,并转换为无符号8位整型
out = out[pad:pad+H, pad:pad+W].astype(np.uint8)
return out
(3)效果展示
可以发现,经过拉普拉斯滤波器处理后的图片强调了图像中的边缘,但是在边缘不是特别明显的区域,图片的一些信息会被丢失。
值得注意的是,当我们将图片经过锐化处理后,再使用拉普拉斯滤波器处理,会发现图片的更多信息被保留下来,图片的边缘更加清晰。
当我们将图片经过圆滑处理后,再使用拉普拉斯滤波器处理,会发现图片的大量信息被丢失,图片的边缘几乎看不见。
拉普拉斯滤波器更倾向于检测到尖锐的边缘,更容易受到图像噪声的影响,这些特性和拉普拉斯滤波器的原理是密切相关的。
2.Sobel滤波器
(1)实现原理
Sobel滤波器是一种基于图像一阶导数的边缘检测器,它通过水平和垂直两个方向的核来分别响应水平和垂直边缘。
核心原理:通过计算图像像素点的一阶导数来识别边缘。Sobel滤波器包括两个互相垂直的卷积核(或滤波器):一个用于检测水平方向的边缘(垂直滤波器),另一个用于检测垂直方向的边缘(水平滤波器)。这些滤波器通过突出显示像素亮度的快速变化来识别边缘。
在实现过程中,水平和垂直滤波器分别应用于图像上。每个滤波器都会产生一个梯度图,这些梯度图分别代表图像在垂直和水平方向的边缘强度。最后,这两个梯度图可以结合起来计算出每个像素的边缘强度和方向,从而生成最终的边缘检测结果。Sobel滤波器因其简单高效而被广泛应用于各种图像分析任务中。
(2)实现代码
import cv2
import numpy as np
# sebel滤波器的实现
def sobel_filter(image):
# 定义Sobel滤波器核(水平和垂直方向)
sobel_x = np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]])
sobel_y = np.array([[-1, -2, -1],
[ 0, 0, 0],
[ 1, 2, 1]])
# 应用Sobel滤波器
sobel_filtered_image_x = cv2.filter2D(image, -1, sobel_x)
sobel_filtered_image_y = cv2.filter2D(image, -1, sobel_y)
# 计算最终的边缘强度图
sobel_filtered_image = np.sqrt(np.square(sobel_filtered_image_x) + np.square(sobel_filtered_image_y))
sobel_filtered_image = np.uint8(sobel_filtered_image)
return sobel_filtered_image
(3)效果展示
可以看出,相较于拉普拉斯滤波器,图片的更多细节被保留下来。由于叶片边缘较光滑,sobel滤波器处理后的效果优于拉普拉斯滤波器。
3.锐化滤波器的横向对比
拉普拉斯滤波器 | Sobel滤波器 | |
优点 | 1、能够同时增强图像中的亮边缘和暗边缘。 2、对边缘的定位精确。 3、结果对边缘的方向不敏感。 | 1、可以分别对图像的水平和垂直边缘进行增强。 |
缺点 | 1、由于增强了所有边缘,包括图像中的噪声,因此对噪声非常敏感。 2、可能会过度增强边缘,导致图像失真。 | 1、边缘增强是方向性的,可能会错过某些方向的边缘。 2、在边缘精度上不如拉普拉斯滤波器。 |
应用场景 |
|
|
综上可知:
拉普拉斯滤波器更倾向于检测到尖锐的边缘,而Sobel滤波器能更好地处理平滑的边缘。
拉普拉斯滤波器更容易受到图像噪声的影响,而Sobel滤波器对噪声有更好的抵抗力。
在选择滤波器时,要根据图像的特性和处理目标来决定。拉普拉斯滤波器在边缘响应上非常强烈,适用于边缘清晰、噪声较少的图像。而Sobel滤波器则因其对噪声的抵抗能力和计算效率在实际应用中更为广泛。