opencv学习(8)形态学图像处理

本博文内容和代码均来自《opencv3编程入门》这本书;

形态学图像处理多的方式主要有:
1、膨胀与腐蚀
2、开运算、闭运算、形态学梯度、顶帽、黑帽

膨胀与腐蚀能实现多种多样的功能,主要如下:
1)消除噪声
2)分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素。
3)寻找图像中的明显的极大值区域或极小值区域
4)求出图像的梯度

首先需要注意,腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。
膨胀就是求局部最大值的操作。
按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。
核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。
而膨胀就是求局部最大值的操作,核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。

膨胀和腐蚀是相反的一对操作,所以腐蚀就是求局部最小值的操作。

膨胀和腐蚀的源码:

void cv::erode( InputArray src, OutputArraydst, InputArray kernel,Point anchor, int iterations, int borderType, constScalar& borderValue )  
{  
//调用morphOp函数,并设定标识符为MORPH_ERODE 
   morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );  
}  
void cv::dilate( InputArray src,OutputArray dst, InputArray kernel, Point anchor, int iterations, int borderType, constScalar& borderValue )  
{  
//调用morphOp函数,并设定标识符为MORPH_DILATE 
   morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );  
}  

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

1、膨胀——dilate函数

原型:

C++: void dilate(  
    InputArray src,  
    OutputArray dst,  
    InputArray kernel,  
    Point anchor=Point(-1,-1),  
    int iterations=1,  
    int borderType=BORDER_CONSTANT,  
    const Scalar& borderValue=morphologyDefaultBorderValue()   
); 

dilate函数,使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出。支持就地(in-place)操作。
参数详解:
第一个参数,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函数相关的调用示例代码如下:

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

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

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

#include <opencv2/core/core.hpp> 
#include<opencv2/highgui/highgui.hpp> 
#include<opencv2/imgproc/imgproc.hpp> 
#include <iostream> 

//-----------------------------------【命名空间声明部分】--------------------------------------- 
// 描述:包含程序所使用的命名空间 
//----------------------------------------------------------------------------------------------- 
using namespace std;  
using namespace cv;  

//-----------------------------------【main( )函数】-------------------------------------------- 
// 描述:控制台应用程序的入口函数,我们的程序从这里开始 
//----------------------------------------------------------------------------------------------- 
int main(  )  
{  

       //载入原图 
       Mat image = imread("1.jpg");  

       //创建窗口 
       namedWindow("【原图】膨胀操作");  
       namedWindow("【效果图】膨胀操作");  

       //显示原图 
       imshow("【原图】膨胀操作", image);  

<span style="white-space:pre">  </span>//获取自定义核 
       Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));  
       Mat out;  
<span style="white-space:pre">  </span>//进行膨胀操作 
       dilate(image,out, element);  

       //显示效果图 
       imshow("【效果图】膨胀操作", out);  

       waitKey(0);  

       return 0;  
}  

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

C++: void erode(  
    InputArray src,  
    OutputArray dst,  
    InputArray kernel,  
    Point anchor=Point(-1,-1),  
    int iterations=1,  
    int borderType=BORDER_CONSTANT,  
    const Scalar& borderValue=morphologyDefaultBorderValue()  
 ); 

参数详解:
第一个参数,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一起使用。

#include <opencv2/core/core.hpp> 
#include<opencv2/highgui/highgui.hpp> 
#include<opencv2/imgproc/imgproc.hpp> 
#include <iostream> 

//-----------------------------------【命名空间声明部分】--------------------------------------- 
// 描述:包含程序所使用的命名空间 
//----------------------------------------------------------------------------------------------- 
using namespace std;  
using namespace cv;  

//-----------------------------------【main( )函数】-------------------------------------------- 
// 描述:控制台应用程序的入口函数,我们的程序从这里开始 
//----------------------------------------------------------------------------------------------- 
int main(  )  
{  
       //载入原图 
       Matimage = imread("1.jpg");  

        //创建窗口 
       namedWindow("【原图】腐蚀操作");  
       namedWindow("【效果图】腐蚀操作");  

       //显示原图 
       imshow("【原图】腐蚀操作", image);  


//获取自定义核 
       Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));  
       Mat out;  

//进行腐蚀操作 
       erode(image,out, element);  
       //显示效果图 
       imshow("【效果图】腐蚀操作", out);  
       waitKey(0);  
       return 0;  
}  

接下来重点介绍morphologyEx函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算、形态学梯度、“顶帽”、“黑帽”等等。

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

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

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

闭运算能够排除小型黑洞(黑色区域)。

5、形态学梯度(MorphologicalGradient)

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

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

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

因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。

7、黑帽(Black Hat)

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

黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。
所以,黑帽运算用来分离比邻近点暗一些的斑块。

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

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" );  
    }  
}  

morphologyEx函数原型:

C++: void morphologyEx(  
InputArray src,  
OutputArray dst,  
int op,  
InputArraykernel,  
Pointanchor=Point(-1,-1),  
intiterations=1,  
intborderType=BORDER_CONSTANT,  
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函数相关的调用示例代码如下:

int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸 

//获取自定义核 
Mat element =getStructuringElement(MORPH_RECT,  
       Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),  
       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)操作。且对于多通道图像,每一个通道都是单独进行操作。

综合示例

#include <opencv2/opencv.hpp> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp> 

//-----------------------------------【命名空间声明部分】-------------------------------------- 
// 描述:包含程序所使用的命名空间 
//----------------------------------------------------------------------------------------------- 
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");//工程目录下需要有一张名为1.jpg的素材图 
    if( !g_srcImage.data ) { printf("Oh,no,读取srcImage错误~! \n"); return false; }  

    //显示原始图 
    namedWindow("【原始图】");  
    imshow("【原始图】", g_srcImage);  

    //创建三个窗口 
    namedWindow("【开运算/闭运算】",1);  
    namedWindow("【腐蚀/膨胀】",1);  
    namedWindow("【顶帽/黑帽】",1);  

    //参数赋值 
    g_nOpenCloseNum=9;  
    g_nErodeDilateNum=9;  
    g_nTopBlackHatNum=2;  

    //分别为三个窗口创建滚动条 
    createTrackbar("迭代值", "【开运算/闭运算】",&g_nOpenCloseNum,g_nMaxIterationNum*2+1,on_OpenClose);  
    createTrackbar("迭代值", "【腐蚀/膨胀】",&g_nErodeDilateNum,g_nMaxIterationNum*2+1,on_ErodeDilate);  
    createTrackbar("迭代值", "【顶帽/黑帽】",&g_nTopBlackHatNum,g_nMaxIterationNum*2+1,on_TopBlackHat);  

    //轮询获取按键信息 
    while(1)  
    {  
        int c;  

        //执行回调函数 
        on_OpenClose(g_nOpenCloseNum, 0);  
        on_ErodeDilate(g_nErodeDilateNum, 0);  
        on_TopBlackHat(g_nTopBlackHatNum,0);  

        //获取按键 
        c = waitKey(0);  

        //按下键盘按键Q或者ESC,程序退出 
        if( (char)c == 'q'||(char)c == 27 )  
            break;  
        //按下键盘按键1,使用椭圆(Elliptic)结构元素结构元素MORPH_ELLIPSE 
        if( (char)c == 49 )//键盘按键1的ASII码为49 
            g_nElementShape = MORPH_ELLIPSE;  
        //按下键盘按键2,使用矩形(Rectangle)结构元素MORPH_RECT 
        else if( (char)c == 50 )//键盘按键2的ASII码为50 
            g_nElementShape = MORPH_RECT;  
        //按下键盘按键3,使用十字形(Cross-shaped)结构元素MORPH_CROSS 
        else if( (char)c == 51 )//键盘按键3的ASII码为51 
            g_nElementShape = MORPH_CROSS;  
        //按下键盘按键space,在矩形、椭圆、十字形结构元素中循环 
        else if( (char)c == ' ' )  
            g_nElementShape = (g_nElementShape + 1) % 3;  
    }  

    return 0;  
}  


//-----------------------------------【on_OpenClose( )函数】---------------------------------- 
// 描述:【开运算/闭运算】窗口的回调函数 
//----------------------------------------------------------------------------------------------- 
static void on_OpenClose(int, void*)  
{  
    //偏移量的定义 
    int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量 
    int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值 
    //自定义核 
    Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
    //进行操作 
    if( offset < 0 )  
        morphologyEx(g_srcImage, g_dstImage, CV_MOP_OPEN, element);  
    else  
        morphologyEx(g_srcImage, g_dstImage, CV_MOP_CLOSE, element);  
    //显示图像 
    imshow("【开运算/闭运算】",g_dstImage);  
}  


//-----------------------------------【on_ErodeDilate( )函数】---------------------------------- 
// 描述:【腐蚀/膨胀】窗口的回调函数 
//----------------------------------------------------------------------------------------------- 
static void on_ErodeDilate(int, void*)  
{  
    //偏移量的定义 
    int offset = g_nErodeDilateNum - g_nMaxIterationNum;    //偏移量 
    int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值 
    //自定义核 
    Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
    //进行操作 
    if( offset < 0 )  
        erode(g_srcImage, g_dstImage, element);  
    else  
        dilate(g_srcImage, g_dstImage, element);  
    //显示图像 
    imshow("【腐蚀/膨胀】",g_dstImage);  
}  


//-----------------------------------【on_TopBlackHat( )函数】-------------------------------- 
// 描述:【顶帽运算/黑帽运算】窗口的回调函数 
//---------------------------------------------------------------------------------------------- 
static void on_TopBlackHat(int, void*)  
{  
    //偏移量的定义 
    int offset = g_nTopBlackHatNum - g_nMaxIterationNum;//偏移量 
    int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值 
    //自定义核 
    Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );  
    //进行操作 
    if( offset < 0 )  
        morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT , element);  
    else  
        morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);  
    //显示图像 
    imshow("【顶帽/黑帽】",g_dstImage);  
}  

//-----------------------------------【ShowHelpText( )函数】---------------------------------- 
// 描述:输出一些帮助信息 
//---------------------------------------------------------------------------------------------- 
static void ShowHelpText()  
{  
//输出一些帮助信息 
    printf("\n\n\n\t请调整滚动条观察图像效果~\n\n");  
    printf( "\n\n\t按键操作说明: \n\n"  
        "\t\t键盘按键【ESC】或者【Q】- 退出程序\n"  
        "\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n"  
        "\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n"  
        "\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n"  
        "\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n"  
        "\n\n\t\t\t\t\t\t\t\t by浅墨"  
        );  
}  

你可能感兴趣的:(opencv)