首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

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

  • 23-09-22 21:01
  • 3249
  • 7768
blog.csdn.net


本系列文章由@浅墨_毛星云 出品,转载请注明出处。  

文章链接: http://iyenn.com/rec/327415.html

作者:毛星云(浅墨)    邮箱: [email protected] 

写作当前博文时配套使用的OpenCV版本: 2.4.8



本篇文章中,我们一起探究了图像处理中,最基本的形态学运算——膨胀与腐蚀。浅墨在文章开头友情提醒,用人物照片做腐蚀和膨胀的素材图片得到的效果会比较惊悚,毁三观的,不建议尝试。。。。。。。。。。


OK,开始吧,依然是先放一张截图:





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



1.1 形态学概述

 

形态学(morphology)一词通常表示生物学的一个分支,该分支主要研究动植物的形态和结构。而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。

数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论。其基本的运算包括:二值腐蚀和膨胀、二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、流域变换、灰值腐蚀和膨胀、灰值开闭运算、灰值形态学梯度等。

 

简单来讲,形态学操作就是基于形状的一系列图像处理操作。OpenCV为进行图像的形态学变换提供了快捷、方便的函数。最基本的形态学操作有二种,他们是:膨胀与腐蚀(Dilation与Erosion)。

膨胀与腐蚀能实现多种多样的功能,主要如下:

  • 消除噪声
  • 分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素。
  • 寻找图像中的明显的极大值区域或极小值区域
  • 求出图像的梯度

 


我们在这里给出下文会用到的,用于对比膨胀与腐蚀运算的“浅墨”字样毛笔字原图:

 

在进行腐蚀和膨胀的讲解之前,首先需要注意,腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。

 





1.2 膨胀

 

其实,膨胀就是求局部最大值的操作。

按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。

核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。

 

而膨胀就是求局部最大值的操作,核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。如下图所示,这就是膨胀操作的初衷。



膨胀的数学表达式:


膨胀效果图(毛笔字):

 

照片膨胀效果图:


 



1.3 腐蚀


再来看一下腐蚀,大家应该知道,膨胀和腐蚀是一对好基友,是相反的一对操作,所以腐蚀就是求局部最小值的操作。

我们一般都会把腐蚀和膨胀对应起来理解和学习。下文就可以看到,两者的函数原型也是基本上一样的。

 

原理图:

 

腐蚀的数学表达式:

 

腐蚀效果图(毛笔字):


照片腐蚀效果图:

 

 浅墨表示这张狗狗超可爱:D

 

 



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

 


直接上源码吧,在…opencvsourcesmodulesimgprocsrc morph.cpp路径中 的第1353行开始就为erode(腐蚀)函数的源码,1361行为dilate(膨胀)函数的源码。

  1. //-----------------------------------【erode()函数中文注释版源代码】----------------------------
  2. // 说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
  3. // OpenCV源代码版本:2.4.8
  4. // 源码路径:…opencvsourcesmodulesimgprocsrc morph.cpp
  5. // 源文件中如下代码的起始行数:1353行
  6. // 中文注释by浅墨
  7. //--------------------------------------------------------------------------------------------------------
  8. void cv::erode( InputArray src, OutputArraydst, InputArray kernel,
  9. Point anchor, int iterations,
  10. int borderType, constScalar& borderValue )
  11. {
  12. //调用morphOp函数,并设定标识符为MORPH_ERODE
  13. morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );
  14. }

  1. //-----------------------------------【dilate()函数中文注释版源代码】----------------------------
  2. // 说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
  3. // OpenCV源代码版本:2.4.8
  4. // 源码路径:…opencvsourcesmodulesimgprocsrc morph.cpp
  5. // 源文件中如下代码的起始行数:1361行
  6. // 中文注释by浅墨
  7. //--------------------------------------------------------------------------------------------------------
  8. void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,
  9. Point anchor, int iterations,
  10. int borderType, constScalar& borderValue )
  11. {
  12. //调用morphOp函数,并设定标识符为MORPH_DILATE
  13. morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );
  14. }


可以发现erode和dilate这两个函数内部就是调用了一下morphOp,只是他们调用morphOp时,第一个参数标识符不同,一个为MORPH_ERODE(腐蚀),一个为MORPH_DILATE(膨胀)。

morphOp函数的源码在…opencvsourcesmodulesimgprocsrcmorph.cpp中的第1286行,有兴趣的朋友们可以研究研究,这里就不费时费力花篇幅展开分析了。

 

 

 

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

 



3.1  形态学膨胀——dilate函数

 


erode函数,使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

函数原型:

  1. C++: void dilate(
  2. InputArray src,
  3. OutputArray dst,
  4. InputArray kernel,
  5. Point anchor=Point(-1,-1),
  6. int iterations=1,
  7. int borderType=BORDER_CONSTANT,
  8. const Scalar& borderValue=morphologyDefaultBorderValue()
  9. );

参数详解:

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
  • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
  • 第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。

我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。

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

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

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

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

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

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


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


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

使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

调用范例:

  1. //载入原图
  2. Mat image = imread("1.jpg");
  3. //获取自定义核
  4. Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
  5. Mat out;
  6. //进行膨胀操作
  7. dilate(image, out, element);

用上面核心代码架起来的完整程序代码:

 

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

 运行截图:



 

 

 

3.2 形态学腐蚀——erode函数



erode函数,使用像素邻域内的局部极小运算符来腐蚀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

 

看一下函数原型:

  1. C++: void erode(
  2. InputArray src,
  3. OutputArray dst,
  4. InputArray kernel,
  5. Point anchor=Point(-1,-1),
  6. int iterations=1,
  7. int borderType=BORDER_CONSTANT,
  8. const Scalar& borderValue=morphologyDefaultBorderValue()
  9. );

参数详解:

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
  • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
  • 第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。(具体看上文中浅出部分dilate函数的第三个参数讲解部分)
  • 第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它。
  • 第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
  • 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
  • 第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

同样的,使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

调用范例:

  1. //载入原图
  2. Mat image = imread("1.jpg");
  3. //获取自定义核
  4. Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
  5. Mat out;
  6. //进行腐蚀操作
  7. erode(image,out, element);

用上面核心代码架起来的完整程序代码:

 

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


运行结果:

 

 

 

 

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

 

 

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

这个示例程序中的效果图窗口有两个滚动条,顾名思义,第一个滚动条“腐蚀/膨胀”用于在腐蚀/膨胀之间进行切换;第二个滚动条”内核尺寸”用于调节形态学操作时的内核尺寸,以得到效果不同的图像,有一定的可玩性。废话不多说,上代码吧:

  1. //-----------------------------------【程序说明】----------------------------------------------
  2. // 程序名称::《【OpenCV入门教程之十】形态学图像处理(一):膨胀与腐蚀 》 博文配套源码
  3. // 开发所用IDE版本:Visual Studio 2010
  4. // 开发所用OpenCV版本: 2.4.8
  5. // 2014年4月14日 Create by 浅墨
  6. // 浅墨的微博:@浅墨_毛星云
  7. //------------------------------------------------------------------------------------------------
  8. //-----------------------------------【头文件包含部分】---------------------------------------
  9. // 描述:包含程序所依赖的头文件
  10. //----------------------------------------------------------------------------------------------
  11. #include
  12. #include
  13. #include
  14. #include
  15. //-----------------------------------【命名空间声明部分】---------------------------------------
  16. // 描述:包含程序所使用的命名空间
  17. //-----------------------------------------------------------------------------------------------
  18. using namespace std;
  19. using namespace cv;
  20. //-----------------------------------【全局变量声明部分】--------------------------------------
  21. // 描述:全局变量声明
  22. //-----------------------------------------------------------------------------------------------
  23. Mat g_srcImage, g_dstImage;//原始图和效果图
  24. int g_nTrackbarNumer = 0;//0表示腐蚀erode, 1表示膨胀dilate
  25. int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
  26. //-----------------------------------【全局函数声明部分】--------------------------------------
  27. // 描述:全局函数声明
  28. //-----------------------------------------------------------------------------------------------
  29. void Process();//膨胀和腐蚀的处理函数
  30. void on_TrackbarNumChange(int, void *);//回调函数
  31. void on_ElementSizeChange(int, void *);//回调函数
  32. //-----------------------------------【main( )函数】--------------------------------------------
  33. // 描述:控制台应用程序的入口函数,我们的程序从这里开始
  34. //-----------------------------------------------------------------------------------------------
  35. int main( )
  36. {
  37. //改变console字体颜色
  38. system("color5E");
  39. //载入原图
  40. g_srcImage= imread("1.jpg");
  41. if(!g_srcImage.data ) { printf("Oh,no,读取srcImage错误~! "); return false; }
  42. //显示原始图
  43. namedWindow("【原始图】");
  44. imshow("【原始图】", g_srcImage);
  45. //进行初次腐蚀操作并显示效果图
  46. namedWindow("【效果图】");
  47. //获取自定义核
  48. Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
  49. erode(g_srcImage,g_dstImage, element);
  50. imshow("【效果图】", g_dstImage);
  51. //创建轨迹条
  52. createTrackbar("腐蚀/膨胀", "【效果图】", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
  53. createTrackbar("内核尺寸", "【效果图】",&g_nStructElementSize, 21, on_ElementSizeChange);
  54. //输出一些帮助信息
  55. cout<" 嗯。运行成功,请调整滚动条观察图像效果~ "
  56. <<" 按下“q”键时,程序退出~! "
  57. <<" by浅墨";
  58. //轮询获取按键信息,若下q键,程序退出
  59. while(char(waitKey(1))!= 'q') {}
  60. return 0;
  61. }
  62. //-----------------------------【Process( )函数】------------------------------------
  63. // 描述:进行自定义的腐蚀和膨胀操作
  64. //-----------------------------------------------------------------------------------------
  65. void Process()
  66. {
  67. //获取自定义核
  68. Mat element = getStructuringElement(MORPH_RECT, Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point( g_nStructElementSize, g_nStructElementSize ));
  69. //进行腐蚀或膨胀操作
  70. if(g_nTrackbarNumer== 0) {
  71. erode(g_srcImage,g_dstImage, element);
  72. }
  73. else{
  74. dilate(g_srcImage,g_dstImage, element);
  75. }
  76. //显示效果图
  77. imshow("【效果图】", g_dstImage);
  78. }
  79. //-----------------------------【on_TrackbarNumChange( )函数】------------------------------------
  80. // 描述:腐蚀和膨胀之间切换开关的回调函数
  81. //-----------------------------------------------------------------------------------------------------
  82. void on_TrackbarNumChange(int, void *)
  83. {
  84. //腐蚀和膨胀之间效果已经切换,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
  85. Process();
  86. }
  87. //-----------------------------【on_ElementSizeChange( )函数】-------------------------------------
  88. // 描述:腐蚀和膨胀操作内核改变时的回调函数
  89. //-----------------------------------------------------------------------------------------------------
  90. void on_ElementSizeChange(int, void *)
  91. {
  92. //内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
  93. Process();
  94. }


 

放出一些效果图吧。原始图:

 


膨胀效果图:

 






腐蚀效果图:







腐蚀和膨胀得到的图,都特有喜感,但千变万变,还是原图好看:



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


本篇文章到这里就基本结束了,最后放出文章配套示例程序的打包下载地址。

 

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


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

 


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




文章知识点与官方知识档案匹配,可进一步学习相关知识
OpenCV技能树二值图像处理腐蚀与膨胀20473 人正在系统学习中
注:本文转载自blog.csdn.net的浅墨_毛星云的文章"http://blog.csdn.net/poem_qianmo/article/details/23710721"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top