OpenCV与图像处理学习六——图像形态学操作:腐蚀、膨胀、开、闭运算、形态学梯度、顶帽和黑帽
- 四、图像形态学操作
- 4.1 腐蚀和膨胀
- 4.1.1 图像腐蚀
- 4.1.2 图像膨胀
- 4.2 开运算与闭运算
- 4.2.1 开运算
- 4.2.2 闭运算
- 4.3 形态学梯度(Gradient)
- 4.4顶帽和黑帽
四、图像形态学操作
形态学,是图像处理中应用最为广泛的技术之一,主要用于从图像中提取对表达和描绘区域形状有意义的图像分量,使后续的识别工作能够抓住目标对象最为本质的形状特征,如边界和连通区域等。
下面会经常用到一个概念,这里先进行说明:
结构元素:设有两幅图像B,X,若X是被处理的对象,而B是用来处理X的,则B称为结构元素(structure element),又被形象地称作刷子。结构元素通常都是一些比较小的图像。
下面将介绍形态学的几种常用操作:腐蚀、膨胀、开运算和闭运算等。
4.1 腐蚀和膨胀
图像的膨胀(Dilation)和腐蚀(Erosion)是两种基本的形态学运算,其中膨胀类似于“领域扩张”,将图像中的白色部分进行扩张,其运行结果图比原图的白色区域更大;而腐蚀类似于“领域被蚕食”,将图像中白色的部分进行缩减细化,其运行结果图比原图的白色区域更小。
4.1.1 图像腐蚀
把结构元素B平移a后得到Ba,若Ba包含于X,我们记下这个a点,所有满足上述条件的a点组成的集合称作X被B腐蚀(Erosion)的结果。如下图所示:
其中X是被处理的对象,B是结构元素。对于任意一个在阴影部分的点a,Ba包含于X,所以X被B腐蚀的结果就是那个阴影部分。阴影部分在X的范围之内,且比X小,就像X被剥掉了一层似的。
腐蚀后的结果如下图黑色部分所示:
相较于原来的灰色部分,仿佛变瘦了。
OpenCV中的函数为:
dst = cv2.erode( src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )
参数为:
- src:输入图像,可以是灰度图或彩色图。
- kernel:腐蚀操作的结构元素,默认为一个3×3的简单矩阵。
- anchor:锚点,默认为结构元素的中心。
- iterations:腐蚀次数,默认为1。
下面看个例子:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('./image/morphology.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
kernel = np.ones((3, 3), np.uint8)
erosion = cv2.erode(img, kernel, iterations = 1)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(erosion), plt.title('erosion')
plt.xticks([]), plt.yticks([])
plt.show()
结果如下所示:
若将结构元素的尺寸扩大到7,结果为:
ps:在构造结构元素的时候,可以使用numpy,也可以使用OpenCV提供的函数cv2.getStructuringElement()
函数:
retval = cv2.getStructuringElement( shape, ksize[, anchor] )
参数:
- shape:结构元素内部的结构,有三种,分别是矩形、十字形和椭圆形:
- ksize:结构元素的尺寸。
- anchor:锚点位置,默认为中心位置。
看一下例子:
import numpy as np
import cv2
kernel = np.ones((5, 5), np.uint8)
print(kernel)
[[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]]
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (7,7))
print(kernel)
[[0 0 0 1 0 0 0]
[0 0 0 1 0 0 0]
[0 0 0 1 0 0 0]
[1 1 1 1 1 1 1]
[0 0 0 1 0 0 0]
[0 0 0 1 0 0 0]
[0 0 0 1 0 0 0]]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7,7))
print(kernel)
[[0 0 0 1 0 0 0]
[0 1 1 1 1 1 0]
[1 1 1 1 1 1 1]
[1 1 1 1 1 1 1]
[1 1 1 1 1 1 1]
[0 1 1 1 1 1 0]
[0 0 0 1 0 0 0]]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
print(kernel)
[[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 1 1 1]
[1 1 1 1 1 1 1]
[1 1 1 1 1 1 1]
[1 1 1 1 1 1 1]]
我们可以用非矩形的结构元素来进行腐蚀操作:
#!/usr/bin/env python3
import cv2
image = cv2.imread("./image/morphology.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("Gray Image", gray)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))
eroded = cv2.erode(gray.copy(), kernel, 10)
# eroded = cv2.erode(gray.copy(), None, 10)
cv2.imshow("Eroded Image", eroded)
cv2.waitKey(0)
cv2.destroyAllWindows()
也是可以达到一定效果的,但是比矩形的那种腐蚀程度低一些些,因为毕竟结构元素里多了一些0。
4.1.2 图像膨胀
膨胀(dilation)可以看做是腐蚀的对偶运算,其定义是:把结构元素B平移后得到Ba,若Ba与X有交集,我们记下这个a点。所有满足上述条件的a点组成的集合称作X被B膨胀后的结果,如下图所示:
其中X是被处理的对象,B是结构元素,对于任意一个在阴影部分的点a,Ba与X有交集,所以X被B膨胀后的结果就是那个阴影部分,阴影部分包括X所有范围,就像是X膨胀了一圈似的。
膨胀后的图像,其中绿色是膨胀多出来的部分:
在OpenCV中的函数为:
dst = cv2.dilate( src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )
参数:
- src:输入图像,可以是灰度图也可以是彩色图。
- kernel:膨胀运算的结构元素,默认为一个3×3的简单矩阵。
- anchor:同上。
- iterations:同上。
看个例子:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('./image/morphology.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#kernel = np.ones((3,),np.uint8)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
dilation = cv2.dilate(img,kernel,iterations = 1)
plt.subplot(121),plt.imshow(img),plt.title('origin')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dilation),plt.title('dilation')
plt.xticks([]), plt.yticks([])
plt.show()
结果为:
若将运算元素尺寸扩大一点,扩大为11:
原本断开的地方或小孔都被填上了。
4.2 开运算与闭运算
4.2.1 开运算
开运算 = 先腐蚀运算,再膨胀运算,看上去把细微连在一起的两块目标分开了,开运算的效果图如下所示:
开运算对一些细微的小点,小块,细条等部分是可以消去的,因为先腐蚀消去它们,导致它们消失了无法再通过膨胀变回来,而一些比较大的块通过腐蚀操作只是会变瘦一点,不会被完全抹去,所以可以通过膨胀运算变回来,那么总的效果就是开运算去除了这些孤立的小点,细长的小条。
开运算总结:
- 开运算能够去除孤立的小点,毛刺和小条,而总的位置和形状不变。
- 开运算是一个基于几何运算的滤波器。
- 结构元素大小的不同将导致滤波效果的不同。
- 不同的结构元素的选择导致了不同的分割,即提取出不同的特征。
开运算和闭运算都用如下函数来表示,这个函数是OpenCV中图像形态学变化的通用函数:
dst = cv2.morphologyEx( src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )
参数如下所示:
- src:输入图像,灰度图或彩色图均可。
- op:形态学操作的类型,包括腐蚀、运算、开运算以及后面要提及的闭运算、形态学梯度、顶帽、黑帽等类型。
- kernel:结构元素,可以使用
cv2.getStructuringElement
函数来定义。 - anchor:锚点位置,一般都用中心位置。
- iterations:腐蚀或膨胀的次数。
下面看个例子:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('./image/open.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#kernel = np.ones((5,5),np.uint8)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(opening), plt.title('opening')
plt.xticks([]), plt.yticks([])
plt.show()
结果如下所示:
一些细小的点被去除了很多,但是开运算的结构元素的尺寸很重要,太小可能去除效果不好,太大可能会得到不想要的结果,如将3改为9,结果将变为:
所以调节这个参数还是很关键的。
4.2.2 闭运算
闭运算 = 先膨胀运算,再腐蚀运算,看上去将两个细微连接的图块封闭在一起,闭运算的效果图如下图所示:
闭运算总结:
- 闭运算能够填平小孔,弥合小缝隙,而总的位置和形状不变。
- 闭运算是通过填充图像的凹角来滤波图像的。
- 结构元素大小的不同将导致滤波效果的不同。
- 不同结构元素的选择导致了不同的分割。
看个例子:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img = cv.imread('./image/close.png')
img = cv.cvtColor(img,cv.COLOR_BGR2RGB)
# kernel = np.ones((5,5),np.uint8)
kernel = np.ones((7, 7), np.uint8)
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(closing), plt.title('closing')
plt.xticks([]), plt.yticks([])
plt.show()
结果如下所示:
一些小孔被填满了。若把尺寸从7改为21,结果为:
就有点过了,把不需要连接和填补的地方也给连接、填补了,所以要合理选择参数。
4.3 形态学梯度(Gradient)
- 基础梯度:基础梯度是用膨胀后的图像减去腐蚀后的图像得到的差值图像,也是OpenCV中支持的计算形态学梯度的方法,而此方法得到梯度又称为基本梯度。
- 内部梯度:是用原图减去腐蚀之后的图像得到的差值图像。
- 外部梯度:图形膨胀后再减去原来图像得到的差值图像。
用cv2.morphologyEx
函数可以实现基础梯度操作,看下面这个例子:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img = cv.imread('./image/morphology.png')
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
kernel = np.ones((3, 3), np.uint8)
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(gradient), plt.title('gradient')
plt.xticks([]), plt.yticks([])
plt.show()
结果如下所示:
4.4顶帽和黑帽
- 顶帽(Top Hat):原图像与开运算图的差值,突出原图像中比周围亮的区域。
- 黑帽(Black Hat):闭运算图与原图的差值,突出原图中比周围暗的区域。
看两个例子:
顶帽:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img = cv.imread('./image/morphology.png')
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
kernel = np.ones((9, 9), np.uint8)
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(tophat), plt.title('tophat')
plt.xticks([]), plt.yticks([])
plt.show()
结果为:
黑帽:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
img = cv.imread('./image/morphology.png')
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
kernel = np.ones((9, 9), np.uint8)
tophat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
plt.subplot(121), plt.imshow(img), plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(tophat), plt.title('blackhat')
plt.xticks([]), plt.yticks([])
plt.show()
图像处理之图像基本操作的笔记就暂时到这里,后面将学习传统方法进行图像分割,包括阈值分割、边缘检测算法、连通域分析以及一些其他区域生长算法。