zoukankan      html  css  js  c++  java
  • 18、【opencv入门】形态学图像处理(一):开运算、闭运算、形态学梯度、顶帽、黑帽合辑

    、理论与概念讲解

    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 << "
    
    
    	请调整滚动条观察图像效果~
    
    ";
    139     cout << "
    
    	按键操作说明: 
    
    "
    140         "		键盘按键【ESC】或者【Q】- 退出程序
    "
    141         "		键盘按键【1】- 使用椭圆(Elliptic)结构元素
    "
    142         "		键盘按键【2】- 使用矩形(Rectangle )结构元素
    "
    143         "		键盘按键【3】- 使用十字型(Cross-shaped)结构元素
    "
    144         "		键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环
    "
    145         "
    
    								";
    146 }
  • 相关阅读:
    POJ 1981 Circle and Points (扫描线)
    UVA 1514 Piece it together (二分图匹配)
    CodeForces 140D New Year Contest
    HDU 4863 Centroid of a Tree
    HDU 4865 Peter's Hobby
    HDU 4870 Rating
    HDU 4864 Task
    CodeForces 766E Mahmoud and a xor trip
    CodeForces 766D Mahmoud and a Dictionary
    CodeForces 767D Cartons of milk
  • 原文地址:https://www.cnblogs.com/Long-w/p/9663316.html
Copyright © 2011-2022 走看看