Bootstrap

python-opencv第八期:开运算与闭运算详解(下)(包括erode、dilate函数详解)

目录

概要:

正文部分:

原理与函数:

原理解析:

直观展示:

函数解析:

(1)腐蚀操作:

(2)膨胀操作:

案例与操作:

 (1)故事背景:

 (2)故事1(开运算):

 (3)故事2(闭运算):

结语:

参考文章: 


概要:

嗦粥汁所周知,在如今计算机视觉(Computer Vision short for CV)是人工智能与机器人技术发展的一个重大研究方向,而opencv作为一个专门为机计算机视觉编程提供技术与函数支持的第三方库,自然是一个需要重点研究的内容。

本期首先要谈谈图像的开运算与闭运算的相关原理与函数解析,以及演示一些经典案例,以供大家参考。

话不多说,我是Kamen Black 君,让我们开始今天的学习!

正文部分:

print("祝大家每天快乐,love and peace!")

原理与函数:

— —初 窥 门 道

原理解析:

其实我们实现开运算与闭运算,主要是通过两个奇妙的函数:dilate与它的兄弟erode

其中dilate用于图像的形态学膨胀,而erode则用于图像的形态学腐蚀。类比于我们上一期的比喻,那dilate的作用相当于下一场倾盆大雨,而erode的作用相当于发一次持续干旱

也就是说:

dilate形象化的作用是将图像向外进行像素生长,达到面积增大的目的;

而erode形象化的作用是将图像向内进行像素退化,达到面积减小的效果。

但是要细究膨胀与腐蚀的原理的话,我们要了解两个最为关键的概念:结构元素、锚点

(1)结构元素——相当于一个蒙版(也可以理解为一个窗口),它能在被操作的图像上进行滑动,每个时刻都能把被操作图像的一部分像素框选出来。结构元素通常为矩形,长与宽可以不相同。

(2)锚点——针对结构元素而言。顾名思义,在结构元素中如同船锚一般,固定重心的点位,是结构元素与被操作图像的连接位置。默认情况下,锚点就是结构元素形状上的中心点锚点的选择方法将在下文的函数解析中有所介绍。

理解这两个概念后,我们就可以简单地说明膨胀与腐蚀的操作原理(以下称被操作图像为A,操作后图像为B,以避免文字繁杂):

(1)腐蚀——对A的一个像素做与结构元素的框选处理,即将结构元素的锚点与A的一个像素重合,然后观察被结构元素这个蒙版框选出来的A的所有像素的像素值,取其中的最小值作为B的这一像素的像素值。对A的每一个像素都实现以上的操作,以生成B。

         用公式表达就是:

 其中,dst(x,y)表示B中坐标为(x,y)的像素的像素值;

            element(x,y)表示结构元素(其实可以直接表达为element,结构元素本身与每个像素并无关系,加上(x,y)只是为了更加形象);

            (x',y')表示结构元素中各位置相对于锚点的坐标,如锚点的坐标为(0,0),而锚点下2行,上3列的坐标为(2,-3);

            src(x+x',y+y')表示A中坐标为(x+x',y+y')的像素的像素值;

            min表示取最小值。

(2)膨胀——对A的一个像素做与结构元素的框选处理,即将结构元素的锚点与A的一个像素重合,然后观察被结构元素这个蒙版框选出来的A的所有像素的像素值,取其中的最大值作为B的这一像素的像素值。对A的每一个像素都实现以上的操作,以生成B。

         用公式表达就是:

 其中,dst(x,y)表示B中坐标为(x,y)的像素的像素值;

            element(x,y)表示结构元素(其实可以直接表达为element,结构元素本身与每个像素并无关系,加上(x,y)只是为了更加形象);

            (x',y')表示结构元素中各位置相对于锚点的坐标,如锚点的坐标为(0,0),而锚点下2行,上3列的坐标为(2,-3);

            src(x+x',y+y')表示A中坐标为(x+x',y+y')的像素的像素值。

            max表示取最大值。

理解操作原理之后,还有几个点要简单说明一下,提前解决各位读者将来的疑惑:

(1)中心点坐标的计算方式:如果结构元素的行数与列数都是奇数,那中心点必然好找,但是倘若行数或列数出现了偶数的情况呢?这时,就以行数来举例,如果结构元素的总行数为偶数,那么中心点所在的行数可以这样计算:总行数/2+1,这就是中心点所在的行数。列数也是同理。注意:这里,行数(列数)的首位都是1,而非0。

         其实无论行数(列数)是奇数还是偶数,都可以通过这样的公式来进行计算:

  其中,ceil函数表示对操作数向上取整,如(2,3]范围内的操作数,ceil函数的结果都是3。

(2) 结构元素框选范围超出被操作图像:结构元素在被操作图像上滑动,若是没有超出范围,则可以根据公式算出操作后图像的像素值,但是倘若超出被操作图像呢?这时,要分为两种情况:一种是未扩充边界的情况,另一种是扩充了边界的情况。

未扩充边界的情况:我们直接不考虑被操作图像外的情况,只考虑范围内的。若是腐蚀操作,只考虑被操作图像中的被框选的像素值中的最小值;若是膨胀操作,只考虑被操作图像中的被框选的像素值中的最大值。

扩充了边界的情况:如果扩充了边界的话,则要考虑到扩充的边界是何种取值,根据相应情况进行操作。举个例子,如果扩充的边界类型(borderType,下文会有介绍)为cv2.BORDER_CONSTANT,且扩充值(borderValue,同样在下文会有说明)为0时,则相当于被操作图像外绕了一圈又一圈的0(爱的魔力转圈圈)。只要能被蒙版框选的区域,且是范围外的区域,像素值一定显示为0。如果扩充值为3,则是绕了一圈又一圈的3。

直观展示:

只使用文字可能仍显得过于抽象,接下来通过图形进行一个较为直观的说明:

(1)结构元素如右图所示:,默认情况下锚点就在中心点。根据我们的中心点坐标计算方式,其中心点即在下

         图中交叉斜向十字位置:

(2)被操作图像如右图所示:

(3)接下来进行腐蚀与膨胀操作后的图像展示:

①腐蚀后图像:

②膨胀后图像:

 根据实际操作,可以证明,形态学的腐蚀膨胀操作符合以上本文所描述的原理与规则。

函数解析:

①使用准备:

与前几期一样,依旧是调用我们的工具人老伙伴----opencv,作为我们使用rectangle的大前提。

import cv2

②使用情况:

该部分在上一期已经做过说明,故不在此再作赘述。

③语法说明:

该期有两个函数:erode与dilate,分开进行说明。

(1)腐蚀操作:

使用语法:dst = cv2.erode(src, kernel, dst, anchor, iterations, borderType, borderValue)

参数说明:首个dst:通过erode函数进行形态学腐蚀之后得到的目标图像,数据类型为(array)数组类型;

                  src:使用erode函数进行处理的源图像,数据类型也是(array)数组类型;

                  kernel:即上文所提到的结构元素。结构元素可以通过cv2.getStructuringElement函数进行构建,也可以通过numpy库中的ones或array函数进行构建。接下来对使用cv2.getStructuringElement函数构建结构元素进行进一步的简要说明:

            使用语法:kernel = cv2.getStructuringElement(shape, ksize, anchor)

            参数说明:kernel:即通过getStructuringElement函数构造得到的结构元素;

                              shape:结构元素的形状,有以下可供选择的取值:cv2.MORPH_RECT(矩形)、cv2.MORPH_CROSS(十字形)、cv2.MORPH_ELLIPSE(椭圆形);

                              ksize:结构元素的行列数,取值格式为(x,y),其中x表示列数,y表示行数(此处与直觉相反,要注意);

                              anchor:结构元素的锚点,不取值或取值为(-1,-1)则取结构元素的中心点作为锚点。此处,仅cv2.MORPH_CROSS(十字形)的蒙版形状与锚点的选择有关,其他形状与这里的锚点选择并无关系,可任意取值。

                  次个dst:与首个dst的说明相同,不再赘述,默认为None;

                  anchor结构元素的锚点,不取值或取值为(-1,-1)则取结构元素的中心点作为锚点。与getStructuringElement函数的锚点有所区别,这里的锚点选择对所有形状的结构元素都有影响;

                  iterations:迭代次数。迭代的第N次使用迭代第N-1次的dst作为src来进行操作,其他参数不变;

                  borderType:扩充边界的方式,默认值为None,即表示不进行边界扩充。具体的类型如下图所示:

表格来源:opencv中borderType的类型,erode与dilate函数不支持BORDER_WRAP类型的取值;

                  borderValue:当borderType取值为cv2.BORDER_CONSTANT时,扩充边界的元素以borderValue填充。borderValue默认值为(0, 0, 0),当然你可以指定其他值,三个元素分别表示 BGR,注意不是RGB。

(2)膨胀操作:

使用语法:dst = cv2.dilate(src, kernel, dst, anchor, iterations, borderType, borderValue)

参数说明:与erode函数相同,故不再赘述。

下面另外附上erode与dilate函数相关说明的原生文档,以供参考:

def erode(src: Mat, kernel, dst: Mat = ..., anchor=..., iterations=..., borderType=..., borderValue=...) -> typing.Any:
    '''
    ·"erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst  
    ·@brief Erodes an image by using a specific structuring element.  

    ·The function erodes the source image using the specified structuring element that determines the  
    ·shape of a pixel neighborhood over which the minimum is taken:

    ·\\f[\\texttt{dst} (x,y) =  \\min _{(x',y'):  \\, \\texttt{element} (x',y') \\ne0 } \\texttt{src} (x+x',y+y')\\f]  

    ·The function supports the in-place mode. Erosion can be applied several ( iterations ) times. In 
    ·case of multi-channel images, each channel is processed independently.

    ·@param src input image; the number of channels can be arbitrary, but the depth should be one of 
    ·CV_8U, CV_16U, CV_16S, CV_32F or CV_64F. 
    ·@param dst output image of the same size and type as src.  
    ·@param kernel structuring element used for erosion; if `element=Mat()`, a `3 x 3` rectangular
    ·structuring element is used. Kernel can be created using #getStructuringElement.  
    ·@param anchor position of the anchor within the element; default value (-1, -1) means that the 
    ·anchor is at the element center.   
    ·@param iterations number of times erosion is applied.   
    ·@param borderType pixel extrapolation method, see #BorderTypes. #BORDER_WRAP is not supported. 
    ·@param borderValue border value in case of a constant border   
    ·@sa  dilate, morphologyEx, getStructuringElement"
    ...
    '''
def dilate(src: Mat, kernel, dst: Mat = ..., anchor=..., iterations=..., borderType=..., borderValue=...) -> typing.Any:
    '''
    ·"dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst 
    ·@brief Dilates an image by using a specific structuring element.      

    ·The function dilates the source image using the specified structuring element that determines the   
    ·shape of a pixel neighborhood over which the maximum is taken:  
    ·\\f[\\texttt{dst} (x,y) =  \\max _{(x',y'):  \\, \\texttt{element} (x',y') \\ne0 } \\texttt{src} (x+x',y+y')\\f]   

    ·The function supports the in-place mode. Dilation can be applied several ( iterations ) times. In   
    ·case of multi-channel images, each channel is processed independently.   

    ·@param src input image; the number of channels can be arbitrary, but the depth should be one of  
    ·CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.   
    ·@param dst output image of the same size and type as src.   
    ·@param kernel structuring element used for dilation; if elemenat=Mat(), a 3 x 3 rectangular   
    ·structuring element is used. Kernel can be created using #getStructuringElement\n.   
    ·@param anchor position of the anchor within the element; default value (-1, -1) means that the
    ·anchor is at the element center.   
    ·@param iterations number of times dilation is applied.   
    ·@param borderType pixel extrapolation method, see #BorderTypes. #BORDER_WRAP is not suported.\   
    ·@param borderValue border value in case of a constant border  
    ·@sa  erode, morphologyEx, getStructuringElement"
    ...
    '''

案例与操作:

— —小 试 牛 刀

接下来我们仍以上一期的两湖小事作为我们的案例操作:

(1)故事背景:

        话说,在很久很久很久以前,有两片湖泊,他们相邻很近,就像恋人一样相互依偎,中间还有一条小河连接着他们俩儿,作为他们连接的纽带。附近的人们管他们其中一片叫铁柱湖,另一片叫翠花泊,中间的小河就叫牵手河,因为这就好似两片湖泊仅仅牵着的小手。这样的日子,过了很久,似乎就要这样一直持续下去。。。(你知道我接下来要说什么:))

铁柱湖与翠花泊

 (2)故事1(开运算):

        可是这样的日子过得久了,就好像人有七年之痒一般,两片湖泊也开始嫌恶互相之间一成不变的陪伴。于是他们向老天爷抱怨,希望能将他们俩儿分开,希望都能够给彼此一片空间,过一过各自的生活。老天爷聆听到了他们的请求,说可以,但是他们必须要接受考验,那就是一场旷日持久的干旱,这场干旱会断开他们之间的牵手河,也会蒸发他们自身的湖水。

        两片湖早就已经对彼此心生厌烦,不由多说,便都接受了考验。很快,干旱到来了,刚开始,两片湖还算能够接受,也只是感觉自身的容量减少了一点。可是到了后来,干旱还是带来了巨大的影响,因为整日的干旱,植物不断地枯死,动物也纷纷去寻找其他的安身之所,生活的村民也渐渐地离开这片伤心地。终于,牵手河彻底蒸发完了,两片湖也算彻底分开了。但是周遭的环境已然是一片凋敝,湖泊也丧失了生气。铁柱湖与翠花泊也都开始回忆起过往的时光,相比于此时,那是无比的灿烂与美好。

        渐渐地,时过境迁,干旱结束了,雨季到来了。随着雨水的不断倾注,两片湖的面积也开始慢慢地膨胀起来,他们心中又开始萌生互相连接的希望。可是随着雨季的结束,两片湖也只是恢复了与以前差不多的面积,但还是没能互相连接,牵手河的河床也早就成了两者之间无法逾越的高地。 

import cv2
import numpy as np

img = cv2.imread("F://src.jpg")

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(10,10))

img1 = cv2.erode(img,kernel,iterations=2)
img2 = cv2.dilate(img1,kernel,iterations=3)

cv2.imwrite("F://rst1.jpg",img2)

cv2.imshow("src",img)
cv2.imshow("rst1",img2)
cv2.waitKey(5000)
cv2.destroyAllWindows()
互相分隔的两片湖

 (3)故事2(闭运算):

        两片湖又向老天爷情愿,希望能将他们俩重新连接在一起。老天爷回应道,如果他们真想如此的话,这一次也可以满足你们的愿望,但是仍有一个条件,那就是你们这次不仅仅是靠一条牵手河来进行连接,而是要进行完全的融合,合成一个湖泊,达成你中有我,我中有你的情况。并且,这是最后的一个愿望,一旦合二为一,就再也不能分开。

        这一次,铁柱与翠花都没有立即下结论,他们都经过了一段长时间的思考与斟酌。最后他们向老天爷说出了自己的选择:他们都愿意合二为一。老天爷表示可以,满足你们的愿望。接着,天上便降下了倾盆大雨。这场雨下得不仅势大,而且持久,久到将原本牵手河的河床重新灌满了河水,并且不断地扩张开来。雨势过大,环境再一次变得不适合动植物生存,湖边的周遭又一次了无生迹,可是这一次两片湖再无怨言,选择了默默接受,互相扶持,共同陪伴到最后一刻。

        终于,雨停了,太阳出现,在已经合二为一的湖面上激起粼粼波光。随着雨季的结束,湖泊的面积慢慢恢复到正常的大小,可从此再也没有分离开来。后来,人们知道这里有两片湖合为一片,却不再记起两片湖分别叫什么名字,只知道这里有一片叫合心海的湖泊。每年,总有许多的情侣来到这里,在此祈愿,希望彼此之间的爱情也能如同合心海的由来一样,虽是历经万般艰险,归来仍是不离不弃,白首共婵娟。

import cv2
import numpy as np

img = cv2.imread("F://rst1.jpg")

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(10,10),anchor=(0,0))

img1 = cv2.dilate(img,kernel,iterations=6)
img2 = cv2.erode(img1,kernel,iterations=3)

cv2.imwrite("F://rst2.jpg",img2)

cv2.imshow("rst1",img)
cv2.imshow("rst2",img2)
cv2.waitKey(5000)
cv2.destroyAllWindows()
合心海

        鄙人认为,最珍贵的爱情不是一见钟情,而是经过浪淘沙洗,依然能够熠熠生光的对于彼此的忠贞与责任。只有经过了风霜的考验与时间的沉积,才是光泽透亮的珍珠。诚如东汉崔瑗所作铭文《座右铭》中所言:“在涅贵不淄,暧暧内含光。

结语:

本期文章,我们了解了膨胀与腐蚀的基本原理,也学习了操作过程中的一些注意点,比如中心点坐标的计算方式、结构元素框选超出被操作图像的各种情况等等,学习了erode与dilate函数中各参数的含义与设置方法,最后还用一个精美(老掉牙)的爱情故事为背景,给大家演示了开运算与闭运算的实际案例。总的来说,本期还是内容比较充实的,希望大家都能学习到知识,也能收获到快乐。

 好了以上就是所有的内容,希望大家多多关注点赞收藏,这对我有很大的帮助。谢谢大家了!

  好了,这里是Kamen Black 君。祝国康家安,大家下次再见喽!!!溜溜球~~

参考文章: 

OpenCV-Python图像处理:腐蚀和膨胀原理及erode、dilate函数介绍

;