形态学处理

  • 腐蚀
  • 膨胀
  • 开运算和闭运算
  • 顶帽变换、底帽变换和形态学梯度


常用的形态学处理方法包括:腐蚀、膨胀、开运算、闭运算、顶帽运算、底帽运算,其中腐蚀和膨胀是最基础的方法,其他方法是两者相互结合而产生的。

腐蚀

结构元: 与平滑操作类似,在平滑操作中使用的是矩形邻域,而在形态学处理中邻域可以是矩形结构,也可以是椭圆形、十字交叉形结构。同样也需要指定一个锚点。

在腐蚀操作中,是取结构元中的最小值作为锚点的值。可以对灰度图或二值图做腐蚀操作。以下图为例(均取中心点为锚点):

opencv腐蚀图片到一根线 opencv 腐蚀算法_opencv

上方三个图中的邻域的最小值分别为 11、21、21,这些最小值输出到图像中的锚点位置,其他位置通过移动结构元以此类推,即可得到完整的输出图像。

由上图可知,因为取每个位置邻域内的最小值,所以腐蚀后输出图像的总体亮度的平均值比起原图会有所降低,图像中比较亮的区域的面积会变小甚至消失,而比较暗的区域的面积会增大。

因为对图像进行腐蚀操作后缩小了亮度区域的面积,所以针对阈值分割后前景时白色的二值图,可以通过原图与腐蚀后的图像相减得到前景的边界。

OpenCV 函数

erode 函数官方地址

// 腐蚀
// C++
void cv::erode(InputArray 		src,
			 	OutputArray 	dst,
				InputArray 	    kernel,
				Point 	        anchor = Point(-1,-1),
				int 	        iterations = 1,
				int 	        borderType = BORDER_CONSTANT,
				const Scalar & 	borderValue = morphologyDefaultBorderValue() 
							)
  
// Python
dst=cv.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])

参数解释:

参数

解释

src

输入矩阵,灰度图或二值图

dst

输出矩阵

kernel

结构元,可以通过 getStructuringElement 函数获取

anchor

锚点的位置;默认值(-1,-1)表示锚点在结构元中心。

iterations

腐蚀的次数

borderType

边界扩充类型

borderValue

边界扩充值

常用的 borderType 有 BORDER_CONSTANT 、BORDER_REPLICATE 和 BORDER_REFLECT ,BORDER_CONSTANT为用常量扩充,BORDER_REPLICATE 为边界复制扩充,BORDER_REFLECT 为镜像扩充。

getStructuringElement 函数官方地址

// 获取结构元
// C++
Mat cv::getStructuringElement(int 	shape,
							Size 	ksize,
							Point 	anchor = Point(-1,-1) 
							)		
// Python:
retval	=	cv.getStructuringElement(	shape, ksize[, anchor])

参数

解释

shape

结构元形状

ksize

结构元大小

anchor

结构元锚点

结构元形状 shape

  • MORPH_RECT
    产生矩形的结构元
  • MORPH_CROSS
    产生十字交叉形的结构元
  • MORPH_ELLIPSE
    产生椭圆形的结构元

Python 示例

import cv2 as cv

if __name__ == '__main__':
    img_src = cv.imread("img2.jpg", 0)

    # 创建矩形结构元
    kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
    # 腐蚀图像
    img_erode = cv.erode(img_src, kernel)
    # 边界提取
    img_edge = img_src - img_erode

    cv.imwrite("./images/img2_src.jpg", img_src)
    cv.imwrite("./images/img2_erode.jpg", img_erode)
    cv.imwrite("./images/img2_edge.jpg", img_edge)

对二值图腐蚀

opencv腐蚀图片到一根线 opencv 腐蚀算法_opencv_02

阈值分割后的图像难免会有很多零星的白色噪点,可以通过腐蚀来去除。由上图可知,腐蚀的结构元越大,目标物体(白色区域)的面积会越来越小,如果对图像反复进行腐蚀运算,则会消除整个目标物体。

对灰度图腐蚀

opencv腐蚀图片到一根线 opencv 腐蚀算法_锚点_03

对灰度图的腐蚀,随着结构元尺寸的增大,灰度较暗的区域的面积也随着增大,同时灰度较亮的区域的面积就随着减小,而且处理后的效果可以隐约看出结构元的形状,即很多重叠的矩形快,很像马赛克效果。如果采用椭圆形或十字交叉形的结构元进行侵蚀,则同样会出现类似的椭圆或者十字交叉的形状。

膨胀

在膨胀操作中,是取结构元中的最大值作为锚点的值。可以对灰度图或二值图做膨胀操作。膨胀后输出图像的总体亮度的平均值比起原图会有所上升,图像中比较亮的区域的面积会变大,而比较暗的区域的面积会减小。

OpenCV 函数

dilate 函数官方地址

// 膨胀
// C++
void cv::dilate(InputArray 	  src,
				OutputArray   dst,
				InputArray 	  kernel,
				Point 	      anchor = Point(-1,-1),
				int 	      iterations = 1,
				int 	      borderType = BORDER_CONSTANT,
				const Scalar & 	borderValue = morphologyDefaultBorderValue() 
				)		
// Python:
dst	=cv.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])

参数与腐蚀操作的一样。

Python 示例

下面使用进度条调节结构半径,观察结构元的尺寸对形态学处理的影响

import cv2 as cv

def change_dilate_r(r):
    # 创建结构元
    kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (2 * r + 1, 2 * r + 1))
    # 腐蚀图像
    img_dilate = cv.dilate(img_src, kernel)
    # 显示膨胀效果
    cv.imshow('dilate', img_dilate)


if __name__ == '__main__':
    img_src = cv.imread("img5.jpg", 0)
    # 显示原图
    cv.imshow("src", img_src)
    # 结构元半径
    r = 1
    max_r = 20
    # 显示膨胀效果的窗口
    cv.namedWindow('dilate', 1)
    # 调节结构元半径
    cv.createTrackbar('r', 'dilate', r, max_r, change_dilate_r)
    change_dilate_r(0)
    cv.waitKey(0)

opencv腐蚀图片到一根线 opencv 腐蚀算法_python_04

开运算和闭运算

开运算 先腐蚀后膨胀叫开运算,具有消除亮度较高的细小区域、在纤细点处分离物体,对于较大物体,可以在不明显改变面积的情况下平滑其边界等作用。

闭运算 先膨胀后腐蚀叫闭运算,具有填充白色物体细小黑色空洞的区域、连接临近物体、同一个结构元、多次迭代处理,也可以在不明显改变其面积的情况下平滑其边界等作用。

开运算和闭运算是膨胀和腐蚀的组合,完全可以利用函数 erode 和 dilate 来完成,OpenCV也提供了函数直接使用

OpenCV 函数

morphologyEx 函数官网地址

// C++
void cv::morphologyEx(InputArray 	src,
					OutputArray 	dst,
					int 	op,
					InputArray 	kernel,
					Point 	anchor = Point(-1,-1),
					int 	iterations = 1,
					int 	borderType = BORDER_CONSTANT,
					const Scalar & 	borderValue = morphologyDefaultBorderValue() 
					)		
// Python:
dst	=cv.morphologyEx(src, op, kernel[,dst[,anchor[,iterations[,borderType[, borderValue]]]]])

参数

解释

op

形态学操作的类型

kernel

结构元,可以通过 getStructuringElement 函数获取

anchor

锚点的位置;默认值(-1,-1)表示锚点在结构元中心。

iterations

迭代次数

borderType

边界扩充类型,见腐蚀

borderValue

边界扩充值,见腐蚀

op 形态学操作的类型

  • MORPH_ERODE 腐蚀 erode
  • MORPH_DILATE 膨胀 dilate
  • MORPH_OPEN 开运算(先腐蚀后膨胀)
  • MORPH_CLOSE 闭运算(先膨胀后腐蚀)
  • MORPH_GRADIENT 形态学梯度
  • MORPH_TOPHAT 顶帽运算
  • MORPH_BLACKHAT 底帽运算

Python 示例

使用进度条调节结构半径以及迭代次数,观察结构元的尺寸对形态学处理的影响

import cv2 as cv

def nothing(*args):
    pass

if __name__ == '__main__':
    img_src = cv.imread("img1.jpg", 0)

    r, i = 1, 1
    max_r, max_i = 20, 20

    cv.namedWindow('morphology', 1)
    cv.createTrackbar('r', 'morphology', r, max_r, nothing)
    cv.createTrackbar('i', 'morphology', i, max_i, nothing)

    while True:
        r = cv.getTrackbarPos('r', 'morphology')
        i = cv.getTrackbarPos('i', 'morphology')
        kernel = cv.getStructuringElement(cv.MORPH_RECT, (2*r+1, 2*r+1))
        # img_open = cv.morphologyEx(img_src, cv.MORPH_OPEN, kernel, iterations=i)
        # cv.imshow('morphology', img_open)
        img_erode = cv.erode(img_src, kernel, iterations=i)
        cv.imshow('morphology', img_erode)

        ch = cv.waitKey(5)
        if ch==27:
            break
        elif ch==115:
            # cv.imwrite(f'./images/img1_{i}.jpg', img_open)
            cv.imwrite(f'./images/img1_{i}.jpg', img_erode)
    cv.destroyAllWindows()

opencv腐蚀图片到一根线 opencv 腐蚀算法_锚点_05

上图为开运算(上行)和腐蚀(下行)对二值图的处理效果,使用 3x3 矩形结构元,依次进行1次、5次、20次的迭代。随着迭代次数的增加,开运算使白色物体周围细小的亮度较高的区域被消除,且白色物体的面积没有明显的改变,边界在局部内也变的平滑。但是腐蚀操作会使物体的面积变小甚至消失,若果多次使用膨胀操作,则会使白色物体的面积逐渐增大。

开运算还有一个很重要的作用:消除暗背景下的较亮区域。如下图所示,目的是在不改变黑色球的面积的情况下,消除球上的白色区域。腐蚀操作会使球的面积增大,而开运算可以避免球的面积增大。

opencv腐蚀图片到一根线 opencv 腐蚀算法_锚点_06

对于闭运算,如下图所示,目的是去掉骰子上的黑色区域。通过膨胀和闭运算都可以做到,但是膨胀会使骰子的面积增大,而闭运算不会。

opencv腐蚀图片到一根线 opencv 腐蚀算法_python_07

顶帽变换、底帽变换和形态学梯度

顶帽变换和底帽变换是分别以开运算和闭运算为基础的

顶帽变换 原图像减去开运算的结果。由于开运算可以消除暗背景下的较亮区域,如果用原图像减去开运算结果就可以得到原图中灰度较亮的区域,所以又称白顶帽变换。可以校正不均匀光照。

底帽变换 原图像减去闭运算的结果。由于闭运算可以删除亮度较高背景下的较暗区域,如果用原图像减去闭运算结果就可以得到原图像灰度较暗的区域,所以又称黑底帽变换。

形态学梯度 膨胀的结果减去腐蚀的结果,因为膨胀是取邻域内的最大值,从而增大亮度高的区域的面积,而腐蚀是取邻域内的最小值,从而减小亮度高的区域的面积,所以得到的图像是图像中物体的边界。

Python 示例

import cv2 as cv

def nothing(*args):
    pass

if __name__ == '__main__':
    img_src = cv.imread("/img5.jpg", 0)

    r, i = 1, 1
    max_r, max_i = 20, 20

    type = 0
    max_type = 2

    cv.namedWindow("morphology", 1)
    cv.createTrackbar('r', 'morphology', r, max_r, nothing)
    cv.createTrackbar('i', 'morphology', i, max_i, nothing)
    cv.createTrackbar('t', 'morphology', type, max_type, nothing)
    m_type = cv.MORPH_TOPHAT
    while True:
        r = cv.getTrackbarPos('r', 'morphology')
        i = cv.getTrackbarPos('i', 'morphology')
        t = cv.getTrackbarPos('t', 'morphology')
        if t==0:
            m_type = cv.MORPH_TOPHAT
        elif t==1:
            m_type = cv.MORPH_BLACKHAT
        else:
            m_type = cv.MORPH_GRADIENT
            
        kernel = cv.getStructuringElement(cv.MORPH_RECT, (2*r+1, 2*r+1))
        img_mor = cv.morphologyEx(img_src, m_type, kernel, iterations=i)

        cv.imshow('morphology', img_mor)
        ch = cv.waitKey(5)
        if ch == 27:
            break
        elif ch == 115:
            cv.imwrite(f'./images/img_{t}.jpg', img_mor)
    cv.destroyAllWindows()

下图为使用 3x3 的矩形结构元进行 12 次的顶帽运算,使用 3x3 的矩形结构元进行10底帽运算的结果,形态学梯度操作后的结果。

opencv腐蚀图片到一根线 opencv 腐蚀算法_opencv_08