2019-7-17 opencv图像处理5-形态学变换(Morphological Transformations)
官网参见https://docs.opencv.org/3.4.1/d9/d61/tutorial_py_morphological_ops.html
形态学变化可以用来解决抑制噪声、特征提取、边缘检测、图像分割、形状识别、纹理分析、图像恢复与重建、图像压缩等图像处理问题。
内容概要
- 腐蚀, cv2.erode(),去白色噪音,分离连接物体
- 膨胀, cv2.dilate(),扩大前景区域,连接物体被断开部分
- 开运算,cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel),先腐蚀再膨胀。用于去除噪音,同时保持前景不缩小
- 闭运算,cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel),先膨胀再腐蚀。用于填充前景物体小洞,或者去除前景小黑点
- 形态学梯度,cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel),膨胀图和腐蚀图只差。用于获得物体边缘。
- 顶帽,cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel),原始图像和开运算后图像之差,往往用来分离比临近点亮一些的斑块。
- 黑帽, cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel),闭运算后的图像和原始图像之差,用来分离比临近点暗一些的斑块,效果图有着非常完美的轮廓。
- 结构化元素,矩形核,圆形核,椭圆核
形态学变换是基于图像形状的一些简单操作,通常是对二值化图像进行操作。
它需要2个参数,一个是原始图像,另外一个被称为结构化元素或者核,它决定了操作的性质。有2个基本的形态学操作:腐蚀和膨胀。它们的变化形式有开运算,闭运算,梯度等。
1.腐蚀
形态学腐蚀的基本概念和土壤腐蚀一样,会把图像中前景物体的边界腐蚀掉(通常会保持前景为白色)。它是怎么做到的呢?
以2D卷积为例,参见
这样做的结果就是,靠近边界的图像元素会根据卷积核的大小决定是否被抛弃(或者说腐蚀掉,也就是变为0)。
腐蚀会让前景物体的厚度或者说大小在整个图像中变小,也可以说图像中的白色区域减少(因为前景是白色的)。
腐蚀可以去除图像中的小白色噪音,也可以用于分离2个连接在一起的物体。
例1,简单的腐蚀,5x5核
# -*- coding: cp936 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)
plt.subplot(121),plt.imshow(img,'gray'),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(erosion,'gray'),plt.title('Erosion')
plt.xticks([]), plt.yticks([])
plt.show()

本例图片来自官网,运行结果如上图。右边是腐蚀结果,白色区域明显减少。
2.膨胀
膨胀与腐蚀相反,卷积核对应图像元素只要有1个值为1,那么中心元素的值就是1。所以膨胀会增加图像前景物体的尺寸,或者说增加图像的白色区域。
通常去除噪音时,会先腐蚀然后再膨胀,因为腐蚀在去除白色噪音的同时,还会缩小前景对象,所以我们还需要膨胀它。
膨胀可以扩大前景物体,还可以用于连接一个物体被断开部分。
例,简单的膨胀
# -*- coding: cp936 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
dilation = cv2.dilate(img,kernel,iterations = 1)
plt.subplot(121),plt.imshow(img,'gray'),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dilation,'gray'),plt.title('Dilation')
plt.xticks([]), plt.yticks([])
plt.show()

运行结果如上图。右边是膨胀结果,白色区域明显增加。
3.开运算
先腐蚀再膨胀的过程,我们就称为开运算。上面已经解释过了,主要用于去除噪音。
opencv中提供cv.morphologyEx()用于开运算。
例,开运算
# -*- coding: cp936 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('j-1.png',0)
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
plt.subplot(121),plt.imshow(img,'gray'),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(opening,'gray'),plt.title('Opening')
plt.xticks([]), plt.yticks([])
plt.show()

运行结果如上图。右边是开运算结果,白色噪音被去除了,但是白色区域没有减少。
4.闭运算
闭运算是和开运算相反的操作,先膨胀再腐蚀。用于填充前景物体的小洞,或者去除前景物体的小黑点。
cv.morphologyEx()也可以用于闭运算。
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
5.形态学梯度
梯度 用于刻画目标边界或边缘位于图像灰度级剧烈变化的区域。
形态学梯度是一幅图像的膨胀图和腐蚀图之差,结果看上去就是物体的轮廓。
所以对图像的二值图(灰度图)进行形态学梯度操作可以将团块(blob)的边缘突出出来,实现保留物体的边缘轮廓。
opencv梯度操作也是使用cv.morphologyEx()函数
# -*- coding: cp936 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('j.png')
kernel = np.ones((5,5),np.uint8)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
plt.subplot(121),plt.imshow(img,'gray'),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(gradient,'gray'),plt.title('Gradient')
plt.xticks([]), plt.yticks([])
plt.show()

结果如上图。
6.顶帽
原始图像和开运算后图像之差,往往用来分离比临近点亮一些的斑块。
在一幅图像具有大幅的背景,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
代码,9x9核
# -*- coding: cp936 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('j.png')
kernel = np.ones((9,9),np.uint8)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
plt.subplot(121),plt.imshow(img,'gray'),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(tophat,'gray'),plt.title('Tophat')
plt.xticks([]), plt.yticks([])
plt.show()

运行结果如上
7.黑帽
闭运算后的图像和原始图像之差,用来分离比临近点暗一些的斑块,效果图有着非常完美的轮廓。
代码
# -*- coding: cp936 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('j.png')
kernel = np.ones((9,9),np.uint8)
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
plt.subplot(121),plt.imshow(img,'gray'),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blackhat,'gray'),plt.title('Blackhat')
plt.xticks([]), plt.yticks([])
plt.show()

运行结果如上图。
结构化元素
在上面的例子中我们使用Numpy 构建了结构化元素(卷积核),它是正方形的。
kernel = np.ones((9,9),np.uint8)
但有时我们需要构建一个椭圆形或者圆形的核。为了实现这种要求,opencv提供了函数cv2.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 Kernel
>>> 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)
