一、理论与概念讲解
1、开运算(Opening Operation)
开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。其数学表达式如下:
开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。
2、闭运算(Closing Operation)
先膨胀后腐蚀的过程称为闭运算(Closing Operation),其数学表达式如下:
闭运算能够排除小型黑洞(黑色区域)。效果图如下所示:
3、形态学梯度(MorphologicalGradient)
形态学梯度(Morphological Gradient)为膨胀图与腐蚀图之差,数学表达式如下:
对二值图像进行这一操作可以将团块(blob)的边缘突出出来。我们可以用形态学梯度来保留物体的边缘轮廓
4、顶帽(Top Hat)
顶帽运算(Top Hat)又常常被译为”礼帽“运算。为原图像与上文刚刚介绍的“开运算“的结果图之差,数学表达式如下:
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
5、黑帽(Black Hat)
黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差。数学表达式为:
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。所以,黑帽运算用来分离比邻近点暗一些的斑块。非常完美的轮廓效果图:
二、示例
1、morphologyEx函数详解
上面我们已经讲到,morphologyEx函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。函数原型如下:
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我们上篇文章中讲过了,这里为了大家参阅方便,再写一遍: 矩形: MORPH_RECT 交叉形: MORPH_CROSS 椭圆形: MORPH_ELLIPSE
而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。
我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。
getStructuringElement函数相关的调用示例代码如下:
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)操作。且对于多通道图像,每一个通道都是单独进行操作。
2、开运算
1 #include <opencv2/opencv.hpp> 2 #include<opencv2/highgui/highgui.hpp> 3 #include<opencv2/imgproc/imgproc.hpp> 4 5 using namespace cv; 6 7 int main( ) 8 { 9 //载入原始图 10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图 11 //创建窗口 12 namedWindow("【原始图】开运算"); 13 namedWindow("【效果图】开运算"); 14 //显示原始图 15 imshow("【原始图】开运算", image); 16 //定义核 17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 18 //进行形态学操作AC 19 morphologyEx(image,image, MORPH_OPEN, element); 20 //显示效果图 21 imshow("【效果图】开运算", image); 22 imwrite("开运算.jpg", image); 23 waitKey(0); 24 25 return 0; 26 }
效果展示:
原图 开运算
3、闭运算
1 #include <opencv2/opencv.hpp> 2 #include <opencv2/highgui/highgui.hpp> 3 #include<opencv2/imgproc/imgproc.hpp> 4 5 using namespace cv; 6 7 int main( ) 8 { 9 //载入原始图 10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图 11 //创建窗口 12 namedWindow("【原始图】闭运算"); 13 namedWindow("【效果图】闭运算"); 14 //显示原始图 15 imshow("【原始图】闭运算", image); 16 //定义核 17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 18 //进行形态学操作 19 morphologyEx(image,image, MORPH_CLOSE, element); 20 //显示效果图 21 imshow("【效果图】闭运算", image); 22 imwrite("闭运算.jpg", image); 23 waitKey(0); 24 25 return 0; 26 }
效果展示:
原图 闭运算
3、梯度
1 #include <opencv2/opencv.hpp> 2 #include<opencv2/highgui/highgui.hpp> 3 #include<opencv2/imgproc/imgproc.hpp> 4 5 using namespace cv; 6 7 int main( ) 8 { 9 //载入原始图 10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图 11 //创建窗口 12 namedWindow("【原始图】形态学梯度"); 13 namedWindow("【效果图】形态学梯度"); 14 //显示原始图 15 imshow("【原始图】形态学梯度", image); 16 //定义核 17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 18 //进行形态学操作 19 morphologyEx(image,image, MORPH_GRADIENT, element); 20 //显示效果图 21 imshow("【效果图】形态学梯度", image); 22 imwrite("梯度.jpg", image); 23 waitKey(0); 24 25 return 0; 26 }
效果展示:
原图 梯度
4、顶帽运算
1 #include <opencv2/opencv.hpp> 2 #include<opencv2/highgui/highgui.hpp> 3 #include<opencv2/imgproc/imgproc.hpp> 4 5 using namespace cv; 6 7 int main( ) 8 { 9 //载入原始图 10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图 11 //创建窗口 12 namedWindow("【原始图】顶帽运算"); 13 namedWindow("【效果图】顶帽运算"); 14 //显示原始图 15 imshow("【原始图】顶帽运算", image); 16 //定义核 17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 18 //进行形态学操作 19 morphologyEx(image,image, MORPH_TOPHAT, element); 20 //显示效果图 21 imshow("【效果图】顶帽运算", image); 22 imwrite("顶帽.jpg", image); 23 waitKey(0); 24 25 return 0; 26 }
效果展示:
原图 顶帽
6、黑帽运算
1 #include <opencv2/opencv.hpp> 2 #include <opencv2/highgui/highgui.hpp> 3 #include<opencv2/imgproc/imgproc.hpp> 4 5 using namespace cv; 6 7 int main( ) 8 { 9 //载入原始图 10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图 11 //创建窗口 12 namedWindow("【原始图】黑帽运算"); 13 namedWindow("【效果图】黑帽运算"); 14 //显示原始图 15 imshow("【原始图】黑帽运算", image); 16 //定义核 17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 18 //进行形态学操作 19 morphologyEx(image,image, MORPH_BLACKHAT, element); 20 //显示效果图 21 imshow("【效果图】黑帽运算", image); 22 imwrite("黑帽.jpg", image); 23 waitKey(0); 24 25 return 0; 26 }
效果展示:
原图 黑帽
7、腐蚀(morphologyEx调用版)
1 #include <opencv2/opencv.hpp> 2 #include <opencv2/highgui/highgui.hpp> 3 #include<opencv2/imgproc/imgproc.hpp> 4 5 using namespace cv; 6 7 int main( ) 8 { 9 //载入原始图 10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图 11 //创建窗口 12 namedWindow("【原始图】腐蚀"); 13 namedWindow("【效果图】腐蚀"); 14 //显示原始图 15 imshow("【原始图】腐蚀", image); 16 //定义核 17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 18 //进行形态学操作 19 morphologyEx(image,image, MORPH_ERODE, element); 20 //显示效果图 21 imshow("【效果图】腐蚀", image); 22 imwrite("腐蚀.jpg", image); 23 waitKey(0); 24 25 return 0; 26 }
效果展示:
原图 腐蚀
8、膨胀(morphologyEx调用版)示例
1 #include <opencv2/opencv.hpp> 2 #include<opencv2/highgui/highgui.hpp> 3 #include<opencv2/imgproc/imgproc.hpp> 4 5 using namespace cv; 6 7 int main( ) 8 { 9 //载入原始图 10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图 11 //创建窗口 12 namedWindow("【原始图】膨胀"); 13 namedWindow("【效果图】膨胀"); 14 //显示原始图 15 imshow("【原始图】膨胀", image); 16 //定义核 17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15)); 18 //进行形态学操作 19 morphologyEx(image,image, MORPH_DILATE, element); 20 //显示效果图 21 imshow("【效果图】膨胀", image); 22 imwrite("膨胀.jpg", image); 23 waitKey(0); 24 25 return 0; 26 }
效果展示:
原图 膨胀
三、综合示例
1 #include <opencv2/opencv.hpp> 2 #include <opencv2/highgui/highgui.hpp> 3 #include <opencv2/imgproc/imgproc.hpp> 4 5 using namespace std; 6 using namespace cv; 7 8 Mat g_srcImage, g_dstImage;//原始图和效果图 9 int g_nElementShape = MORPH_RECT;//元素结构的形状 10 11 //变量接收的TrackBar位置参数 12 int g_nMaxIterationNum = 10; 13 int g_nOpenCloseNum = 0; 14 int g_nErodeDilateNum = 0; 15 int g_nTopBlackHatNum = 0; 16 17 static void on_OpenClose(int, void*);//回调函数 18 static void on_ErodeDilate(int, void*);//回调函数 19 static void on_TopBlackHat(int, void*);//回调函数 20 static void ShowHelpText();//帮助文字显示 21 22 int main( ) 23 { 24 25 ShowHelpText(); 26 27 //载入原图 28 g_srcImage = imread("1.jpg");//工程目录下需要有一张名为1.jpg的素材图 29 if( !g_srcImage.data ) 30 { 31 cout << "载入错误!" << endl; 32 } 33 //显示原始图 34 namedWindow("【原始图】"); 35 imshow("【原始图】", g_srcImage); 36 37 //创建三个窗口 38 namedWindow("【开运算/闭运算】",1); 39 namedWindow("【腐蚀/膨胀】",1); 40 namedWindow("【顶帽/黑帽】",1); 41 42 //参数赋值 43 g_nOpenCloseNum=9; 44 g_nErodeDilateNum=9; 45 g_nTopBlackHatNum=2; 46 47 //分别为三个窗口创建滚动条 48 createTrackbar("迭代值", "【开运算/闭运算】",&g_nOpenCloseNum,g_nMaxIterationNum*2+1,on_OpenClose); 49 createTrackbar("迭代值", "【腐蚀/膨胀】",&g_nErodeDilateNum,g_nMaxIterationNum*2+1,on_ErodeDilate); 50 createTrackbar("迭代值", "【顶帽/黑帽】",&g_nTopBlackHatNum,g_nMaxIterationNum*2+1,on_TopBlackHat); 51 52 //轮询获取按键信息 53 while(1) 54 { 55 int c; 56 57 //执行回调函数 58 on_OpenClose(g_nOpenCloseNum, 0); 59 on_ErodeDilate(g_nErodeDilateNum, 0); 60 on_TopBlackHat(g_nTopBlackHatNum,0); 61 62 //获取按键 63 c = waitKey(0); 64 65 //按下键盘按键Q或者ESC,程序退出 66 if( (char)c == 'q'||(char)c == 27 ) 67 break; 68 //按下键盘按键1,使用椭圆(Elliptic)结构元素结构元素MORPH_ELLIPSE 69 if( (char)c == 49 )//键盘按键1的ASII码为49 70 g_nElementShape = MORPH_ELLIPSE; 71 //按下键盘按键2,使用矩形(Rectangle)结构元素MORPH_RECT 72 else if( (char)c == 50 )//键盘按键2的ASII码为50 73 g_nElementShape = MORPH_RECT; 74 //按下键盘按键3,使用十字形(Cross-shaped)结构元素MORPH_CROSS 75 else if( (char)c == 51 )//键盘按键3的ASII码为51 76 g_nElementShape = MORPH_CROSS; 77 //按下键盘按键space,在矩形、椭圆、十字形结构元素中循环 78 else if( (char)c == ' ' ) 79 g_nElementShape = (g_nElementShape + 1) % 3; 80 } 81 82 return 0; 83 } 84 85 static void on_OpenClose(int, void*) 86 { 87 //偏移量的定义 88 int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量 89 int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值 90 //自定义核 91 Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) ); 92 //进行操作 93 if( offset < 0 ) 94 morphologyEx(g_srcImage, g_dstImage, CV_MOP_OPEN, element); 95 else 96 morphologyEx(g_srcImage, g_dstImage, CV_MOP_CLOSE, element); 97 //显示图像 98 imshow("【开运算/闭运算】",g_dstImage); 99 } 100 101 102 static void on_ErodeDilate(int, void*) 103 { 104 //偏移量的定义 105 int offset = g_nErodeDilateNum - g_nMaxIterationNum; //偏移量 106 int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值 107 //自定义核 108 Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) ); 109 //进行操作 110 if( offset < 0 ) 111 erode(g_srcImage, g_dstImage, element); 112 else 113 dilate(g_srcImage, g_dstImage, element); 114 //显示图像 115 imshow("【腐蚀/膨胀】",g_dstImage); 116 } 117 118 119 static void on_TopBlackHat(int, void*) 120 { 121 //偏移量的定义 122 int offset = g_nTopBlackHatNum - g_nMaxIterationNum;//偏移量 123 int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值 124 //自定义核 125 Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) ); 126 //进行操作 127 if( offset < 0 ) 128 morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT , element); 129 else 130 morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element); 131 //显示图像 132 imshow("【顶帽/黑帽】",g_dstImage); 133 } 134 135 static void ShowHelpText() 136 { 137 //输出一些帮助信息 138 cout << "\n\n\n\t请调整滚动条观察图像效果~\n\n"; 139 cout << "\n\n\t按键操作说明: \n\n" 140 "\t\t键盘按键【ESC】或者【Q】- 退出程序\n" 141 "\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n" 142 "\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n" 143 "\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n" 144 "\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n" 145 "\n\n\t\t\t\t\t\t\t\t"; 146 }