一、平滑图像
目标:
本章节你需要学习以下内容:
*使用各种低通滤波器模糊图像
*将自定义滤波器应用于图像(2D卷积)
1、2D卷积(图像过滤)
与一维信号一样,图像也可以使用各种低通滤波器(LPF),高通滤波器(HPF)等进行滤波。
LPF有助于消除噪声,模糊图像等。
HPF滤波器有助于找到图片的边缘。
OpenCV提供了一个函数cv.filter2D()来将卷积核与图像进行卷积。
例如,我们将尝试对图像进行平均滤波。下面是一个5x5平均滤波器的核:
$$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}$$
操作步骤如下:将此核放在一个像素A上,求与核对应的图像上 25(5x5)个像素的和,取其平均值并用新的平均值替换像素A的值。
重复以上操作直到将图像的每一个像素值都更新一遍。试试这段代码并检查结果:
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()
窗口将如下图显示:
2、图像模糊(图像平滑)
通过将图像与低通滤波器卷积核卷积来实现平滑图像。它有助于消除噪音,从图像中去除了高频内容(例如:噪声,边缘)。
因此在此操作中边缘会模糊一点。(有的平滑技术也不会平滑边缘)。OpenCV主要提供四种平滑技术。
(1)平均
这是由一个归一化卷积框完成的。它取卷积核区域下所有像素的平均值并替换中心元素。这是由函数cv.blur()或cv.boxFilter()完成的。查看文档以获取有关卷积核的更多详细信息。我们应该指定卷积核的宽度和高度,3x3标准化的盒式过滤器如下所示:
$$K=frac{1}{9}begin{bmatrix} 1 1 1 1 1 1 1 1 1 end{bmatrix}$$
注意:如果不想使用规范化的框过滤器,请使用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()在降低噪音方面非常有效,同时保持边缘清晰。但与其他过滤器相比,操作速度较慢。
我们已经看到高斯滤波器采用像素周围的邻域并找到其高斯加权平均值。
该高斯滤波器仅是空间的函数,即在滤波时考虑附近的像素。
它没有考虑像素是否具有几乎相同的强度。它不考虑像素是否是边缘像素。所以它也模糊了边缘,我们不想这样做。
双边滤波器在空间中也采用高斯滤波器,但是还有一个高斯滤波器是像素差的函数。
空间的高斯函数确保仅考虑附近的像素用于模糊,而强度差的高斯函数确保仅考虑具有与中心像素相似的强度的像素用于模糊。
因此它保留了边缘,因为边缘处的像素将具有较大的强度变化。
下面的示例显示使用双边过滤器(有关参数的详细信息,请访问docs)。
blur = cv.bilateralFilter(img,9,75,75)
窗口将如下图显示:
二、形态学转换
目标:
本章节你需要学习以下内容:
*你将学习不同的形态学操作,如侵蚀,膨胀,开放,关闭等。
*你将看到不同的函数,如:cv.erode(),cv.dilate(),cv.morphologyEx()等。
理论
形态学转换是基于图像形状的一些简单操作。
它通常在二进制图像上执行。它需要两个输入参数,一个是我们的原始图像,第二个是称为结构元素或卷积核,它决定了操作的性质。
腐蚀和膨胀是两个基本的形态学运算符。
然后它的变体形式如开运算,闭运算,梯度等也发挥作用。我们将在以下图片的帮助下逐一看到它们:
1、腐蚀
腐蚀的基本思想就像土壤侵蚀一样,它会腐蚀前景物体的边界(总是试图保持前景为白色)。
它是如何做到的呢?
卷积核在图像中滑动(如在2D卷积中),只有当卷积核下的所有像素都是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)
窗口将如下图显示:
8、结构元素
我们在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], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0]], dtype=uint8) # Cross-shaped Kerne l>>> cv.getStructuringElement(cv.MORPH_CROSS,(5,5)) array([[0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]], dtype=uint8)