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)的结果。如下图所示:

opencv去噪算法对比_python


其中X是被处理的对象,B是结构元素。对于任意一个在阴影部分的点a,Ba包含于X,所以X被B腐蚀的结果就是那个阴影部分。阴影部分在X的范围之内,且比X小,就像X被剥掉了一层似的。

opencv去噪算法对比_python_02


腐蚀后的结果如下图黑色部分所示:

opencv去噪算法对比_计算机视觉_03


相较于原来的灰色部分,仿佛变瘦了。

OpenCV中的函数为:

dst	= cv2.erode( src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )

参数为:

  1. src:输入图像,可以是灰度图或彩色图。
  2. kernel:腐蚀操作的结构元素,默认为一个3×3的简单矩阵。
  3. anchor:锚点,默认为结构元素的中心。
  4. 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()

结果如下所示:

opencv去噪算法对比_opencv_04


若将结构元素的尺寸扩大到7,结果为:

opencv去噪算法对比_计算机视觉_05


ps:在构造结构元素的时候,可以使用numpy,也可以使用OpenCV提供的函数cv2.getStructuringElement()

函数:

retval = cv2.getStructuringElement( shape, ksize[, anchor] )

参数:

  1. shape:结构元素内部的结构,有三种,分别是矩形、十字形和椭圆形:
  2. ksize:结构元素的尺寸。
  3. 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()

opencv去噪算法对比_人工智能_06


也是可以达到一定效果的,但是比矩形的那种腐蚀程度低一些些,因为毕竟结构元素里多了一些0。

4.1.2 图像膨胀

膨胀(dilation)可以看做是腐蚀的对偶运算,其定义是:把结构元素B平移后得到Ba,若Ba与X有交集,我们记下这个a点。所有满足上述条件的a点组成的集合称作X被B膨胀后的结果,如下图所示:

opencv去噪算法对比_人工智能_07


其中X是被处理的对象,B是结构元素,对于任意一个在阴影部分的点a,Ba与X有交集,所以X被B膨胀后的结果就是那个阴影部分,阴影部分包括X所有范围,就像是X膨胀了一圈似的。

opencv去噪算法对比_python_08


膨胀后的图像,其中绿色是膨胀多出来的部分:

opencv去噪算法对比_计算机视觉_09


在OpenCV中的函数为:

dst	= cv2.dilate( src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )

参数:

  1. src:输入图像,可以是灰度图也可以是彩色图。
  2. kernel:膨胀运算的结构元素,默认为一个3×3的简单矩阵。
  3. anchor:同上。
  4. 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()

结果为:

opencv去噪算法对比_opencv_10


若将运算元素尺寸扩大一点,扩大为11:

opencv去噪算法对比_计算机视觉_11


原本断开的地方或小孔都被填上了。

4.2 开运算与闭运算

4.2.1 开运算

开运算 = 先腐蚀运算,再膨胀运算,看上去把细微连在一起的两块目标分开了,开运算的效果图如下所示:

opencv去噪算法对比_opencv去噪算法对比_12


开运算对一些细微的小点,小块,细条等部分是可以消去的,因为先腐蚀消去它们,导致它们消失了无法再通过膨胀变回来,而一些比较大的块通过腐蚀操作只是会变瘦一点,不会被完全抹去,所以可以通过膨胀运算变回来,那么总的效果就是开运算去除了这些孤立的小点,细长的小条。

开运算总结:

  1. 开运算能够去除孤立的小点,毛刺和小条,而总的位置和形状不变。
  2. 开运算是一个基于几何运算的滤波器。
  3. 结构元素大小的不同将导致滤波效果的不同。
  4. 不同的结构元素的选择导致了不同的分割,即提取出不同的特征。

开运算和闭运算都用如下函数来表示,这个函数是OpenCV中图像形态学变化的通用函数:

dst	= cv2.morphologyEx( src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )

参数如下所示:

  1. src:输入图像,灰度图或彩色图均可。
  2. op:形态学操作的类型,包括腐蚀、运算、开运算以及后面要提及的闭运算、形态学梯度、顶帽、黑帽等类型。
  3. kernel:结构元素,可以使用cv2.getStructuringElement函数来定义。
  4. anchor:锚点位置,一般都用中心位置。
  5. 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()

结果如下所示:

opencv去噪算法对比_人工智能_13


一些细小的点被去除了很多,但是开运算的结构元素的尺寸很重要,太小可能去除效果不好,太大可能会得到不想要的结果,如将3改为9,结果将变为:

opencv去噪算法对比_python_14


所以调节这个参数还是很关键的。

4.2.2 闭运算

闭运算 = 先膨胀运算,再腐蚀运算,看上去将两个细微连接的图块封闭在一起,闭运算的效果图如下图所示:

opencv去噪算法对比_计算机视觉_15


闭运算总结:

  1. 闭运算能够填平小孔,弥合小缝隙,而总的位置和形状不变。
  2. 闭运算是通过填充图像的凹角来滤波图像的。
  3. 结构元素大小的不同将导致滤波效果的不同。
  4. 不同结构元素的选择导致了不同的分割。

看个例子:

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()

结果如下所示:

opencv去噪算法对比_人工智能_16


一些小孔被填满了。若把尺寸从7改为21,结果为:

opencv去噪算法对比_opencv_17


就有点过了,把不需要连接和填补的地方也给连接、填补了,所以要合理选择参数。

4.3 形态学梯度(Gradient)

  1. 基础梯度:基础梯度是用膨胀后的图像减去腐蚀后的图像得到的差值图像,也是OpenCV中支持的计算形态学梯度的方法,而此方法得到梯度又称为基本梯度。
  2. 内部梯度:是用原图减去腐蚀之后的图像得到的差值图像。
  3. 外部梯度:图形膨胀后再减去原来图像得到的差值图像。

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()

结果如下所示:

opencv去噪算法对比_人工智能_18

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()

结果为:

opencv去噪算法对比_python_19


黑帽:

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()

opencv去噪算法对比_opencv_20


图像处理之图像基本操作的笔记就暂时到这里,后面将学习传统方法进行图像分割,包括阈值分割、边缘检测算法、连通域分析以及一些其他区域生长算法。