【OpenCV新手教程之十一】 形态学图像处理(二):开运算、闭运算、形态学梯度
知乎:
邮箱:
写作当前博文时配套使用的版本号: 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)运算为”闭运算“的结果图与原图像之差。数学表达式为:
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域。且这一操作和选择的核的大小相关。
所以。黑帽运算用来分离比邻近点暗一些的斑块。非常完美的轮廓效果图:
实际素材效果图:
二、深入——源代码分析溯源
本文的主角是中的函数。它利用主要的膨胀和腐蚀技术。来执行更加高级的形态学变换,如开闭运算。形态学梯度。“顶帽”、“黑帽”等等。
这一节我们来一起看一下函数的源代码。
//-----------------------------------【erode()函数中文凝视版源代码】----------------------------
// 说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
// OpenCV源代码版本号:2.4.8
// 源代码路径:…\opencv\sources\modules\imgproc\src\morph.cpp
// 源文件里例如以下代码的起始行数:1369行
// 中文凝视by浅墨
//--------------------------------------------------------------------------------------------------------
void cv::morphologyEx( InputArray _src,OutputArray _dst, int op,InputArray kernel, Pointanchor, int iterations,int borderType, constScalar& borderValue )
{
//拷贝Mat数据到暂时变量Mat src = _src.getMat(), temp;_dst.create(src.size(), src.type());Mat dst = _dst.getMat();//一个大switch,依据不同的标识符取不同的操作switch( op ){case MORPH_ERODE:erode( src, dst, kernel, anchor, iterations, borderType, borderValue );break;case MORPH_DILATE:dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );break;case MORPH_OPEN:erode( src, dst, kernel, anchor, iterations, borderType, borderValue );dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );break;case CV_MOP_CLOSE:dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );break;case CV_MOP_GRADIENT:erode( src, temp, kernel, anchor, iterations, borderType, borderValue );dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );dst -= temp;break;case CV_MOP_TOPHAT:if( src.data != dst.data )temp = dst;erode( src, temp, kernel, anchor, iterations, borderType, borderValue );dilate( temp, temp, kernel, anchor,iterations, borderType, borderValue );dst = src - temp;break;case CV_MOP_BLACKHAT:if( src.data != dst.data )temp = dst;dilate( src, temp, kernel, anchor, iterations, borderType, borderValue);erode( temp, temp, kernel, anchor, iterations, borderType, borderValue);dst = temp - src;break;default:CV_Error( CV_StsBadArg, "unknown morphological operation" );}
}
看上面的源代码能够发现,事实上函数事实上就是内部一个大而已。依据不同的标识符取不同的操作。比方开运算,按我们上文中解说的数学表达式,就是先腐蚀后膨胀,即依次调用erode和函数。为非常简明干净的代码。
三、浅出——API函数高速上手
3.函数具体解释
上面我们已经讲到,函数利用主要的膨胀和腐蚀技术,来执行更加高级形态学变换。如开闭运算。形态学梯度,“顶帽”、“黑帽”等等。这一节我们来了解它的參数意义和用法。
C++: void morphologyEx(
InputArray src,
OutputArray dst,
int op,
InputArraykernel,
Pointanchor=Point(-1,-1),
intiterations=1,
intborderType=BORDER_CONSTANT,
constScalar& borderValue=morphologyDefaultBorderValue() );
另有CV版本号的标识符也可选择,如。,,,这应该是.0系列版本号遗留下来的标识符。和上面的“”一样的效果。
当中。t函数的第一个參数表示内核的形状,我们能够选择例如以下三种形状之中的一个:
而t函数的第二和第三个參数各自是内核的尺寸以及锚点的位置。
我们一般在调用erode以及函数之前。先定义一个Mat类型的变量来获得t函数的返回值。
对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且须要注意,十字形的形状唯一依赖于锚点的位置。而在其它情况下,锚点仅仅是影响了形态学运算结果的偏移。
t函数相关的调用演示样例代码例如以下:
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸//获取自己定义核
Mat element =getStructuringElement(MORPH_RECT,Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),Point(g_nStructElementSize, g_nStructElementSize ));
调用这样之后。我们便能够在接下来调用erode、或函数时。參数填保存t返回值的Mat类型变量。
相应于我们上面的演示样例,就是填变量。
当中的这些操作都能够进行就地(in-place)操作。且对于多通道图像,每个通道都是单独进行操作。
OK。解说完成。以下就是使用的范例。
高能预警!高能预警。高能预警!
一大波演示样例代码正在逼近。
为了方便大家须要的时候随时取用。以下我们依次列举出开运算,闭运算,形态学梯度,顶帽。黑帽。腐蚀。膨胀的效果实现简化版完整代码。
事实上说白了。这些代码基本上内容一致,事实上就是改一下里面的第三个标识符參数而已。核都是选的,矩形元素结构。
另外,通过看源代码我们发现。最主要的腐蚀和膨胀操作也能够用函数来实现。他们由函数源代码中的前两个case来实现(尽管在case体内就是简单地各自调用了一下erode和函数,但还是有写出来的必要)。
所以在这里。我们也用再又一次来实现一遍他们。
按着顺序来列出吧,就直接列具体凝视好的代码和执行结果了。
3.2开运算演示样例程序
中调用函数进行开运算操作的演示样例程序例如以下:
//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include
#include
#include//-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数。我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{//加载原始图 Mat image = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图//创建窗体 namedWindow("【原始图】开运算"); namedWindow("【效果图】开运算"); //显示原始图 imshow("【原始图】开运算", image); //定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //进行形态学操作morphologyEx(image,image, MORPH_OPEN, element);//显示效果图 imshow("【效果图】开运算", image); waitKey(0); return 0;
}
执行效果图:
3.3闭运算演示样例程序
中调用函数进行闭运算操作的演示样例程序例如以下:
//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include
#include
#include//-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{//加载原始图 Mat image = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图//创建窗体 namedWindow("【原始图】闭运算"); namedWindow("【效果图】闭运算"); //显示原始图 imshow("【原始图】闭运算", image); //定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //进行形态学操作morphologyEx(image,image, MORPH_CLOSE, element);//显示效果图 imshow("【效果图】闭运算", image); waitKey(0); return 0;
}
执行效果图:
3.4形态学梯度演示样例程序
中调用函数进行形态学梯度操作的演示样例程序例如以下:
//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include
#include
#include//-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{//加载原始图 Mat image = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图//创建窗体 namedWindow("【原始图】形态学梯度"); namedWindow("【效果图】形态学梯度"); //显示原始图 imshow("【原始图】形态学梯度", image); //定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //进行形态学操作morphologyEx(image,image, MORPH_GRADIENT, element);//显示效果图 imshow("【效果图】形态学梯度", image); waitKey(0); return 0;
}
执行效果图:
3.5顶帽运算(Top Hat)演示样例程序
中调用函数进行顶帽运算操作的演示样例程序例如以下:
//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include
#include
#include//-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{//加载原始图 Mat image = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图//创建窗体 namedWindow("【原始图】顶帽运算"); namedWindow("【效果图】顶帽运算"); //显示原始图 imshow("【原始图】顶帽运算", image); //定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //进行形态学操作morphologyEx(image,image, MORPH_TOPHAT, element);//显示效果图 imshow("【效果图】顶帽运算", image); waitKey(0); return 0;
}
执行效果图:
3.6黑帽运算()演示样例程序
中调用函数进行黑帽运算操作的演示样例程序例如以下:
//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include
#include
#include//-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数。我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{//加载原始图 Mat image = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图//创建窗体 namedWindow("【原始图】黑帽运算"); namedWindow("【效果图】黑帽运算"); //显示原始图 imshow("【原始图】黑帽运算", image); //定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //进行形态学操作morphologyEx(image,image, MORPH_BLACKHAT, element);//显示效果图 imshow("【效果图】黑帽运算", image); waitKey(0); return 0;
}
执行效果图:
3.7腐蚀(调用版)演示样例程序
中调用函数进行腐蚀操作的演示样例程序例如以下:
//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include
#include
#include//-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{//加载原始图 Mat image = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图//创建窗体 namedWindow("【原始图】腐蚀"); namedWindow("【效果图】腐蚀"); //显示原始图 imshow("【原始图】腐蚀", image); //定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //进行形态学操作morphologyEx(image,image, MORPH_ERODE, element);//显示效果图 imshow("【效果图】腐蚀", image); waitKey(0); return 0;
}
执行效果图:
3.8膨胀(调用版)演示样例程序
中调用函数进行膨胀操作的演示样例程序例如以下:
//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include
#include
#include//-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{//加载原始图 Mat image = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图//创建窗体 namedWindow("【原始图】膨胀"); namedWindow("【效果图】膨胀"); //显示原始图 imshow("【原始图】膨胀", image); //定义核Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); //进行形态学操作morphologyEx(image,image, MORPH_DILATE, element);//显示效果图 imshow("【效果图】膨胀", image); waitKey(0); return 0;
}
执行效果图:
四、综合演示样例——在实战中熟稔
依旧是每篇文章都会配给大家的一个具体凝视的博文配套演示样例程序,把这篇文章中介绍的知识点以代码为载体,展现给大家。
这个演示样例程序中,一共同拥有四个显示图像的窗体。
原始图一个,开/闭运算为一个,腐蚀/膨胀为一个,顶帽/黑帽运算为一个。
分别使用滚动栏,来控制得到的形态学效果。且迭代值为10的时候,为中间。
另外。还能够通过键盘按键1,2,3以及空格,来调节成不同的元素结构(矩形、椭圆、十字形)。
说明页面例如以下:
废话不多说,上代码吧:
//-----------------------------------【程序说明】----------------------------------------------
// 程序名称::《【OpenCV新手教程之十一】形态学图像处理(一):膨胀与腐蚀 》 博文配套源代码
// 开发所用IDE版本号:Visual Studio 2010
// 开发所用OpenCV版本号: 2.4.8
// 2014年4月25日 Create by 浅墨
//----------------------------------------------------------------------------------------------//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include
#include
#include //-----------------------------------【命名空间声明部分】--------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;//-----------------------------------【全局变量声明部分】--------------------------------------
// 描写叙述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage;//原始图和效果图
int g_nElementShape = MORPH_RECT;//元素结构的形状//变量接收的TrackBar位置參数
int g_nMaxIterationNum = 10;
int g_nOpenCloseNum = 0;
int g_nErodeDilateNum = 0;
int g_nTopBlackHatNum = 0;//-----------------------------------【全局函数声明部分】--------------------------------------
// 描写叙述:全局函数声明
//-----------------------------------------------------------------------------------------------
static void on_OpenClose(int, void*);//回调函数
static void on_ErodeDilate(int, void*);//回调函数
static void on_TopBlackHat(int, void*);//回调函数
static void ShowHelpText();//帮助文字显示//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{//改变console字体颜色system("color 2F"); ShowHelpText();//加载原图g_srcImage = imread("1.jpg");//project文件夹下须要有一张名为1.jpg的素材图if( !g_srcImage.data ) { printf("Oh。no,读取srcImage错误~!
\n"); false; } //显示原始图 ("【原始图】"); ("【原始图】", ); //创建三个窗体 ("【开运算/闭运算】",1); ("【腐蚀/膨胀】",1); ("【顶帽/黑帽】",1); //參数赋值 =9; =9; =2; //分别为三个窗体创建滚动栏 ("迭代值", "【开运算/闭运算】",&,*2+1,); ("迭代值", "【腐蚀/膨胀】",&,*2+1,); ("迭代值", "【顶帽/黑帽】",&,*2+1,); //轮询获取按键信息 while(1) { int c; //执行回调函数 (, 0); (, 0); (,0); //获取按键 c = (0); //按下键盘按键Q或者ESC。
程序退出 if( (char)c == 'q'||(char)c == 27 ) break; //按下键盘按键1。使用椭圆()结构元素结构元素 if( (char)c == 49 )//键盘按键1的ASII码为49 = ; //按下键盘按键2。使用矩形()结构元素 else if( (char)c == 50 )//键盘按键2的ASII码为50 = ; //按下键盘按键3,使用十字形(Cross-)结构元素 else if( (char)c == 51 )//键盘按键3的ASII码为51 = ; //按下键盘按键space,在矩形、椭圆、十字形结构元素中循环 else if( (char)c == ' ' ) = ( + 1) % 3; } 0; } //-----------------------------------【( )函数】---------------------------------- // 描写叙述:【开运算/闭运算】窗体的回调函数 //----------------------------------------------------------------------------------------------- void (int, void*) { //偏移量的定义 int = - ;//偏移量 int = > 0 ?
: -;//偏移量绝对值 //自己定义核 Mat = t(, Size(*2+1, *2+1), Point(, ) ); //进行操作 if( < 0 ) (, , , ); else (, , , ); //显示图像 ("【开运算/闭运算】",); } //-----------------------------------【( )函数】---------------------------------- // 描写叙述:【腐蚀/膨胀】窗体的回调函数 //----------------------------------------------------------------------------------------------- void (int, void*) { //偏移量的定义 int = - ; //偏移量 int = > 0 ? : -;//偏移量绝对值 //自己定义核 Mat = t(, Size(*2+1, *2+1), Point(, ) ); //进行操作 if( < 0 ) erode(, , ); else (, , ); //显示图像 ("【腐蚀/膨胀】",); } //-----------------------------------【( )函数】-------------------------------- // 描写叙述:【顶帽运算/黑帽运算】窗体的回调函数 //---------------------------------------------------------------------------------------------- void (int, void*) { //偏移量的定义 int = - ;//偏移量 int = > 0 ? : -;//偏移量绝对值 //自己定义核 Mat = t(, Size(*2+1, *2+1), Point(, ) ); //进行操作 if( < 0 ) (, , , ); else (, , , ); //显示图像 ("【顶帽/黑帽】",); } //-----------------------------------【( )函数】---------------------------------- // 描写叙述:输出一些帮助信息 //---------------------------------------------------------------------------------------------- void () { //输出一些帮助信息 ("\n\n\n\t请调整滚动栏观察图像效果~\n\n"); ( "\n\n\t按键操作说明: \n\n" "\t\t键盘按键【ESC】或者【Q】- 退出程序\n" "\t\t键盘按键【1】- 使用椭圆()结构元素\n" "\t\t键盘按键【2】- 使用矩形( )结构元素\n" "\t\t键盘按键【3】- 使用十字型(Cross-)结构元素\n" "\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n" "\n\n\t\t\t\t\t\t\t\t by浅墨" ); }
放出一些效果图:
首先是原图:
非常帅气的 有木有!
腐蚀效果图:
膨胀效果图:
开运算效果图:
闭运算效果图:
顶帽运算效果图:
黑帽运算效果图:
好的,就放出这些效果图吧。具体很多其它的执行效果大家就自己下载演示样例程序回去玩~
本篇文章的配套源代码请点击这里下载:
【浅墨新手教程之十一】配套源代码下载
OK。今天的内容大概就是这些,我们下篇文章见:)