形态学图像处理(二):开运算、闭运算、形态学梯度、顶帽、黑帽合辑
知乎:
邮箱:
写作当前博文时配套使用的版本: 2.4.8
上篇文章中,我们重点了解了腐蚀和膨胀这两种最基本的形态学操作,而运用这两个基本操作,我们可以实现更高级的形态学变换。
所以,本文的主角是中的函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算、形态学梯度、“顶帽”、“黑帽”等等。
先上几张示例程序的截图吧:
有没有很熟悉这张图?没错,这就是最近热映的电影 ~
下面这张图的效果就有些凶残了:
OK,截图先看到这里。在正文之前先来唠唠和主题相关的事情。
第一件事,最新版本更新到了2.4.9。
在写这篇博文的两天之前(4月25日上午),官网页面显示最新版本还是2.4.8,但是通过浅墨细心地发现,文档页面的标题已经悄悄而低调地改成了2.4.9.所以我们当时应该可以去断定,.4.9应该马上就要和我们见面了。
果然,.4.9就在两天后(4月27日),正式在官方网站上上线了。现在转到的官方主页,赫然发现最新版本已然显示为2.4.9:
这是的官方主页传送门:
大家可以自己前去看看以及下载最新版本的。如果不出意外的话呢,下次文章我们就将紧跟时代,用上最新版本的.4.9进行讲解和程序的书写,所以,大家在看这篇文章之后呢,可以去下载当前最新的2.4.9版本并装上配置好。
第二件事,是浅墨想跟大家做一个关于系列文章的书写内容和风格的思想汇报。
是这样的,浅墨发现最近几期写出来的文章有些偏离自己开始开这个专栏的最初的愿望——原理和概念部分占的比重有些大,有些弱化实际的使用。
写这些博文的初心是教大家如何使用来写代码,原理部分我想很多朋友应该多少都懂,就算某些同学对某些概念有些模糊,大家也完全可以带着关键词句去或者百度。
浅墨的想法是,以后的专栏文章原理部分尽量从简,“深入”的源码剖析部分也是从简,重点突出“浅出”部分,让大家快速上手函数的使用,这样浅墨的工作量也会小很多,更新也会更勤。
PS:浅墨其实每次在写图像处理原理部分的时候都特纠结,因为浅墨其实感兴趣的和大家一样,也是如何写代码,而不是那些多多少少让人提不起兴趣来的图像处理公式和概念。这往往就照成了博文更新的拖延症。
所以呢,在浅墨以后写的文章中,原理和深入部分我们就点到为止,文章的拳头内容是“浅出”部分,重点教大家如何快速上手 API。我想这也是大家一直期待和想要看到的浅墨出品的文章的样子吧。:)
OK,大概就是这些。我们开始今天的正题。
一、理论与概念讲解——从现象到本质
首先呢,要知道形态学的高级形态,往往都是建立在腐蚀和膨胀这两个基本操作之上的。而关于腐蚀和膨胀,概念和细节以及相关代码可以看浅墨之前写的这篇文章:
【入门教程之十】 形态学图像处理(一):膨胀与腐蚀
对膨胀和腐蚀心中有数了,接下来的高级形态学操作,应该就不难理解。
另外,为了下面对比和演示以及理解的方便,浅墨自己制作了一张毛笔字图,这里先上原图:
OK,我们开始讲解。
1.1开运算( )
开运算( ),其实就是先腐蚀后膨胀的过程。其数学表达式如下:
开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。效果图是这样的:
实际效果图:
1.2闭运算( )
先膨胀后腐蚀的过程称为闭运算( ),其数学表达式如下:
闭运算能够排除小型黑洞(黑色区域)。效果图如下所示:
实际效果图:
1.3形态学梯度(t)
形态学梯度( )为膨胀图与腐蚀图之差,数学表达式如下:
对二值图像进行这一操作可以将团块(blob)的边缘突出出来。我们可以用形态学梯度来保留物体的边缘轮廓,如下所示:
实际素材效果图:
1.4顶帽(Top Hat)
顶帽运算(Top Hat)又常常被译为”礼帽“运算。为原图像与上文刚刚介绍的“开运算“的结果图之差,数学表达式如下:
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
如下所示:
素材效果图:
1.5黑帽(Black Hat)
黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差。数学表达式为:
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。
所以,黑帽运算用来分离比邻近点暗一些的斑块。非常完美的轮廓效果图:
实际素材效果图:
二、深入——源码分析溯源
本文的主角是中的函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。这一节我们来一起看一下函数的源代码。
[cpp] view plain copy print ?
//-----------------------------------【erode()函数中文注释版源代码】---------------------------- //说明:以下代码为来自于计算机开源视觉库的官方源代码 //源代码版本:2。4。8 //源码路径:…\\\\\src\morph。cpp //源文件中如下代码的起始行数:1369行 //中文注释by浅墨 //-------------------------------------------------------------------------------------------------------- ::(,,intop, ,,, ,&) { //拷贝Mat数据到临时变量 =_src。
(),temp; _dst。(src。size(),src。type()); =_dst。(); //一个大,根据不同的标识符取不同的操作 (op) { : erode(src,dst,,,,,); break; : (src,dst,,,,,); break; : erode(src,dst,,,,,); (dst,dst,,,,,); break; : (src,dst,,,,,); erode(dst,dst,,,,,); break; : erode(src,temp,,,,,); (src,dst,,,,,); dst-=temp; break; : if(src。
data!=dst。data) temp=dst; erode(src,temp,,,,,); (temp,temp,,,,,); dst=src-temp; break; : if(src。data!=dst。data) temp=dst; (src,temp,,,,,); erode(temp,temp,,,,,); dst=temp-src; break; : (,""); } }
看上面的源码可以发现,其实函数其实就是内部一个大而已。根据不同的标识符取不同的操作。比如开运算,按我们上文中讲解的数学表达式,就是先腐蚀后膨胀,即依次调用erode和函数,为非常简明干净的代码。