从opencv-python入门opencv--图像处理之图像滤波
- 一、文章介绍
- 二、滤波器原理简述
- [三、 cv.filter2D()](#三、 cv.filter2D())
- [四、 cv.blur()](#四、 cv.blur())
- 五、cv.boxFilter()
- [六、 cv.GaussianBlur()和cv.bilateralFilter()](#六、 cv.GaussianBlur()和cv.bilateralFilter())
- 七、cv.medianBlur()
一、文章介绍
本文主要介绍经典的图像滤波器,包括均值滤波、中值滤波、高斯滤波和双边滤波。
涉及的opencv-python函数有cv.filter2D()、 cv.boxFilter()、cv.GaussianBlur()、cv.blur()和cv.bilateralFilter等。
opencv中各个滤波器效果如下:
二、滤波器原理简述
在百度百科中,图像滤波的定义如下:
图像滤波,即在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。
跟一维滤波相同,图像也可以设计低通滤波器、高通滤波器进行边缘查找以及噪声消除等。使用的滤波参数为二维的卷积核。
图像处理中,常见的滤波器有均值滤波、中值滤波和高斯滤波等。
在图像中,低频的部分是连续的平滑结构,高频部分是边缘等突兀的细节。使用均值、中值、高斯滤波主要是为了去除噪声,噪声属于高频部分,因此,这些滤波器本质上属于低通滤波器。而如果我们想要得到图像的边缘,使用一些边缘梯度算子,如果sobel算子、拉普拉斯算子等,这些本质上属于高通滤波器。
卷积原理:
使用一个矩阵(假设为 3 ∗ 3 3*3 3∗3),如下图所示,扫过一张 5 ∗ 5 5*5 5∗5的图像, 3 ∗ 3 3*3 3∗3的矩阵和扫过的图像 3 ∗ 3 3*3 3∗3的区域每一个像素点对应相乘再相加,得到一个新的像素点的值,为了防止卷积后图像变小,卷积操作都会在边缘补0,比如3*3的卷积核,在图像周围补1个0像素,对于 k ∗ k k*k k∗k大小的卷积核,图像补零个数的公式为:
p = k − 1 2 p=\frac{k-1}{2} p=2k−1
其中,p为四周补零的个数,k为卷积核的尺寸。
三、 cv.filter2D()
1、参数说明
(1)src
意义:输入图像,可以是单通道或多通道的图像。
类型:numpy.ndarray
(2)ddepth
意义:输出图像的深度(数据类型)。可以是以下值之一:
cv.CV_8U:无符号8位整型
cv.CV_16S:有符号16位整型
cv.CV_32F:32位浮点型
cv.CV_64F:64位浮点型
-1:与输入图像相同的深度
类型:整数
(3)kernel
意义:卷积核(滤波器),是一个二维数组,定义了如何对图像进行卷积操作。核的大小通常是奇数(如3x3、5x5等),并且可以是任意形状。
类型:numpy.ndarray
(4)anchor
意义:卷积核的锚点,指定卷积核的中心位置。默认值为 (-1, -1),表示卷积核的中心点是其几何中心。如果你想要改变锚点,可以设置为具体的坐标。
类型:元组或 None
(5)delta
意义:在卷积结果上添加的值。可以用于调整输出图像的亮度。例如,如果你想在每个像素值上加一个常数,可以通过这个参数实现。
类型:标量(数值)
(6)borderType
意义:边界类型,用于处理图像边界。当卷积核超出图像边界时,如何处理这些区域。常用的边界类型包括:
cv.BORDER_CONSTANT:用常数填充边界
cv.BORDER_REPLICATE:复制边界像素
cv.BORDER_REFLECT:反射边界像素
cv.BORDER_REFLECT_101:反射边界像素(不包括最外层)
cv.BORDER_WRAP:循环边界
cv.BORDER_DEFAULT:默认边界类型
类型:整数
(7)dst
意义:输出图像,经过卷积操作后生成的图像。输出图像的尺寸和深度根据输入图像和卷积核的大小以及指定的边界类型而定。
类型:numpy.ndarray
2、公式
d s t ( x , y ) = ∑ 0 < = x ′ < k e r n e l . c o l s ; 0 < = y ′ < k e r n e l . r o w s k e r n e l ( x ′ , y ′ ) ∗ s r c ( x + x ′ − a n c h o r . x , y + y ′ − a n c h o r . y ) dst(x,y)=\sum_{0<=x^{'}<kernel.cols;0<=y^{'}<kernel.rows}kernel(x^{'},y^{'})*src(x+x^{'}-anchor.x,y+y^{'}-anchor.y) dst(x,y)=0<=x′<kernel.cols;0<=y′<kernel.rows∑kernel(x′,y′)∗src(x+x′−anchor.x,y+y′−anchor.y)
其中, k e r n e l ( x ′ , y ′ ) kernel(x^{'},y^{'}) kernel(x′,y′)表示卷积核的系数, s r c src src表示原图像, a n c h o r anchor anchor表示锚点的坐标。
3、代码
设计如下滤波核:
1 9 [ 1 1 1 1 1 1 1 1 1 ] (3) \frac{1}{9} \left[ \begin{matrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{matrix} \right] \tag{3} 91 111111111 (3)
相当于 3 ∗ 3 3*3 3∗3的均值滤波。
代码如下:
python
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('data\squirrel_cls_noise.png')
kernel = np.ones((3,3),np.float32)
dst = cv.filter2D(img,-1,kernel)
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.show()
4、效果
四、 cv.blur()
1、参数详解
(1)src
意义:输入图像,可以是单通道或多通道的图像。
类型:numpy.ndarray
(2)ksize
意义:模糊的内核大小,通常是一个元组 (宽度, 高度),表示模糊区域的大小。内核大小必须是正奇数(例如:(3, 3)、(5, 5) 等)。
类型:元组或整数
注意:如果传入一个整数 k,则会被解释为 (k, k),即宽度和高度相同。
(3)anchor
意义:内核的锚点,指定模糊内核的中心位置。默认值为 (-1, -1),表示内核的中心是其几何中心。如果你想要改变锚点,可以设置为具体的坐标。
类型:元组或 None
(4)borderType
意义:边界类型,用于处理图像边界。当模糊内核超出图像边界时,如何处理这些区域。常用的边界类型包括:
cv.BORDER_CONSTANT:用常数填充边界
cv.BORDER_REPLICATE:复制边界像素
cv.BORDER_REFLECT:反射边界像素
cv.BORDER_REFLECT_101:反射边界像素(不包括最外层)
cv.BORDER_WRAP:循环边界
cv.BORDER_DEFAULT:默认边界类型
类型:整数
(5)输出参数dst
意义:输出图像,经过模糊处理后的结果。
类型:numpy.ndarray
2、公式
k = 1 k s i z e . w i d t h ∗ k s i z e . h e i g h t [ 1 1 1 . . . 1 1 1 1 . . . 1 . . . . . . . . . . . . 1 1 1 1 . . . 1 ] (3) k=\frac{1}{ksize.width*ksize.height} \left[ \begin{matrix} 1 & 1 & 1 &...&1\\ 1 & 1 & 1 &...&1\\ ... & ... & ...&...&1\\ 1 & 1 & 1&...&1 \end{matrix} \right] \tag{3} k=ksize.width∗ksize.height1 11...111...111...1............1111 (3)
3、代码
python
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('data\squirrel_cls_noise.png')
blur = cv.blur(img,(3,3))
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()
五、cv.boxFilter()
cv.boxFilter()也是用于均值滤波的函数,相比于cv.blur(),其 可以处理不同的深度,并且可以选择是否归一化输出。
1、参数
(1)src: 输入图像,类型为 numpy.ndarray。
(2)ddepth: 输出图像的深度。可以是 cv.CV_8U、cv.CV_32F 等。如果设置为 -1,则输出图像的深度与输入图像相同。
(3)ksize: 模糊内核的大小,必须是正奇数,例如 (3, 3)、(5, 5) 等。
(4)anchor: 内核的锚点,默认值为 (-1, -1),表示内核的中心。
(5)normalize: 布尔值,指示是否对输出进行归一化。默认值为 True,表示对内核进行归一化处理。
(6)borderType: 边界处理方式,可以选择不同的边界类型,如 cv.BORDER_CONSTANT、cv.BORDER_REPLICATE 等。
2、公式
k = α [ 1 1 1 . . . 1 1 1 1 . . . 1 . . . . . . . . . . . . 1 1 1 1 . . . 1 ] k=\alpha \left[ \begin{matrix} 1 & 1 & 1 &...&1\\ 1 & 1 & 1 &...&1\\ ... & ... & ...&...&1\\ 1 & 1 & 1&...&1 \end{matrix} \right] k=α 11...111...111...1............1111
α = { 1 k s i z e . w i d t h ∗ k s i z e . h e i g h t , w h e n n o r m a l i z e = t r u e 1 , o t h e r w i s e \alpha=\begin{cases} \frac{1}{ksize.width*ksize.height}, & when normalize=true\\ 1, & otherwise \end{cases} α={ksize.width∗ksize.height1,1,whennormalize=trueotherwise
3、代码
python
import cv2 as cv
import numpy as np
# 读取图像
src = cv.imread('data\squirrel_cls_noise.png')
# 检查图像是否成功加载
if src is None:
print("Error: Unable to load image.")
exit()
# 定义内核大小
ksize = (5, 5) # 5x5的模糊内核
# 使用 boxFilter 进行均值滤波
dst = cv.boxFilter(src, ddepth=-1, ksize=ksize, normalize=True)
# 显示原图和滤波后的图像
cv.imshow('Original Image', src)
cv.imshow('Box Filtered Image', dst)
# 等待按键并关闭窗口
cv.waitKey(0)
cv.destroyAllWindows()
六、 cv.GaussianBlur()和cv.bilateralFilter()
高斯滤波卷积核系数采用高斯函数进行计算。其公式将在双边滤波器中一起介绍。
1、cv.GaussianBlur()参数详解
(1)src: 输入图像,类型为 numpy.ndarray,可以是灰度图像或彩色图像。
(2)ksize: 高斯内核的大小。它是一个元组 (width, height),且必须是正奇数(例如 (3, 3)、(5, 5) 等)。如果设置为 (0, 0),则内核的大小会根据 sigmaX 和 sigmaY 自动计算。
(3)sigmaX: 高斯核在 X 方向上的标准差。它控制模糊的程度,值越大,模糊效果越强。如果设置为 0,OpenCV 会根据内核大小自动计算。
(4)sigmaY: 高斯核在 Y 方向上的标准差。默认值为 0,表示与 sigmaX 相同。如果需要在 X 和 Y 方向上使用不同的模糊程度,可以单独设置。
(5)borderType: 边界处理方式,控制当图像边界被访问时的行为。常用的边界类型包括:
cv.BORDER_CONSTANT: 边界外部用常量填充。
cv.BORDER_REPLICATE: 边界外部用边界像素复制。
cv.BORDER_REFLECT: 边界外部用镜像反射。
cv.BORDER_DEFAULT: 默认边界模式。
2、cv.bilateralFilter()参数详解
(1)src: 输入图像,类型为 numpy.ndarray,可以是灰度图像或彩色图像。
(2)d: 邻域直径,表示滤波时考虑的像素邻域的大小。它是一个正整数,表示在进行双边滤波时,计算每个像素的邻域大小。如果设置为 0,OpenCV 会根据 sigmaSpace 自动计算。
(3)sigmaColor: 颜色空间的标准差。它控制颜色相似性,值越大,表示在颜色上允许的差异越大,滤波效果越强。较大的值会导致更多的颜色被平滑。
(4)sigmaSpace: 坐标空间的标准差。它控制空间相似性,值越大,表示在空间上允许的距离越大,滤波效果越强。较大的值会导致更远的像素也参与计算。
(5)borderType: 边界处理方式,控制当图像边界被访问时的行为。常用的边界类型包括:
cv.BORDER_CONSTANT: 边界外部用常量填充。
cv.BORDER_REPLICATE: 边界外部用边界像素复制。
cv.BORDER_REFLECT: 边界外部用镜像反射。
cv.BORDER_DEFAULT: 默认边界模式。
3、双边滤波原理简述
图像处理中,滤波是必不可少,常见的滤波有均值,中值和高斯,这三种滤波算法,都是各项同性滤波算法,即每一个像素点,都采用相同的滤波属性,结构(边缘等细节)和非结构(平滑区域)操作是完全一样的,在去噪的同时,图像细节信息会丢失,造成图像模糊。
双边滤波器(bilateral filter)在边缘和平滑区域的处理是不一样的。顾名思义,双边,就是滤波系数考虑两种因素,一边是空间属性,一边是像素属性,即两个权重,一个权重根据邻域距离计算,另一个权重根据邻域像素值确定,最终两个权重相乘为一个权重。
公式如下:
对于位置为 ( i , j ) (i,j) (i,j)的像素 i m g ( i , j ) img(i,j) img(i,j),其邻域为 N N N,邻域的像素坐标记为 ( k , l ) (k,l) (k,l),邻域的像素值记为 i m g N ( k , l ) img_N(k,l) imgN(k,l),滤波结果为 i m g b ( i , j ) img_b(i,j) imgb(i,j),有如下公式:
i m g b ( i , j ) = ∑ k , j N i m g ( k , l ) ∗ w i , j ( k , l ) ∑ k , j N w i , j ( k , l ) img_b(i,j)=\frac{\sum_{k,j}^Nimg(k,l)*w_{i,j}(k,l)}{\sum_{k,j}^Nw_{i,j}(k,l)} imgb(i,j)=∑k,jNwi,j(k,l)∑k,jNimg(k,l)∗wi,j(k,l)
其中, w i , j ( k , l ) w_{i,j}(k,l) wi,j(k,l)为点 ( i , j ) (i,j) (i,j)在邻域 ( k , j ) (k,j) (k,j)处的滤波权重,记空间一边的滤波权重为 w i , j 1 ( k , l ) w^1_{i,j}(k,l) wi,j1(k,l),像素一边的滤波权重为 w i , j 2 ( k , l ) w^2_{i,j}(k,l) wi,j2(k,l),有如下公式:
w i , j 1 ( k , l ) = e − ( ( i − k ) 2 + ( j − l ) 2 2 σ d 2 ) w^1_{i,j}(k,l)=e^{-(\frac{(i-k)^2+(j-l)^2}{2{\sigma_d}^2})} wi,j1(k,l)=e−(2σd2(i−k)2+(j−l)2)
w i , j 2 ( k , l ) = e − ( ∣ ∣ i m g ( i , j ) − i m g N ( k , l ) ∣ ∣ 2 2 σ p 2 ) w^2_{i,j}(k,l)=e^{-(\frac{||img(i,j)-img_N(k,l)||^2}{2{\sigma_p}^2})} wi,j2(k,l)=e−(2σp2∣∣img(i,j)−imgN(k,l)∣∣2)
w i , j ( k , l ) = w i , j 1 ( k , l ) ∗ w i , j 2 ( k , l ) w_{i,j}(k,l)=w^1_{i,j}(k,l)*w^2_{i,j}(k,l) wi,j(k,l)=wi,j1(k,l)∗wi,j2(k,l)
其中, σ d \sigma_d σd和 σ p \sigma_p σp表示标准差,越大表示影响越大。
从公式中可以看出,在空间一边中,权重计算与高斯滤波一样,使用欧式距离,离中心点越近的点,其权重系数越大。在像素值一边中,使用当前中心的像素值与邻域像素值作差,灰度值越接近中心点灰度值的点,其与中心点构成边缘或细节的可能性越小,其权重应更大,灰度值相差大的点权重应小。
假设有一个3*3的邻域如下:
255 | 255 | 255 |
---|---|---|
50 | 50 | 50 |
50 | 50 | 50 |
对于灰度值为50中心点,对其进行滤波, w 1 w^1 w1与高斯滤波一样, w 2 w^2 w2的计算体现了边缘该权重几乎为0的特性,与 w 1 w^1 w1相乘后最终权重也几乎为0。
假设 σ p \sigma_p σp=30,255的灰度对其权重贡献为 e − 23.347 ≈ 1.016 × 1 0 − 10 e^ {−23.347} ≈1.016×10^ {−10} e−23.347≈1.016×10−10,几乎为0,50对其的权重贡献为1。如此,255的边缘对该点的贡献都近似为0,该点最终的值为5个50对其的高斯平均,如此,边缘区域像素值几乎不会有太大的改变,因此被很好的保留了下来。
4、高斯滤波和双边滤波代码及效果对比
代码:
python
import cv2 as cv
import numpy as np
# 读取图像
src = cv.imread('data\squirrel_cls_noise.png')
# 检查图像是否成功加载
if src is None:
print("Error: Unable to load image.")
exit()
# 定义高斯滤波参数
gaussian_ksize = (5, 5) # 高斯内核大小
gaussian_sigmaX = 1.5 # X方向的标准差
# 进行高斯滤波
gaussian_filtered = cv.GaussianBlur(src, gaussian_ksize, gaussian_sigmaX)
# 定义双边滤波参数
bilateral_d = 9 # 邻域直径
bilateral_sigmaColor = 75 # 颜色标准差
bilateral_sigmaSpace = 75 # 空间标准差
# 进行双边滤波
bilateral_filtered = cv.bilateralFilter(src, bilateral_d, bilateral_sigmaColor, bilateral_sigmaSpace)
# 创建一个窗口显示结果
result = np.hstack((src, gaussian_filtered, bilateral_filtered)) # 水平拼接图像
# 添加标题和说明
cv.putText(result, 'Original Image', (10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv.LINE_AA)
cv.putText(result, 'Gaussian Filtered', (src.shape[1] + 10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv.LINE_AA)
cv.putText(result, 'Bilateral Filtered', (2 * src.shape[1] + 10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv.LINE_AA)
# 显示结果
cv.imshow('Image Processing Results', result)
# 等待按键并关闭窗口
cv.waitKey(0)
cv.destroyAllWindows()
七、cv.medianBlur()
cv.medianBlur()是opencv的中值滤波函数,中值滤波取内核区域下所有像素的中值,将中央元素替换为该中值。中值滤波对于去除椒盐噪声非常的有效。
1、参数详解
(1)src
类型: numpy.ndarray
说明: 输入图像,必须是单通道或三通道的图像(如灰度图或彩色图)。图像的数据类型通常为 uint8。
要求: 输入图像的尺寸必须大于内核大小(ksize)。
(2)ksize
类型: int
说明: 中值滤波器的内核大小。它定义了在计算中值时考虑的邻域的大小。
要求: 必须是一个正奇数(例如 3, 5, 7 等)。如果指定的内核大小是偶数,OpenCV 会自动将其增加到下一个奇数。
2、代码及效果
该代码读取一张图像,添加椒盐噪声,然后使用中值滤波去除,结果显示原始图像、噪声图像以及滤波结果图
代码:
python
import cv2 as cv
import numpy as np
def add_salt_and_pepper_noise(image, salt_prob, pepper_prob):
noisy = np.copy(image)
total_pixels = image.size
# 添加盐噪声
num_salt = np.ceil(salt_prob * total_pixels)
coords = [np.random.randint(0, i - 1, int(num_salt)) for i in image.shape]
noisy[coords[0], coords[1], :] = 1 # 白色盐噪声
# 添加椒噪声
num_pepper = np.ceil(pepper_prob * total_pixels)
coords = [np.random.randint(0, i - 1, int(num_pepper)) for i in image.shape]
noisy[coords[0], coords[1], :] = 0 # 黑色椒噪声
return noisy
# 读取图像
src = cv.imread('data\pca_test1.jpg')
# 检查图像是否成功加载
if src is None:
print("Error: Unable to load image.")
exit()
# 添加椒盐噪声
salt_prob = 0.02 # 盐噪声概率
pepper_prob = 0.02 # 椒噪声概率
noisy_image = add_salt_and_pepper_noise(src, salt_prob, pepper_prob)
# 使用中值滤波去除噪声
denoised_image = cv.medianBlur(noisy_image, 5)
# 创建一个窗口显示结果
result = np.hstack((src, noisy_image, denoised_image)) # 水平拼接图像
# 添加标题和说明
cv.putText(result, 'Original Image', (10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv.LINE_AA)
cv.putText(result, 'Salt & Pepper Noise', (src.shape[1] + 10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv.LINE_AA)
cv.putText(result, 'Denoised Image', (2 * src.shape[1] + 10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv.LINE_AA)
# 显示结果
cv.imshow('Image Processing Results', result)
# 等待按键并关闭窗口
cv.waitKey(0)
cv.destroyAllWindows()
效果: