形态学图像处理(二):开运算、闭运算、形态学梯度、顶帽、黑帽合辑

转载:http://blog.csdn.net/poem_qianmo/article/details/24599073

上篇文章中,我们重点了解了腐蚀和膨胀这两种最基本的形态学操作,而运用这两个基本操作,我们可以实现更高级的形态学变换。

所以,本文的主角是OpenCV中的morphologyEx函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算、形态学梯度、“顶帽”、“黑帽”等等。

 先上几张示例程序的截图吧:


有没有很熟悉这张图?没错,这就是最近热映的电影Captain America~

下面这张图的效果就有些凶残了:

OK,截图先看到这里。在正文之前先来唠唠和主题相关的事情。


第一件事,OpenCV最新版本更新到了2.4.9。


在写这篇博文的两天之前(4月25日上午),OpenCV官网页面显示最新版本还是2.4.8,但是通过浅墨细心地发现,文档页面的标题已经悄悄而低调地改成了2.4.9.所以我们当时应该可以去断定,OpenCV2.4.9应该马上就要和我们见面了。

 

果然,OpenCV2.4.9就在两天后(4月27日),正式在OpenCV官方网站上上线了。现在转到OpenCV的官方主页,赫然发现最新版本已然显示为2.4.9:

这是OpenCV的官方主页传送门:http://opencv.org/

大家可以自己前去看看以及下载最新版本的OpenCV。如果不出意外的话呢,下次文章我们就将紧跟时代,用上最新版本的OpenCV2.4.9进行讲解和程序的书写,所以,大家在看这篇文章之后呢,可以去下载当前最新的2.4.9版本并装上配置好。




第二件事,是浅墨想跟大家做一个关于OpenCV系列文章的书写内容和风格的思想汇报。


是这样的,浅墨发现最近几期写出来的文章有些偏离自己开始开这个专栏的最初的愿望——原理和概念部分占的比重有些大,有些弱化OpenCV实际的使用。

写这些博文的初心是教大家如何使用OpenCV来写代码,原理部分我想很多朋友应该多少都懂,就算某些同学对某些概念有些模糊,大家也完全可以带着关键词句去google或者百度。

浅墨的想法是,以后的专栏文章原理部分尽量从简,“深入”的源码剖析部分也是从简,重点突出“浅出”部分,让大家快速上手OpenCV函数的使用,这样浅墨的工作量也会小很多,更新也会更勤。

PS:浅墨其实每次在写图像处理原理部分的时候都特纠结,因为浅墨其实感兴趣的和大家一样,也是如何写代码,而不是那些多多少少让人提不起兴趣来的图像处理公式和概念。这往往就照成了博文更新的拖延症。

所以呢,在浅墨以后写的OpenCV文章中,原理和深入部分我们就点到为止,文章的拳头内容是“浅出”部分,重点教大家如何快速上手OpenCV API。我想这也是大家一直期待和想要看到的浅墨出品的文章的样子吧。:)

OK,大概就是这些。我们开始今天的正题。






一、理论与概念讲解——从现象到本质



首先呢,要知道形态学的高级形态,往往都是建立在腐蚀和膨胀这两个基本操作之上的。而关于腐蚀和膨胀,概念和细节以及相关代码可以看浅墨之前写的这篇文章:


【OpenCV入门教程之十】 形态学图像处理(一):膨胀与腐蚀


对膨胀和腐蚀心中有数了,接下来的高级形态学操作,应该就不难理解。

另外,为了下面对比和演示以及理解的方便,浅墨自己制作了一张毛笔字图,这里先上原图:

 

 OK,我们开始讲解。





1.1 开运算(Opening Operation)

 



开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。其数学表达式如下:




开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。效果图是这样的:

实际效果图:

 




1.2 闭运算(Closing Operation)


先膨胀后腐蚀的过程称为闭运算(Closing Operation),其数学表达式如下:

 

闭运算能够排除小型黑洞(黑色区域)。效果图如下所示:

 

实际效果图:





1.3 形态学梯度(MorphologicalGradient)


形态学梯度(Morphological Gradient)为膨胀图与腐蚀图之差,数学表达式如下:

 

对二值图像进行这一操作可以将团块(blob)的边缘突出出来。我们可以用形态学梯度来保留物体的边缘轮廓,如下所示:

 

实际素材效果图:





1.4 顶帽(Top Hat)


顶帽运算(Top Hat)又常常被译为”礼帽“运算。为原图像与上文刚刚介绍的“开运算“的结果图之差,数学表达式如下:

因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。

顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。

如下所示:

素材效果图:

 

 



1.5 黑帽(Black Hat)



黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差。数学表达式为:


黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。

所以,黑帽运算用来分离比邻近点暗一些的斑块。非常完美的轮廓效果图:

 

实际素材效果图:





 

二、深入——OpenCV源码分析溯源




本文的主角是OpenCV中的morphologyEx函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。这一节我们来一起看一下morphologyEx函数的源代码。

[cpp]  view plain copy print ?
  1. //-----------------------------------【erode()函数中文注释版源代码】----------------------------    
  2. //   说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码    
  3. //   OpenCV源代码版本:2.4.8    
  4. //   源码路径:…\opencv\sources\modules\imgproc\src\morph.cpp    
  5. //   源文件中如下代码的起始行数:1369行    
  6. //   中文注释by浅墨    
  7. //--------------------------------------------------------------------------------------------------------     
  8. void cv::morphologyEx( InputArray _src,OutputArray _dst, int op,  
  9.                        InputArray kernel, Pointanchor, int iterations,  
  10.                        int borderType, constScalar& borderValue )  
  11. {  
  12. //拷贝Mat数据到临时变量  
  13.    Mat src = _src.getMat(), temp;  
  14.    _dst.create(src.size(), src.type());  
  15.    Mat dst = _dst.getMat();  
  16.    
  17. //一个大switch,根据不同的标识符取不同的操作  
  18.    switch( op )  
  19.     {  
  20.    case MORPH_ERODE:  
  21.        erode( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  22.        break;  
  23.    case MORPH_DILATE:  
  24.        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  25.        break;  
  26.    case MORPH_OPEN:  
  27.        erode( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  28.        dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );  
  29.        break;  
  30.    case CV_MOP_CLOSE:  
  31.        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  32.        erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );  
  33.        break;  
  34.    case CV_MOP_GRADIENT:  
  35.        erode( src, temp, kernel, anchor, iterations, borderType, borderValue );  
  36.        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  37.        dst -= temp;  
  38.        break;  
  39.    case CV_MOP_TOPHAT:  
  40.        if( src.data != dst.data )  
  41.            temp = dst;  
  42.        erode( src, temp, kernel, anchor, iterations, borderType, borderValue );  
  43.         dilate( temp, temp, kernel, anchor,iterations, borderType, borderValue );  
  44.        dst = src - temp;  
  45.        break;  
  46.    case CV_MOP_BLACKHAT:  
  47.        if( src.data != dst.data )  
  48.            temp = dst;  
  49.        dilate( src, temp, kernel, anchor, iterations, borderType, borderValue);  
  50.        erode( temp, temp, kernel, anchor, iterations, borderType, borderValue);  
  51.        dst = temp - src;  
  52.        break;  
  53.    default:  
  54.        CV_Error( CV_StsBadArg, "unknown morphological operation" );  
  55.     }  
  56. }  

看上面的源码可以发现,其实morphologyEx函数其实就是内部一个大switch而已。根据不同的标识符取不同的操作。比如开运算MORPH_OPEN,按我们上文中讲解的数学表达式,就是先腐蚀后膨胀,即依次调用erode和dilate函数,为非常简明干净的代码。

 




 


三、浅出——API函数快速上手

 



3.1 morphologyEx函数详解



上面我们已经讲到,morphologyEx函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。这一节我们来了解它的参数意义和使用方法。

[cpp]  view plain copy print ?
  1. C++: void morphologyEx(  
  2. InputArray src,  
  3. OutputArray dst,  
  4. int op,  
  5. InputArraykernel,  
  6. Pointanchor=Point(-1,-1),  
  7. intiterations=1,  
  8. intborderType=BORDER_CONSTANT,  
  9. constScalar& borderValue=morphologyDefaultBorderValue() );  

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
  • 第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
  • 第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
    • MORPH_OPEN – 开运算(Opening operation)
    • MORPH_CLOSE – 闭运算(Closing operation)
    • MORPH_GRADIENT -形态学梯度(Morphological gradient)
    • MORPH_TOPHAT - “顶帽”(“Top hat”)
    • MORPH_BLACKHAT - “黑帽”(“Black hat“)

另有CV版本的标识符也可选择,如CV_MOP_CLOSE,CV_MOP_GRADIENT,CV_MOP_TOPHAT,CV_MOP_BLACKHAT,这应该是OpenCV1.0系列版本遗留下来的标识符,和上面的“MORPH_OPEN”一样的效果。

 

  • 第四个参数,InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。关于getStructuringElement我们上篇文章中讲过了,这里为了大家参阅方便,再写一遍:

其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:

  • 矩形: MORPH_RECT
  • 交叉形: MORPH_CROSS
  • 椭圆形: MORPH_ELLIPSE

而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。

我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。

getStructuringElement函数相关的调用示例代码如下:

[cpp]  view plain copy print ?
  1. int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸  
  2.    
  3. //获取自定义核  
  4. Mat element =getStructuringElement(MORPH_RECT,  
  5.        Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),  
  6.        Point(g_nStructElementSize, g_nStructElementSize ));  


调用这样之后,我们便可以在接下来调用erode、dilate或morphologyEx函数时,kernel参数填保存getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。

  • 第五个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
  • 第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
  • 第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
  • 第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

其中的这些操作都可以进行就地(in-place)操作。且对于多通道图像,每一个通道都是单独进行操作。

 OK,讲解完毕,下面就是使用的范例。



高能预警!高能预警!高能预警!

一大波示例代码正在逼近。

为了方便大家需要的时候随时取用。下面我们依次列举出开运算,闭运算,形态学梯度,顶帽,黑帽,腐蚀,膨胀的效果实现简化版完整代码。

其实说白了,这些代码基本上内容一致,其实就是改一下morphologyEx里面的第三个标识符参数而已。核都是选的MORPH_RECT,矩形元素结构。

另外,通过看源码我们发现,最基本的腐蚀和膨胀操作也可以用morphologyEx函数来实现,他们由morphologyEx函数源码中switch的前两个case来实现(虽然在case体内就是简单地各自调用了一下erode和dilation函数,但还是有写出来的必要)。所以在这里,我们也用morphologyEx再重新来实现一遍他们。

按着顺序来列出吧,就直接列详细注释好的代码和运行结果了。

 




3.2 开运算示例程序


OpenCV中调用morphologyEx函数进行开运算操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include  
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】开运算");   
  21.        namedWindow("【效果图】开运算");   
  22.        //显示原始图   
  23.        imshow("【原始图】开运算", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_OPEN, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】开运算", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:







3.3 闭运算示例程序



OpenCV中调用morphologyEx函数进行闭运算操作的示例程序如下:


[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include   
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】闭运算");   
  21.        namedWindow("【效果图】闭运算");   
  22.        //显示原始图   
  23.        imshow("【原始图】闭运算", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_CLOSE, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】闭运算", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:





3.4 形态学梯度示例程序



OpenCV中调用morphologyEx函数进行形态学梯度操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include  
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】形态学梯度");   
  21.        namedWindow("【效果图】形态学梯度");   
  22.        //显示原始图   
  23.        imshow("【原始图】形态学梯度", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_GRADIENT, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】形态学梯度", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:






3.5 顶帽运算(Top Hat)示例程序



OpenCV中调用morphologyEx函数进行顶帽运算操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include  
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】顶帽运算");   
  21.        namedWindow("【效果图】顶帽运算");   
  22.        //显示原始图   
  23.        imshow("【原始图】顶帽运算", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_TOPHAT, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】顶帽运算", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:




3.6 黑帽运算(BlackHat)示例程序


OpenCV中调用morphologyEx函数进行黑帽运算操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include   
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】黑帽运算");   
  21.        namedWindow("【效果图】黑帽运算");   
  22.        //显示原始图   
  23.        imshow("【原始图】黑帽运算", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_BLACKHAT, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】黑帽运算", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:



 



3.7 腐蚀(morphologyEx调用版)示例程序



OpenCV中调用morphologyEx函数进行腐蚀操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include   
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】腐蚀");   
  21.        namedWindow("【效果图】腐蚀");   
  22.        //显示原始图   
  23.        imshow("【原始图】腐蚀", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_ERODE, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】腐蚀", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:



 



3.8 膨胀(morphologyEx调用版)示例程序



OpenCV中调用morphologyEx函数进行膨胀操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include  
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】膨胀");   
  21.        namedWindow("【效果图】膨胀");   
  22.        //显示原始图   
  23.        imshow("【原始图】膨胀", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_DILATE, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】膨胀", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:



 








四、综合示例——在实战中熟稔

 



依然是每篇文章都会配给大家的一个详细注释的博文配套示例程序,把这篇文章中介绍的知识点以代码为载体,展现给大家。

这个示例程序中,一共有四个显示图像的窗口。

原始图一个,开/闭运算为一个,腐蚀/膨胀为一个,顶帽/黑帽运算为一个。

分别使用滚动条,来控制得到的形态学效果。且迭代值为10的时候,为中间。

另外,还可以通过键盘按键1,2,3以及空格,来调节成不同的元素结构(矩形、椭圆、十字形)。说明页面如下:





废话不多说,上代码吧:


[cpp]  view plain copy print ?
  1. //-----------------------------------【程序说明】----------------------------------------------  
  2. //      程序名称::《【OpenCV入门教程之十一】形态学图像处理(一):膨胀与腐蚀  》 博文配套源码   
  3. //      开发所用IDE版本:Visual Studio 2010  
  4. //        开发所用OpenCV版本:    2.4.8  
  5. //      2014年4月25日 Create by 浅墨  
  6. //----------------------------------------------------------------------------------------------  
  7.   
  8. //-----------------------------------【头文件包含部分】---------------------------------------  
  9. //      描述:包含程序所依赖的头文件  
  10. //----------------------------------------------------------------------------------------------   
  11. #include   
  12. #include   
  13. #include   
  14.   
  15. //-----------------------------------【命名空间声明部分】--------------------------------------  
  16. //      描述:包含程序所使用的命名空间  
  17. //-----------------------------------------------------------------------------------------------   
  18. using namespace std;  
  19. using namespace cv;  
  20.   
  21.   
  22. //-----------------------------------【全局变量声明部分】--------------------------------------  
  23. //      描述:全局变量声明  
  24. //-----------------------------------------------------------------------------------------------  
  25. Mat g_srcImage, g_dstImage;//原始图和效果图  
  26. int g_nElementShape = MORPH_RECT;//元素结构的形状  
  27.   
  28. //变量接收的TrackBar位置参数  
  29. int g_nMaxIterationNum = 10;  
  30. int g_nOpenCloseNum = 0;  
  31. int g_nErodeDilateNum = 0;  
  32. int g_nTopBlackHatNum = 0;  
  33.   
  34.   
  35.   
  36. //-----------------------------------【全局函数声明部分】--------------------------------------  
  37. //      描述:全局函数声明  
  38. //-----------------------------------------------------------------------------------------------  
  39. static void on_OpenClose(intvoid*);//回调函数  
  40. static void on_ErodeDilate(intvoid*);//回调函数  
  41. static void on_TopBlackHat(intvoid*);//回调函数  
  42. static void ShowHelpText();//帮助文字显示  
  43.   
  44.   
  45. //-----------------------------------【main( )函数】--------------------------------------------  
  46. //      描述:控制台应用程序的入口函数,我们的程序从这里开始  
  47. //-----------------------------------------------------------------------------------------------  
  48. int main( )  
  49. {  
  50.     //改变console字体颜色  
  51.     system("color 2F");    
  52.   
  53.     ShowHelpText();  
  54.   
  55.     //载入原图  
  56.     g_srcImage = imread("1.jpg");//工程目录下需要有一张名为1.jpg的素材图  
  57.     if( !g_srcImage.data ) { printf("Oh,no,读取srcImage错误~! \n"); return false; }  
  58.   
  59.     //显示原始图  
  60.     namedWindow("【原始图】");  
  61.     imshow("【原始图】", g_srcImage);  
  62.   
  63.     //创建三个窗口  
  64.     namedWindow("【开运算/闭运算】",1);  
  65.     namedWindow("【腐蚀/膨胀】",1);  
  66.     namedWindow("【顶帽/黑帽】",1);  
  67.   
  68.     //参数赋值  
  69.     g_nOpenCloseNum=9;  
  70.     g_nErodeDilateNum=9;  
  71.     g_nTopBlackHatNum=2;  
  72.   
  73.     //分别为三个窗口创建滚动条  
  74.     createTrackbar("迭代值""【开运算/闭运算】",&g_nOpenCloseNum,g_nMaxIterationNum*2+1,on_OpenClose);  
  75.     createTrackbar("迭代值""【腐蚀/膨胀】",&g_nErodeDilateNum,g_nMaxIterationNum*2+1,on_ErodeDilate);  
  76.     createTrackbar("迭代值""【顶帽/黑帽】",&g_nTopBlackHatNum,g_nMaxIterationNum*2+1,on_TopBlackHat);  
  77.   
  78.     //轮询获取按键信息  
  79.     while(1)  
  80.     {  
  81.         int c;  
  82.   
  83.         //执行回调函数  
  84.         on_OpenClose(g_nOpenCloseNum, 0);  
  85.         on_ErodeDilate(g_nErodeDilateNum, 0);  
  86.         on_TopBlackHat(g_nTopBlackHatNum,0);  
  87.   
  88.         //获取按键  
  89.         c = waitKey(0);  
  90.   
  91.         //按下键盘按键Q或者ESC,程序退出  
  92.         if( (char)c == 'q'||(char)c == 27 )  
  93.             break;  
  94.         //按下键盘按键1,使用椭圆(Elliptic)结构元素结构元素MORPH_ELLIPSE  
  95.         if( (char)c == 49 )//键盘按键1的ASII码为49  
  96.             g_nElementShape = MORPH_ELLIPSE;  
  97.         //按下键盘按键2,使用矩形(Rectangle)结构元素MORPH_RECT  
  98.         else if( (char)c == 50 )//键盘按键2的ASII码为50  
  99.             g_nElementShape = MORPH_RECT;  
  100.         //按下键盘按键3,使用十字形(Cross-shaped)结构元素MORPH_CROSS  
  101.         else if( (char)c == 51 )//键盘按键3的ASII码为51  
  102.             g_nElementShape = MORPH_CROSS;  
  103.         //按下键盘按键space,在矩形、椭圆、十字形结构元素中循环  
  104.         else if( (char)c == ' ' )  
  105.             g_nElementShape = (g_nElementShape + 1) % 3;  
  106.     }  
  107.   
  108.     return 0;  
  109. }  
  110.   
  111.   
  112. //-----------------------------------【on_OpenClose( )函数】----------------------------------  
  113. //      描述:【开运算/闭运算】窗口的回调函数  
  114. //-----------------------------------------------------------------------------------------------  
  115. static void on_OpenClose(intvoid*)  
  116. {  
  117.     //偏移量的定义  
  118.     int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量  
  119.     int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值  
  120.     //自定义核  
  121.     Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
  122.     //进行操作  
  123.     if( offset < 0 )  
  124.         morphologyEx(g_srcImage, g_dstImage, CV_MOP_OPEN, element);  
  125.     else  
  126.         morphologyEx(g_srcImage, g_dstImage, CV_MOP_CLOSE, element);  
  127.     //显示图像  
  128.     imshow("【开运算/闭运算】",g_dstImage);  
  129. }  
  130.   
  131.   
  132. //-----------------------------------【on_ErodeDilate( )函数】----------------------------------  
  133. //      描述:【腐蚀/膨胀】窗口的回调函数  
  134. //-----------------------------------------------------------------------------------------------  
  135. static void on_ErodeDilate(intvoid*)  
  136. {  
  137.     //偏移量的定义  
  138.     int offset = g_nErodeDilateNum - g_nMaxIterationNum;    //偏移量  
  139.     int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值  
  140.     //自定义核  
  141.     Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
  142.     //进行操作  
  143.     if( offset < 0 )  
  144.         erode(g_srcImage, g_dstImage, element);  
  145.     else  
  146.         dilate(g_srcImage, g_dstImage, element);  
  147.     //显示图像  
  148.     imshow("【腐蚀/膨胀】",g_dstImage);  
  149. }  
  150.   
  151.   
  152. //-----------------------------------【on_TopBlackHat( )函数】--------------------------------  
  153. //      描述:【顶帽运算/黑帽运算】窗口的回调函数  
  154. //----------------------------------------------------------------------------------------------  
  155. static void on_TopBlackHat(intvoid*)  
  156. {  
  157.     //偏移量的定义  
  158.     int offset = g_nTopBlackHatNum - g_nMaxIterationNum;//偏移量  
  159.     int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值  
  160.     //自定义核  
  161.     Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
  162.     //进行操作  
  163.     if( offset < 0 )  
  164.         morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT , element);  
  165.     else  
  166.         morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);  
  167.     //显示图像  
  168.     imshow("【顶帽/黑帽】",g_dstImage);  
  169. }  
  170.   
  171. //-----------------------------------【ShowHelpText( )函数】----------------------------------  
  172. //      描述:输出一些帮助信息  
  173. //----------------------------------------------------------------------------------------------  
  174. static void ShowHelpText()  
  175. {  
  176. //输出一些帮助信息  
  177.     printf("\n\n\n\t请调整滚动条观察图像效果~\n\n");  
  178.     printf( "\n\n\t按键操作说明: \n\n"  
  179.         "\t\t键盘按键【ESC】或者【Q】- 退出程序\n"  
  180.         "\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n"  
  181.         "\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n"  
  182.         "\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n"  
  183.         "\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n"  
  184.         "\n\n\t\t\t\t\t\t\t\t by浅墨"  
  185.         );  
  186. }  

放出一些效果图:

首先是原图:


非常帅气的Captain America有木有!

腐蚀效果图:


膨胀效果图:



开运算效果图:



闭运算效果图:



顶帽运算效果图:


黑帽运算效果图:



好的,就放出这些效果图吧,具体更多的运行效果大家就自己下载示例程序回去玩~

 

本篇文章的配套源代码请点击这里下载:


【浅墨OpenCV入门教程之十一】配套源代码下载

 


OK,今天的内容大概就是这些,我们下篇文章见:)



上篇文章中,我们重点了解了腐蚀和膨胀这两种最基本的形态学操作,而运用这两个基本操作,我们可以实现更高级的形态学变换。

所以,本文的主角是OpenCV中的morphologyEx函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算、形态学梯度、“顶帽”、“黑帽”等等。

 先上几张示例程序的截图吧:


有没有很熟悉这张图?没错,这就是最近热映的电影Captain America~

下面这张图的效果就有些凶残了:

OK,截图先看到这里。在正文之前先来唠唠和主题相关的事情。


第一件事,OpenCV最新版本更新到了2.4.9。


在写这篇博文的两天之前(4月25日上午),OpenCV官网页面显示最新版本还是2.4.8,但是通过浅墨细心地发现,文档页面的标题已经悄悄而低调地改成了2.4.9.所以我们当时应该可以去断定,OpenCV2.4.9应该马上就要和我们见面了。

 

果然,OpenCV2.4.9就在两天后(4月27日),正式在OpenCV官方网站上上线了。现在转到OpenCV的官方主页,赫然发现最新版本已然显示为2.4.9:

这是OpenCV的官方主页传送门:http://opencv.org/

大家可以自己前去看看以及下载最新版本的OpenCV。如果不出意外的话呢,下次文章我们就将紧跟时代,用上最新版本的OpenCV2.4.9进行讲解和程序的书写,所以,大家在看这篇文章之后呢,可以去下载当前最新的2.4.9版本并装上配置好。




第二件事,是浅墨想跟大家做一个关于OpenCV系列文章的书写内容和风格的思想汇报。


是这样的,浅墨发现最近几期写出来的文章有些偏离自己开始开这个专栏的最初的愿望——原理和概念部分占的比重有些大,有些弱化OpenCV实际的使用。

写这些博文的初心是教大家如何使用OpenCV来写代码,原理部分我想很多朋友应该多少都懂,就算某些同学对某些概念有些模糊,大家也完全可以带着关键词句去google或者百度。

浅墨的想法是,以后的专栏文章原理部分尽量从简,“深入”的源码剖析部分也是从简,重点突出“浅出”部分,让大家快速上手OpenCV函数的使用,这样浅墨的工作量也会小很多,更新也会更勤。

PS:浅墨其实每次在写图像处理原理部分的时候都特纠结,因为浅墨其实感兴趣的和大家一样,也是如何写代码,而不是那些多多少少让人提不起兴趣来的图像处理公式和概念。这往往就照成了博文更新的拖延症。

所以呢,在浅墨以后写的OpenCV文章中,原理和深入部分我们就点到为止,文章的拳头内容是“浅出”部分,重点教大家如何快速上手OpenCV API。我想这也是大家一直期待和想要看到的浅墨出品的文章的样子吧。:)

OK,大概就是这些。我们开始今天的正题。






一、理论与概念讲解——从现象到本质



首先呢,要知道形态学的高级形态,往往都是建立在腐蚀和膨胀这两个基本操作之上的。而关于腐蚀和膨胀,概念和细节以及相关代码可以看浅墨之前写的这篇文章:


【OpenCV入门教程之十】 形态学图像处理(一):膨胀与腐蚀


对膨胀和腐蚀心中有数了,接下来的高级形态学操作,应该就不难理解。

另外,为了下面对比和演示以及理解的方便,浅墨自己制作了一张毛笔字图,这里先上原图:

 

 OK,我们开始讲解。





1.1 开运算(Opening Operation)

 



开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。其数学表达式如下:




开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。效果图是这样的:

实际效果图:

 




1.2 闭运算(Closing Operation)


先膨胀后腐蚀的过程称为闭运算(Closing Operation),其数学表达式如下:

 

闭运算能够排除小型黑洞(黑色区域)。效果图如下所示:

 

实际效果图:





1.3 形态学梯度(MorphologicalGradient)


形态学梯度(Morphological Gradient)为膨胀图与腐蚀图之差,数学表达式如下:

 

对二值图像进行这一操作可以将团块(blob)的边缘突出出来。我们可以用形态学梯度来保留物体的边缘轮廓,如下所示:

 

实际素材效果图:





1.4 顶帽(Top Hat)


顶帽运算(Top Hat)又常常被译为”礼帽“运算。为原图像与上文刚刚介绍的“开运算“的结果图之差,数学表达式如下:

因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。

顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。

如下所示:

素材效果图:

 

 



1.5 黑帽(Black Hat)



黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差。数学表达式为:


黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。

所以,黑帽运算用来分离比邻近点暗一些的斑块。非常完美的轮廓效果图:

 

实际素材效果图:





 

二、深入——OpenCV源码分析溯源




本文的主角是OpenCV中的morphologyEx函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。这一节我们来一起看一下morphologyEx函数的源代码。

[cpp]  view plain copy print ?
  1. //-----------------------------------【erode()函数中文注释版源代码】----------------------------    
  2. //   说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码    
  3. //   OpenCV源代码版本:2.4.8    
  4. //   源码路径:…\opencv\sources\modules\imgproc\src\morph.cpp    
  5. //   源文件中如下代码的起始行数:1369行    
  6. //   中文注释by浅墨    
  7. //--------------------------------------------------------------------------------------------------------     
  8. void cv::morphologyEx( InputArray _src,OutputArray _dst, int op,  
  9.                        InputArray kernel, Pointanchor, int iterations,  
  10.                        int borderType, constScalar& borderValue )  
  11. {  
  12. //拷贝Mat数据到临时变量  
  13.    Mat src = _src.getMat(), temp;  
  14.    _dst.create(src.size(), src.type());  
  15.    Mat dst = _dst.getMat();  
  16.    
  17. //一个大switch,根据不同的标识符取不同的操作  
  18.    switch( op )  
  19.     {  
  20.    case MORPH_ERODE:  
  21.        erode( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  22.        break;  
  23.    case MORPH_DILATE:  
  24.        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  25.        break;  
  26.    case MORPH_OPEN:  
  27.        erode( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  28.        dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );  
  29.        break;  
  30.    case CV_MOP_CLOSE:  
  31.        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  32.        erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );  
  33.        break;  
  34.    case CV_MOP_GRADIENT:  
  35.        erode( src, temp, kernel, anchor, iterations, borderType, borderValue );  
  36.        dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  
  37.        dst -= temp;  
  38.        break;  
  39.    case CV_MOP_TOPHAT:  
  40.        if( src.data != dst.data )  
  41.            temp = dst;  
  42.        erode( src, temp, kernel, anchor, iterations, borderType, borderValue );  
  43.         dilate( temp, temp, kernel, anchor,iterations, borderType, borderValue );  
  44.        dst = src - temp;  
  45.        break;  
  46.    case CV_MOP_BLACKHAT:  
  47.        if( src.data != dst.data )  
  48.            temp = dst;  
  49.        dilate( src, temp, kernel, anchor, iterations, borderType, borderValue);  
  50.        erode( temp, temp, kernel, anchor, iterations, borderType, borderValue);  
  51.        dst = temp - src;  
  52.        break;  
  53.    default:  
  54.        CV_Error( CV_StsBadArg, "unknown morphological operation" );  
  55.     }  
  56. }  

看上面的源码可以发现,其实morphologyEx函数其实就是内部一个大switch而已。根据不同的标识符取不同的操作。比如开运算MORPH_OPEN,按我们上文中讲解的数学表达式,就是先腐蚀后膨胀,即依次调用erode和dilate函数,为非常简明干净的代码。

 




 


三、浅出——API函数快速上手

 



3.1 morphologyEx函数详解



上面我们已经讲到,morphologyEx函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。这一节我们来了解它的参数意义和使用方法。

[cpp]  view plain copy print ?
  1. C++: void morphologyEx(  
  2. InputArray src,  
  3. OutputArray dst,  
  4. int op,  
  5. InputArraykernel,  
  6. Pointanchor=Point(-1,-1),  
  7. intiterations=1,  
  8. intborderType=BORDER_CONSTANT,  
  9. constScalar& borderValue=morphologyDefaultBorderValue() );  

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
  • 第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
  • 第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
    • MORPH_OPEN – 开运算(Opening operation)
    • MORPH_CLOSE – 闭运算(Closing operation)
    • MORPH_GRADIENT -形态学梯度(Morphological gradient)
    • MORPH_TOPHAT - “顶帽”(“Top hat”)
    • MORPH_BLACKHAT - “黑帽”(“Black hat“)

另有CV版本的标识符也可选择,如CV_MOP_CLOSE,CV_MOP_GRADIENT,CV_MOP_TOPHAT,CV_MOP_BLACKHAT,这应该是OpenCV1.0系列版本遗留下来的标识符,和上面的“MORPH_OPEN”一样的效果。

 

  • 第四个参数,InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。关于getStructuringElement我们上篇文章中讲过了,这里为了大家参阅方便,再写一遍:

其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:

  • 矩形: MORPH_RECT
  • 交叉形: MORPH_CROSS
  • 椭圆形: MORPH_ELLIPSE

而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。

我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。

getStructuringElement函数相关的调用示例代码如下:

[cpp]  view plain copy print ?
  1. int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸  
  2.    
  3. //获取自定义核  
  4. Mat element =getStructuringElement(MORPH_RECT,  
  5.        Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),  
  6.        Point(g_nStructElementSize, g_nStructElementSize ));  


调用这样之后,我们便可以在接下来调用erode、dilate或morphologyEx函数时,kernel参数填保存getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。

  • 第五个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
  • 第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
  • 第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
  • 第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

其中的这些操作都可以进行就地(in-place)操作。且对于多通道图像,每一个通道都是单独进行操作。

 OK,讲解完毕,下面就是使用的范例。



高能预警!高能预警!高能预警!

一大波示例代码正在逼近。

为了方便大家需要的时候随时取用。下面我们依次列举出开运算,闭运算,形态学梯度,顶帽,黑帽,腐蚀,膨胀的效果实现简化版完整代码。

其实说白了,这些代码基本上内容一致,其实就是改一下morphologyEx里面的第三个标识符参数而已。核都是选的MORPH_RECT,矩形元素结构。

另外,通过看源码我们发现,最基本的腐蚀和膨胀操作也可以用morphologyEx函数来实现,他们由morphologyEx函数源码中switch的前两个case来实现(虽然在case体内就是简单地各自调用了一下erode和dilation函数,但还是有写出来的必要)。所以在这里,我们也用morphologyEx再重新来实现一遍他们。

按着顺序来列出吧,就直接列详细注释好的代码和运行结果了。

 




3.2 开运算示例程序


OpenCV中调用morphologyEx函数进行开运算操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include  
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】开运算");   
  21.        namedWindow("【效果图】开运算");   
  22.        //显示原始图   
  23.        imshow("【原始图】开运算", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_OPEN, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】开运算", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:







3.3 闭运算示例程序



OpenCV中调用morphologyEx函数进行闭运算操作的示例程序如下:


[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include   
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】闭运算");   
  21.        namedWindow("【效果图】闭运算");   
  22.        //显示原始图   
  23.        imshow("【原始图】闭运算", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_CLOSE, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】闭运算", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:





3.4 形态学梯度示例程序



OpenCV中调用morphologyEx函数进行形态学梯度操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include  
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】形态学梯度");   
  21.        namedWindow("【效果图】形态学梯度");   
  22.        //显示原始图   
  23.        imshow("【原始图】形态学梯度", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_GRADIENT, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】形态学梯度", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:






3.5 顶帽运算(Top Hat)示例程序



OpenCV中调用morphologyEx函数进行顶帽运算操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include  
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】顶帽运算");   
  21.        namedWindow("【效果图】顶帽运算");   
  22.        //显示原始图   
  23.        imshow("【原始图】顶帽运算", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_TOPHAT, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】顶帽运算", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:




3.6 黑帽运算(BlackHat)示例程序


OpenCV中调用morphologyEx函数进行黑帽运算操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include   
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】黑帽运算");   
  21.        namedWindow("【效果图】黑帽运算");   
  22.        //显示原始图   
  23.        imshow("【原始图】黑帽运算", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_BLACKHAT, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】黑帽运算", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:



 



3.7 腐蚀(morphologyEx调用版)示例程序



OpenCV中调用morphologyEx函数进行腐蚀操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include   
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】腐蚀");   
  21.        namedWindow("【效果图】腐蚀");   
  22.        //显示原始图   
  23.        imshow("【原始图】腐蚀", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_ERODE, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】腐蚀", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:



 



3.8 膨胀(morphologyEx调用版)示例程序



OpenCV中调用morphologyEx函数进行膨胀操作的示例程序如下:

[cpp]  view plain copy print ?
  1. //-----------------------------------【头文件包含部分】---------------------------------------  
  2. //            描述:包含程序所依赖的头文件  
  3. //----------------------------------------------------------------------------------------------  
  4. #include   
  5. #include  
  6. #include  
  7.    
  8. //-----------------------------------【命名空间声明部分】---------------------------------------  
  9. //            描述:包含程序所使用的命名空间  
  10. //-----------------------------------------------------------------------------------------------  
  11. using namespace cv;  
  12. //-----------------------------------【main( )函数】--------------------------------------------  
  13. //            描述:控制台应用程序的入口函数,我们的程序从这里开始  
  14. //-----------------------------------------------------------------------------------------------  
  15. int main( )  
  16. {  
  17.        //载入原始图    
  18.        Mat image = imread("1.jpg");  //工程目录下应该有一张名为1.jpg的素材图  
  19.        //创建窗口    
  20.        namedWindow("【原始图】膨胀");   
  21.        namedWindow("【效果图】膨胀");   
  22.        //显示原始图   
  23.        imshow("【原始图】膨胀", image);   
  24.        //定义核  
  25.        Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));   
  26.        //进行形态学操作  
  27.        morphologyEx(image,image, MORPH_DILATE, element);  
  28.        //显示效果图   
  29.        imshow("【效果图】膨胀", image);   
  30.    
  31.        waitKey(0);   
  32.    
  33.        return 0;   
  34. }  

运行效果图:



 








四、综合示例——在实战中熟稔

 



依然是每篇文章都会配给大家的一个详细注释的博文配套示例程序,把这篇文章中介绍的知识点以代码为载体,展现给大家。

这个示例程序中,一共有四个显示图像的窗口。

原始图一个,开/闭运算为一个,腐蚀/膨胀为一个,顶帽/黑帽运算为一个。

分别使用滚动条,来控制得到的形态学效果。且迭代值为10的时候,为中间。

另外,还可以通过键盘按键1,2,3以及空格,来调节成不同的元素结构(矩形、椭圆、十字形)。说明页面如下:





废话不多说,上代码吧:


[cpp]  view plain copy print ?
  1. //-----------------------------------【程序说明】----------------------------------------------  
  2. //      程序名称::《【OpenCV入门教程之十一】形态学图像处理(一):膨胀与腐蚀  》 博文配套源码   
  3. //      开发所用IDE版本:Visual Studio 2010  
  4. //        开发所用OpenCV版本:    2.4.8  
  5. //      2014年4月25日 Create by 浅墨  
  6. //----------------------------------------------------------------------------------------------  
  7.   
  8. //-----------------------------------【头文件包含部分】---------------------------------------  
  9. //      描述:包含程序所依赖的头文件  
  10. //----------------------------------------------------------------------------------------------   
  11. #include   
  12. #include   
  13. #include   
  14.   
  15. //-----------------------------------【命名空间声明部分】--------------------------------------  
  16. //      描述:包含程序所使用的命名空间  
  17. //-----------------------------------------------------------------------------------------------   
  18. using namespace std;  
  19. using namespace cv;  
  20.   
  21.   
  22. //-----------------------------------【全局变量声明部分】--------------------------------------  
  23. //      描述:全局变量声明  
  24. //-----------------------------------------------------------------------------------------------  
  25. Mat g_srcImage, g_dstImage;//原始图和效果图  
  26. int g_nElementShape = MORPH_RECT;//元素结构的形状  
  27.   
  28. //变量接收的TrackBar位置参数  
  29. int g_nMaxIterationNum = 10;  
  30. int g_nOpenCloseNum = 0;  
  31. int g_nErodeDilateNum = 0;  
  32. int g_nTopBlackHatNum = 0;  
  33.   
  34.   
  35.   
  36. //-----------------------------------【全局函数声明部分】--------------------------------------  
  37. //      描述:全局函数声明  
  38. //-----------------------------------------------------------------------------------------------  
  39. static void on_OpenClose(intvoid*);//回调函数  
  40. static void on_ErodeDilate(intvoid*);//回调函数  
  41. static void on_TopBlackHat(intvoid*);//回调函数  
  42. static void ShowHelpText();//帮助文字显示  
  43.   
  44.   
  45. //-----------------------------------【main( )函数】--------------------------------------------  
  46. //      描述:控制台应用程序的入口函数,我们的程序从这里开始  
  47. //-----------------------------------------------------------------------------------------------  
  48. int main( )  
  49. {  
  50.     //改变console字体颜色  
  51.     system("color 2F");    
  52.   
  53.     ShowHelpText();  
  54.   
  55.     //载入原图  
  56.     g_srcImage = imread("1.jpg");//工程目录下需要有一张名为1.jpg的素材图  
  57.     if( !g_srcImage.data ) { printf("Oh,no,读取srcImage错误~! \n"); return false; }  
  58.   
  59.     //显示原始图  
  60.     namedWindow("【原始图】");  
  61.     imshow("【原始图】", g_srcImage);  
  62.   
  63.     //创建三个窗口  
  64.     namedWindow("【开运算/闭运算】",1);  
  65.     namedWindow("【腐蚀/膨胀】",1);  
  66.     namedWindow("【顶帽/黑帽】",1);  
  67.   
  68.     //参数赋值  
  69.     g_nOpenCloseNum=9;  
  70.     g_nErodeDilateNum=9;  
  71.     g_nTopBlackHatNum=2;  
  72.   
  73.     //分别为三个窗口创建滚动条  
  74.     createTrackbar("迭代值""【开运算/闭运算】",&g_nOpenCloseNum,g_nMaxIterationNum*2+1,on_OpenClose);  
  75.     createTrackbar("迭代值""【腐蚀/膨胀】",&g_nErodeDilateNum,g_nMaxIterationNum*2+1,on_ErodeDilate);  
  76.     createTrackbar("迭代值""【顶帽/黑帽】",&g_nTopBlackHatNum,g_nMaxIterationNum*2+1,on_TopBlackHat);  
  77.   
  78.     //轮询获取按键信息  
  79.     while(1)  
  80.     {  
  81.         int c;  
  82.   
  83.         //执行回调函数  
  84.         on_OpenClose(g_nOpenCloseNum, 0);  
  85.         on_ErodeDilate(g_nErodeDilateNum, 0);  
  86.         on_TopBlackHat(g_nTopBlackHatNum,0);  
  87.   
  88.         //获取按键  
  89.         c = waitKey(0);  
  90.   
  91.         //按下键盘按键Q或者ESC,程序退出  
  92.         if( (char)c == 'q'||(char)c == 27 )  
  93.             break;  
  94.         //按下键盘按键1,使用椭圆(Elliptic)结构元素结构元素MORPH_ELLIPSE  
  95.         if( (char)c == 49 )//键盘按键1的ASII码为49  
  96.             g_nElementShape = MORPH_ELLIPSE;  
  97.         //按下键盘按键2,使用矩形(Rectangle)结构元素MORPH_RECT  
  98.         else if( (char)c == 50 )//键盘按键2的ASII码为50  
  99.             g_nElementShape = MORPH_RECT;  
  100.         //按下键盘按键3,使用十字形(Cross-shaped)结构元素MORPH_CROSS  
  101.         else if( (char)c == 51 )//键盘按键3的ASII码为51  
  102.             g_nElementShape = MORPH_CROSS;  
  103.         //按下键盘按键space,在矩形、椭圆、十字形结构元素中循环  
  104.         else if( (char)c == ' ' )  
  105.             g_nElementShape = (g_nElementShape + 1) % 3;  
  106.     }  
  107.   
  108.     return 0;  
  109. }  
  110.   
  111.   
  112. //-----------------------------------【on_OpenClose( )函数】----------------------------------  
  113. //      描述:【开运算/闭运算】窗口的回调函数  
  114. //-----------------------------------------------------------------------------------------------  
  115. static void on_OpenClose(intvoid*)  
  116. {  
  117.     //偏移量的定义  
  118.     int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量  
  119.     int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值  
  120.     //自定义核  
  121.     Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
  122.     //进行操作  
  123.     if( offset < 0 )  
  124.         morphologyEx(g_srcImage, g_dstImage, CV_MOP_OPEN, element);  
  125.     else  
  126.         morphologyEx(g_srcImage, g_dstImage, CV_MOP_CLOSE, element);  
  127.     //显示图像  
  128.     imshow("【开运算/闭运算】",g_dstImage);  
  129. }  
  130.   
  131.   
  132. //-----------------------------------【on_ErodeDilate( )函数】----------------------------------  
  133. //      描述:【腐蚀/膨胀】窗口的回调函数  
  134. //-----------------------------------------------------------------------------------------------  
  135. static void on_ErodeDilate(intvoid*)  
  136. {  
  137.     //偏移量的定义  
  138.     int offset = g_nErodeDilateNum - g_nMaxIterationNum;    //偏移量  
  139.     int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值  
  140.     //自定义核  
  141.     Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
  142.     //进行操作  
  143.     if( offset < 0 )  
  144.         erode(g_srcImage, g_dstImage, element);  
  145.     else  
  146.         dilate(g_srcImage, g_dstImage, element);  
  147.     //显示图像  
  148.     imshow("【腐蚀/膨胀】",g_dstImage);  
  149. }  
  150.   
  151.   
  152. //-----------------------------------【on_TopBlackHat( )函数】--------------------------------  
  153. //      描述:【顶帽运算/黑帽运算】窗口的回调函数  
  154. //----------------------------------------------------------------------------------------------  
  155. static void on_TopBlackHat(intvoid*)  
  156. {  
  157.     //偏移量的定义  
  158.     int offset = g_nTopBlackHatNum - g_nMaxIterationNum;//偏移量  
  159.     int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值  
  160.     //自定义核  
  161.     Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
  162.     //进行操作  
  163.     if( offset < 0 )  
  164.         morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT , element);  
  165.     else  
  166.         morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);  
  167.     //显示图像  
  168.     imshow("【顶帽/黑帽】",g_dstImage);  
  169. }  
  170.   
  171. //-----------------------------------【ShowHelpText( )函数】----------------------------------  
  172. //      描述:输出一些帮助信息  
  173. //----------------------------------------------------------------------------------------------  
  174. static void ShowHelpText()  
  175. {  
  176. //输出一些帮助信息  
  177.     printf("\n\n\n\t请调整滚动条观察图像效果~\n\n");  
  178.     printf( "\n\n\t按键操作说明: \n\n"  
  179.         "\t\t键盘按键【ESC】或者【Q】- 退出程序\n"  
  180.         "\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n"  
  181.         "\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n"  
  182.         "\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n"  
  183.         "\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n"  
  184.         "\n\n\t\t\t\t\t\t\t\t by浅墨"  
  185.         );  
  186. }  

放出一些效果图:

首先是原图:


非常帅气的Captain America有木有!

腐蚀效果图:


膨胀效果图:



开运算效果图:



闭运算效果图:



顶帽运算效果图:


黑帽运算效果图:



好的,就放出这些效果图吧,具体更多的运行效果大家就自己下载示例程序回去玩~

 

本篇文章的配套源代码请点击这里下载:


【浅墨OpenCV入门教程之十一】配套源代码下载

 


OK,今天的内容大概就是这些,我们下篇文章见:)



你可能感兴趣的:(形态学图像处理(二):开运算、闭运算、形态学梯度、顶帽、黑帽合辑)