形态变换
在opencv之膨胀与腐蚀中介绍了Dilation/Erosion的原理.建议先读这一篇,搞懂原理. 这样就可以很轻松地理解为什么本文的这些形态变换可以取得相应的效果.
基于此,我们可以组合出更多的形态变换以达到不同的目的.
有以下几种:
Opening
Closing
Morphological Gradient
Top Hat
Black Hat
Opening
先腐蚀再膨胀,可以把较小的目标去除.比如:
Closing
可以把物体内的小黑洞消除.比如:
Morphological Gradient
可以提取出物体的轮廓.
比如下图,腐蚀和膨胀对物体内部的像素影响不大,(内部的局部最大值和最小值差不多),所以做完插值以后,边缘的像素值差比较大,内部像素差值变为0,从而提取出物体轮廓.
Top Hat
Black Hat
from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
morph_size = 0
max_operator = 4
max_elem = 2
max_kernel_size = 21
title_trackbar_operator_type = 'Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat'
title_trackbar_element_type = 'Element:\n 0: Rect - 1: Cross - 2: Ellipse'
title_trackbar_kernel_size = 'Kernel size:\n 2n + 1'
title_window = 'Morphology Transformations Demo'
morph_op_dic = {0: cv.MORPH_OPEN, 1: cv.MORPH_CLOSE, 2: cv.MORPH_GRADIENT, 3: cv.MORPH_TOPHAT, 4: cv.MORPH_BLACKHAT}
def morphology_operations(val):
morph_operator = cv.getTrackbarPos(title_trackbar_operator_type, title_window)
morph_size = cv.getTrackbarPos(title_trackbar_kernel_size, title_window)
morph_elem = 0
val_type = cv.getTrackbarPos(title_trackbar_element_type, title_window)
if val_type == 0:
morph_elem = cv.MORPH_RECT
elif val_type == 1:
morph_elem = cv.MORPH_CROSS
elif val_type == 2:
morph_elem = cv.MORPH_ELLIPSE
element = cv.getStructuringElement(morph_elem, (2*morph_size + 1, 2*morph_size+1), (morph_size, morph_size))
operation = morph_op_dic[morph_operator]
dst = cv.morphologyEx(src, operation, element)
cv.imshow(title_window, dst)
src = cv.imread("/home/sc/disk/keepgoing/opencv_test/j.png")
if src is None:
print('Could not open or find the image: ', args.input)
exit(0)
cv.namedWindow(title_window)
cv.createTrackbar(title_trackbar_operator_type, title_window , 0, max_operator, morphology_operations)
cv.createTrackbar(title_trackbar_element_type, title_window , 0, max_elem, morphology_operations)
cv.createTrackbar(title_trackbar_kernel_size, title_window , 0, max_kernel_size, morphology_operations)
morphology_operations(0)
cv.waitKey()
可以用上述代码感受一下对不同图片,采用不同操作,不同参数,得到的结果是怎样的.
利用形态变换提取图像中的水平线
看一个具体的例子
我们想从下图中提取出水平线出来.
前面讲过,膨胀和腐蚀都是通过卷积核去定义一个要从什么样的区域去取局部极大值或局部极小值. 那为了完成水平线的提取,我们可以定义自己的特定形状的卷积核去完成这个功能.
形态变换实现水平线和音符提取
import cv2 as cv
import numpy as np
def test():
src = cv.imread("/home/sc/disk/keepgoing/opencv_test/music.png",cv.IMREAD_COLOR)
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
gray = cv.bitwise_not(gray)
cv.imshow("gray",gray)
horizontal = np.copy(gray)
vertical = np.copy(gray)
##设计特定形状卷积核
cols = horizontal.shape[1]
horizontal_size = cols // 30
horizontalStructure = cv.getStructuringElement(cv.MORPH_RECT, (horizontal_size, 1))
print(horizontalStructure)
horizontal1 = cv.erode(horizontal, horizontalStructure)
cv.imshow("h1",horizontal1)
horizontal2 = cv.dilate(horizontal1, horizontalStructure)
cv.imshow("h2",horizontal2)
##设计特定形状卷积核
rows = vertical.shape[0]
verticalsize = rows // 30
verticalStructure = cv.getStructuringElement(cv.MORPH_RECT, (1, verticalsize))
vertical = cv.erode(vertical, verticalStructure)
vertical = cv.dilate(vertical, verticalStructure)
cv.imshow("v",vertical)
test()
if 27 == cv.waitKey(0):
cv.destroyAllWindows()
首先我们完成将图像的预处理.
这里用了cv.bitwise_not(gray)将我们关注的部分变为亮的.不关注的变为暗的. 因为膨胀和腐蚀都是针对亮的区域而言的.指亮的区域的扩张或收缩.
接下来就是如何定义我们的卷积核呢?
以音符的提取为例,我们希望把水平方向的白色横线去除,即我们更关注垂直方向的像素点. 水平方向的白色横线的上下位置基本是黑色的背景. 所以我们需要的卷积核是一个如下:
这样对白色横线上的像素点,通过取该像素点上下方若干个点的最小值,把该像素点灰度值变为0.从而达到去除白色横线的目的.
最终得到如下: