目录
- 3.4 平滑化图像
- 目标
- 二维卷积 ( 图像滤波 )
- 图像模糊(图像平滑)
- 其他资源
- 3.5 形态学转换
- 目标
- 理论
- 结构化元素
- 3.6 图像梯度
- 目标
- 理论
- 代码
- 一个重要的问题!
翻译及二次校对:cvtutorials.com
编辑者:廿瓶鲸(和鲸社区Siby团队成员)
3.4 平滑化图像
目标
学习:
1.用各种低通滤波器模糊图像
2.在图像上应用定制的滤波器(二维卷积)
二维卷积 ( 图像滤波 )
与一维信号一样,图像也可以用各种低通滤波器(LPF)、高通滤波器(HPF)等进行过滤。LPF有助于去除噪音、模糊图像等。HPF滤波器有助于寻找图像的边缘。
OpenCV提供了一个函数cv.filter2D()来将一个核与图像进行融合。作为一个例子,我们将在一个图像上尝试一个平均滤波器。一个5x5的平均滤波核看起来就像下面这样。
K = 1 25 [ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ] K = \frac{1}{25} \begin{bmatrix} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix} K=251 1111111111111111111111111
这个操作是这样的:在一个像素上面保持这个核,把这个核下面的所有25个像素加起来,取平均值,然后用新的平均值替换中心像素。图像中的所有像素都施加这个操作。试试这段代码并检查结果。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('opencv_logo.png')
kernel = np.ones((5,5),np.float32)/25
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()
结果如下:
图像模糊(图像平滑)
图像模糊是通过用低通滤波器核对图像进行卷积实现的。它对去除噪音很有用。它实际上是从图像中去除高频内容(如:噪声、边缘)。因此,在这个操作中,边缘会被模糊一些(也有一些模糊技术是不模糊边缘的)。OpenCV提供了四种主要的模糊技术。
1.均值模糊
这是通过用一个归一化的盒式滤波器对图像进行卷积来完成的。它只是取核区下所有像素的平均值,并替换中心元素。这是由函数cv.blur()或cv.boxFilter()完成的。查看文档以了解关于核的更多细节。我们应该指定核的宽度和高度。一个3x3的归一化盒式滤波器看起来就像下面这样。
K = 1 9 [ 1 1 1 1 1 1 1 1 1 ] K = \frac{1}{9} \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix} K=91 111111111
注意:如果你不想使用规范化的盒子过滤器,请使用cv.boxFilter()。向该函数传递一个参数normalize=False。
请看下面的样本演示,核大小为5x5。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('opencv-logo-white.png')
blur = cv.blur(img,(5,5))
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()
结果如下:
2.高斯模糊
在这个方法中,使用了高斯核而不是盒式滤波器。它是通过函数cv.GaussianBlur()完成的。我们应该指定核的宽度和高度,应该是正数和奇数。我们还应该指定X和Y方向的标准偏差,分别为sigmaX和sigmaY。如果只指定sigmaX,sigmaY将被视为与sigmaX相同。如果两者都是零,则根据核大小计算。高斯模糊对去除图像中的高斯噪声非常有效。
如果你愿意,你可以用函数cv.getGaussianKernel()创建一个高斯核。
上面的代码可以为高斯模糊进行修改。
blur = cv.GaussianBlur(img,(5,5),0)
结果如下:
3.中值模糊
在这里,函数cv.medianBlur()取核区下所有像素的中值,中心元素被替换成这个中值。这对图像中的椒盐噪声非常有效。有趣的是,在上述过滤器中,中心元素是一个新的计算值,可能是图像中的一个像素值或一个新值。但在中值模糊中,中心元素总是被图像中的某个像素值所取代。它能有效地减少噪音。它的核大小应该是一个正奇数的整数。
在这个演示中,我给我们的原始图像添加了50%的噪声,并应用中值模糊。检查一下结果。
median = cv.medianBlur(img,5)
结果:
4.双边滤波
cv.bilateralFilter()在去除噪声的同时保持边缘的清晰度方面非常有效。但与其他过滤器相比,其操作速度较慢。我们已经看到,高斯滤波器取像素周围的邻域并找到其高斯加权平均值。这个高斯滤波器是一个单独的空间函数,也就是说,在过滤时考虑附近的像素。它不考虑像素是否有几乎相同的灰度。它不考虑一个像素是否是一个边缘像素。因此,它也模糊了边缘,这是我们不希望看到的。
双边滤波也需要一个空间的高斯滤波,但多了一个高斯滤波,这是一个像素差异的函数。空间的高斯函数确保只有附近的像素被考虑用于模糊处理,而灰度差的高斯函数则确保只有那些与中心像素灰度相似的像素被考虑用于模糊处理。所以它保留了边缘,因为边缘的像素会有很大的灰度变化。
下面的例子显示了双边滤波器的使用(关于参数的细节,请访问文档)。
blur = cv.bilateralFilter(img,9,75,75)
结果:
看,表面的纹理已经消失了,但边缘仍然保存着。
其他资源
- 关于双边过滤的细节
3.5 形态学转换
目标
在本章中:
- 我们将学习不同的形态学操作,如腐蚀、膨胀、开运算、闭运算等。
- 我们将看到不同的函数,如:cv.erode(), cv.dilate(), cv.morphologyEx() 等。
理论
形态学变换是基于图像形状的一些简单操作。它通常是在二进制图像上进行的。它需要两个输入,一个是我们的原始图像,第二个被称为结构化元素或核,它决定了操作的性质。两个基本的形态学运算符是腐蚀和膨胀。然后,它的变体形式,如开运算、闭运算、梯度等也开始发挥作用。我们将在以下图片的帮助下逐一看到它们。
1.腐蚀
腐蚀的基本概念就像土壤腐蚀一样,只是它腐蚀了前景物体的边界(总是尽量保持前景为白色)。那么它是怎么做的呢?核在图像中滑动(如二维卷积)。原始图像中的一个像素(1或0)只有在核下的所有像素都是1时才会被认为是1,否则就会被腐蚀(变成0)。
因此,发生的情况是,根据核的大小,所有靠近边界的像素将被丢弃。因此,前景物体的厚度或大小减少了,或者说是图像中的白色区域减少了。这对于去除小的白色噪音(正如我们在色彩空间一章中所看到的),分离两个相连的物体等很有用。
这里,作为一个例子,我将使用一个充满1的5x5核。让我们看看它是如何工作的。
import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv.erode(img,kernel,iterations = 1)
结果如下:
2.膨胀
它与腐蚀刚好相反。这里,一个像素元素是 “1”,如果核心下至少有一个像素是 “1”。因此,它增加了图像中的白色区域或增加了前景物体的大小。通常情况下,在去除噪声等情况下,腐蚀之后是膨胀。因为,腐蚀可以去除白色噪音,但它也会缩小我们的物体。所以我们要膨胀它。由于噪音已经消失,它们不会再出现,但我们的对象面积会增加。它在连接一个物体的破碎部分时也很有用。
dilation = cv.dilate(img,kernel,iterations = 1)
结果如下:
3.开运算
开运算只是腐蚀和膨胀的另一个名称。正如我们上面解释的那样,它在去除噪音方面很有用。在这里,我们使用函数cv.morphologyEx()
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
结果如下:
4.闭运算
闭运算是开运算的反面,膨胀之后是腐蚀。它在关闭前景物体内部的小孔,或物体上的小黑点时很有用。
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
结果如下:
5.形态学梯度
它是图像的膨胀和腐蚀之间的区别。
其结果将看起来像物体的轮廓。
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
结果如下:
6.高帽
它是输入图像和图像开运算之间的差异。下面的例子是针对一个9x9的核做的。
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
结果如下:
7.黑帽
它是指输入图像和输入图像的闭运算度之差。
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
结果如下:
结构化元素
在前面的例子中,我们在Numpy的帮助下手动创建了一个结构化元素:矩阵。但在某些情况下,你可能需要椭圆/圆形的核。因此,为了这个目的,OpenCV有一个函数,cv.getStructuringElement()。你可以通过核的形状和大小去定义一个核。
# Rectangular Kernel
>>> cv.getStructuringElement(cv.MORPH_RECT,(5,5))
array([[1, 1, 1, 1, 1],[1, 1, 1, 1, 1],[1, 1, 1, 1, 1],[1, 1, 1, 1, 1],[1, 1, 1, 1, 1]], dtype=uint8)
# Elliptical Kernel
>>> cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
… [0, 0, 1, 0, 0]], dtype=uint8)
其他资源
- HIPR2的形态学操作
3.6 图像梯度
目标
在本章中,我们将学习:
- 寻找图像梯度和边缘等
- 我们将看到以下函数:cv.Sobel()、cv.Scharr()、cv.Laplacian()等。
理论
OpenCV提供了三种类型的梯度滤波器或高通滤波器,Sobel, Scharr和Laplacian。
1.Sobel和Scharr梯度
Sobel运算符是一种高斯平滑加微分的联合运算,所以它对噪声的抵抗力更强。你可以指定要取的导数的方向,垂直或水平(分别通过参数yorder和xorder)。你还可以通过参数ksize指定核的大小。如果ksize = -1,则使用3x3 Scharr滤波器,它比3x3 Sobel滤波器的结果更好。请看文档中核的用法。
2.拉普拉斯导数
它计算由以下关系式给出的图像拉普拉斯系数。结果如下:
Δ s r c = ∂ 2 s r c ∂ x 2 + ∂ 2 s r c ∂ y 2 \Delta src = \frac{\partial ^2{src}}{\partial x^2} + \frac{\partial ^2{src}}{\partial y^2} Δsrc=∂x2∂2src+∂y2∂2src
其中每个导数都是用Sobel导数找到的。如果ksize=1,则使用以下核进行过滤。
k e r n e l = [ 0 1 0 1 − 4 1 0 1 0 ] kernel = \begin{bmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{bmatrix} kernel= 0101−41010
代码
下面的代码在一个图中显示了所有的运算符。所有核都是5x5大小。输出图像的深度是通过-1来获得np.uint8类型的结果。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('dave.jpg',0)
laplacian = cv.Laplacian(img,cv.CV_64F)
sobelx = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
sobely = cv.Sobel(img,cv.CV_64F,0,1,ksize=5)
plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2),plt.imshow(laplacian,cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,3),plt.imshow(sobelx,cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,4),plt.imshow(sobely,cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
plt.show()
结果如下:
一个重要的问题!
在我们最后一个例子中,输出数据类型是cv.CV_8U或np.uint8。但是这里面有一个小问题。黑到白的过渡被认为是正斜率(它有一个正值),而白到黑的过渡被认为是负斜率(它有负值)。所以当你把数据转换成np.uint8时,所有的负斜率都变成了0。简单地说,你会丢失这个边缘。
如果你想检测两个边缘,更好的选择是将输出数据类型保持为更高的形式,如cv.CV_16S,cv.CV_64F等,取其绝对值,然后转换回cv.CV_8U。下面的代码演示了水平Sobel滤波器的这个过程和结果的差异。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('box.png',0)
# Output dtype = cv.CV_8U
sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=5)
# Output dtype = cv.CV_64F. Then take its absolute and convert to cv.CV_8U
sobelx64f = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)
plt.subplot(1,3,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,2),plt.imshow(sobelx8u,cmap = 'gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,3),plt.imshow(sobel_8u,cmap = 'gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])
plt.show()
结果如下: