27.形态学图像运算(形态学梯度计算/开运算/闭运算/顶帽运算/黑帽)-- OpenCV从零开始到图像(人脸 + 物体)识别系列


本文作者:小嗷

微信公众号:aoxiaoji

吹比QQ群:736854977

微信链接:https://mp.weixin.qq.com/s?__biz=MzU1MTgxNjQyMg==&mid=2247483868&idx=1&sn=10389ce684aa0e13a74e11244bbb6933&chksm=fb8adc55ccfd5543395cf9cdcbaa02a61783b59056539e627168618794d9a1dc5641553aca52#rd


image
image
image

本文你会找到以下问题的答案:

  1. 形态学梯度计算(详解)

  2. 开运算(Opening Operation)

  3. 闭运算(Closing operation)

  4. 顶帽运算(Top Hat)

  5. 黑帽(Black Hat)

用途:

形态学梯度:形态学梯度来保留物体边缘轮廓。

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

闭运算:可以排除小的黑色区域。

黑帽运算:突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。

顶帽运算:放大了裂缝或者局部低亮度的区域。

(所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。)


2.1 形态学梯度计算(MorphologicalGradient)

当时小嗷在学OpenCV时候,其实对形态学梯度计算这个概念一笔跳过(就是简单运用一下API而已)。

不过,现在写文章打算写细点,方便大家以及自己日后查找和理解。

特点:对二值图进行这一操作可以将团块的边缘突出出来,我们可以用形态学梯度来保留物体的边缘轮廓

2.1.1 概念介绍

梯度用于刻画目标边界或边缘位于图像灰度级剧烈变化的区域.

形态学梯度根据膨胀或者腐蚀与原图作差组合来实现增强结构元素领域中像素的强度,突出高亮区域的外围

计算图像的形态学梯度是形态学重要操作,常常将膨胀和腐蚀基础操作组合起来一起使用实现一些复杂的图像形态学梯度。可以计算的梯度常见如下四种:

2.1.1.1 基本梯度(保留物体边缘轮廓)

基本梯度是用膨胀后的图像减去腐蚀后的图像得到差值图像,称为梯度图像也是OpenCV中支持的计算形态学梯度的方法,而此方法得到梯度有被称为基本梯度。

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

image.gif

膨胀图与腐蚀图之差,对二值图像进行这一操作可以将团块(blob)的边缘突出出来。形态学梯度来保留物体边缘轮廓

2.1.1.2 内部梯度

是用原图像减去腐蚀之后的图像得到差值图像,称为图像的内部梯度

原图 - 膨胀 = 内部梯度

2.1.1.3 外部梯度

是用图像膨胀之后再减去原来的图像得到的差值图像,称为图像的外部梯度。

膨胀 - 原图 = 外部梯度

2.1.1.4 方向梯度

方向梯度是使用X方向与Y方向的直线作为结构元素之后得到图像梯度,X的结构元素(核)分布膨胀与腐蚀得到图像之后求差值得到称为X方向梯度,用Y方向直线做结构分别膨胀与腐蚀之后得到图像求差值之后称为Y方向梯度。

即:

  1. 膨胀 - 腐蚀 = Y方向梯度(使用X方向直线作为结构元素(核)之后得到图像梯度)

  2. 腐蚀 - 膨胀 = X方向梯度(使用Y方向直线作为结构元素(核)之后得到图像梯度)

效果图如下:

image
  1. 原图

  2. 基本梯度

  3. 内部梯度

  4. 外部梯度

XY方向效果图如下:

image

处理方法很简单,第25篇写了膨胀与腐蚀 + 第6篇写了图像的数学运算

简单来说,就是对图像进行数学运算操作(小学数学:加减乘除)。

只有方向梯度有点难理解。不过,相信大家看完代码后,应该懂。不懂的话,QQ邮箱call小嗷。


这里就简单写写实现方法(基本梯度):

首先,膨胀和腐蚀函数用到API(当然,必要时小嗷会手写算法。)

  1. erode();

  2. dilate();

  3. getStructuringElement();

翻开小嗷写的第6篇文章,获取运算公式如下信息:

(有条件就PC打开,PC打开排版好看点,也可以去公众号底下的文章分类 -> 编程 -> 查看第四篇文章)

image.gif

代码如下:

 1/*  2功能:实现4中形态学梯度:基本梯度、内部梯度、外部梯度、方向梯度  3*/   4#include <opencv2/core/core.hpp>                   5#include <opencv2/highgui/highgui.hpp>                   6#include <opencv2/imgproc/imgproc.hpp>                 7#include <iostream>                 8using namespace std;   9using namespace cv;  10int main()  11{  12    Mat srcImage, grayImage; //源图像,输出图像,灰度图像  13    //---------【1】读取源图像并检查图像是否读取成功---------        14    srcImage = imread("D:\\OutPutResult\\ImageTest\\ju.jpg");  15    if (!srcImage.data)  16    {  17        cout << "读取图片错误,请重新输入正确路径!\n";  18        system("pause");  19        return -1;  20    }  21    imshow("【源图像】", srcImage);   22    //---------【2】获取自定义核及对源图像进行腐蚀与膨胀---------  23    Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));  24    Mat erode_ouput, dilate_output;  25    erode(srcImage, erode_ouput, element); //腐蚀  26    dilate(srcImage, dilate_output, element); //膨胀  27    //---------【3】计算基本梯度:膨胀后的图像减去腐蚀后的图像----------  28    Mat basicGradient;  29    subtract(dilate_output, erode_ouput, basicGradient, Mat());  30    imshow("【基本梯度】", basicGradient);  31    //---------【4】计算内部梯度:原图像减去腐蚀之后的图像----------  32    Mat internalGradientImg;  33    subtract(srcImage, erode_ouput, internalGradientImg, Mat());  34    imshow("【内部梯度】", internalGradientImg);  35    //---------【5】计算外部梯度:膨胀后的图像减去原图像----------  36    Mat externalGradientImg;  37    subtract(dilate_output, srcImage, externalGradientImg, Mat());  38    imshow("【外部梯度】", externalGradientImg);   39    //---------【6】方向梯度:使用X方向与Y方向的直线作为结构元素---------  40    Mat hse = getStructuringElement(MORPH_RECT, Size(srcImage.cols / 16, 1));  41    Mat vse = getStructuringElement(MORPH_RECT, Size(1, srcImage.rows / 16));  42    Mat erode_direct, dilate_direct;  43    Mat binImg, xDirectImg, yDirectImg;  44    // 转为灰度图  45    cvtColor(srcImage, grayImage, CV_BGR2GRAY);  46    // 将灰度图二值化  47    threshold(grayImage, binImg, 0, 255, CV_THRESH_OTSU);  48    // X 方向梯度:膨胀与腐蚀之后得到图像求差值  49    erode(binImg, erode_direct, hse);  50    dilate(binImg, dilate_direct, hse);  51    subtract(dilate_direct, erode_direct, xDirectImg, Mat());  52    imshow("【X 方向梯度】", xDirectImg);  53    // Y 方向梯度:膨胀与腐蚀之后得到图像求差值  54    erode(binImg, erode_direct, vse);  55    dilate(binImg, dilate_direct, vse);  56    subtract(dilate_direct, erode_direct, yDirectImg, Mat());  57    imshow("【Y 方向梯度】", yDirectImg);  58    waitKey(0);  59    return 0;  60}  

这里就不上效果图,基本和上面差不多,大家可以自己试试

2.2 开运算(Opening Operation)

用途:先腐蚀再膨胀,可以去掉目标外的孤立点

先腐蚀后膨胀的过程称为开运算。它具有消除细小物体,在纤细处分离物体和平滑较大物体边界的作用。

数学表达式:

dst = open(src,element) = dilate(erode(src, element))

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

2.3 闭运算(Closing operation)

用途:先膨胀再腐蚀,可以去掉目标内的孔。

先膨胀后腐蚀的过程称为闭运算。它具有填充物体内细小空洞,连接邻近物体和平滑边界的作用。

数学表达式:

dst = open(src,element) = erode(dilate(src, element))

闭运算可以用来排除小型黑洞(黑色区域)

2.4 顶帽(Top Hat)

用途:原图与开运算结果图之差,顶帽运算往往用来分离比临近点亮一些的斑块,在一幅图像具有大幅的背景,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。

数学表达式:

dst = tophat(src,element) = src - open(src,element)

因为开运算带来的结果是放大了裂缝或者局部低亮度的区域。因此从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作与选择的核的的大小相关。

2.5 黑帽(Black Hat)

“闭运算”的结果图与原图像之差,用来分离比临近点暗一些的斑块,效果图有着非常完美的轮廓

数学表达式:

dst = blackhat(src,element) = close(src,element) - src

黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关

image

3.1 morphology函数

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

作用:该函数可以进行形态学滤波的操作,里面包含了开运算、闭运算、形态学梯度、顶帽、黑帽、腐蚀、膨胀等。

参数详情:

参数1:输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV8U, CV16U,CV_16S, CV32F 或CV64F。

参数2:OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。

参数3:int类型的op,表示形态学运算的类型,可以是如下之一的标识符:

  • MORPH_OPEN – 开运算(Opening operation)

  • MORPH_CLOSE – 闭运算(Closing operation)

  • MORPH_GRADIENT -形态学梯度(Morphological gradient)

  • MORPH_TOPHAT - “顶帽”(“Top hat”)

  • MORPH_BLACKHAT - “黑帽”(“Black hat“)

  • MORPH_ERODE - “腐蚀”

  • MORPH_DILATE - “膨胀”

另有CV版本的标识符也可选择,如CVMOPCLOSE,CVMOPGRADIENT,CVMOPTOPHAT,CVMOPBLACKHAT等,这应该是OpenCV1.0系列版本遗留下来的标识符,和上面的“MORPH_OPEN”一样的效果。

参数4:InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。

我们一般使用函数 getStructuringElement()配合这个参数的使用。

getStructuringElement()函数会返回指定形状和尺寸的结构元素(内核矩阵)。关于getStructuringElement()函数(具体参照第25篇)

参数5:Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。

参数6:int类型的iterations,迭代使用函数的次数,默认值为1。

参数7:int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。

参数8:const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

使用morphologyEx()函数,一般我们只需要填前面的四个参数,后面的四个参数都有默认值。

3.2 morphologyEx函数的源代码

 1//-----------------------------------【erode()函数中文注释版源代码】----------------------------     2//   说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码        3//   源码路径:…\opencv\sources\modules\imgproc\src\morph.cpp     4//   源文件中如下代码的起始行数:1369行       5//--------------------------------------------------------------------------------------------------------      6void cv::morphologyEx( InputArray _src,OutputArray _dst, int op,   7                       InputArray kernel, Pointanchor, int iterations,   8                       int borderType, constScalar& borderValue )   9{  10//拷贝Mat数据到临时变量  11   Mat src = _src.getMat(), temp;  12   _dst.create(src.size(), src.type());  13   Mat dst = _dst.getMat();  14//一个大switch,根据不同的标识符取不同的操作  15   switch( op )  16    {  17   case MORPH_ERODE:  18       erode( src, dst, kernel, anchor, iterations, borderType, borderValue );  19       break;  20   case MORPH_DILATE:  21       dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  22       break;  23   case MORPH_OPEN:  24       erode( src, dst, kernel, anchor, iterations, borderType, borderValue );  25       dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );  26       break;  27   case CV_MOP_CLOSE:  28       dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  29       erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );  30       break;  31   case CV_MOP_GRADIENT:  32       erode( src, temp, kernel, anchor, iterations, borderType, borderValue );  33       dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );  34       dst -= temp;  35       break;  36   case CV_MOP_TOPHAT:  37       if( src.data != dst.data )  38           temp = dst;  39       erode( src, temp, kernel, anchor, iterations, borderType, borderValue );  40        dilate( temp, temp, kernel, anchor,iterations, borderType, borderValue );  41       dst = src - temp;  42       break;  43   case CV_MOP_BLACKHAT:  44       if( src.data != dst.data )  45           temp = dst;  46       dilate( src, temp, kernel, anchor, iterations, borderType, borderValue);  47       erode( temp, temp, kernel, anchor, iterations, borderType, borderValue);  48       dst = temp - src;  49       break;  50   default:  51       CV_Error( CV_StsBadArg, "unknown morphological operation" );  52    }  53}  

看上面的源码可以发现,其实morphologyEx函数其实就是内部一个大switch而已。根据不同的标识符取不同的操作。(对于,看小嗷文章各位,代码简单)

比如开运算MORPH_OPEN,按我们上文中讲解的数学表达式,就是先腐蚀后膨胀,即依次调用erode和dilate函数,为非常简明干净的代码。

3.3 函数运用示例

 1void usage_high_level_erode_dilate(char* which) 2{ 3    Mat   image = imread("./smimage/1.jpg"); 4    Mat   ele = getStructuringElement(MORPH_RECT, Size(5, 5)); 5    Mat   res; 6    //which 7    //open close gradient tophat blackhat 8    //o c g t b 9    switch (which[0]){10    case 'o':11        morphologyEx(image, res, MORPH_OPEN, ele);12        break;13    case 'c':14        morphologyEx(image, res, MORPH_CLOSE, ele);15        break;16    case 'g':17        morphologyEx(image, res, MORPH_GRADIENT, ele);18        break;19    case 't':20        morphologyEx(image, res, MORPH_TOPHAT, ele);21        break;22    case 'b':23        morphologyEx(image, res, MORPH_BLACKHAT, ele);24        break;25    }26    //show the image27    imshow("SRC", image);28    imshow(which, res);29    waitKey(0);30}
image.gif

任务:

简单调用API函数就OK(估计以后实战中,经常使用形态学图像处理问题)

代码如下:

  1/*  2功能:综合示例——形态学滤波  3一共包含11中操作:腐蚀、膨胀、开运算、闭运算、顶帽、黑帽  4形态学梯度(又为基本梯度)、内部梯度、外部梯度、X方向梯度、Y方向梯度  5*/  6#include <opencv2/core/core.hpp>                      7#include <opencv2/highgui/highgui.hpp>                      8#include <opencv2/imgproc/imgproc.hpp>                     9#include <iostream>    10using namespace cv; 11using namespace std; 12#define WINDOWNAME "【形态学滤波-效果图】"   13//-------------------【全局变量声明部分】------------------------   14Mat g_srcImage; //源图像   15Mat g_dstImage; //得到的效果图   16int g_nElementShape = MORPH_RECT;   //元素结构的形状   17int g_nStructElementSize = 3;   //结构元素(内核矩阵)的尺寸   18int g_nMaxNum = 21; //内核最大值   19int g_nTypeChoice = 0; //形态学操作类型选择   20                       //-------------------【全局函数声明部分】------------------------   21void on_TrackbarNumChange(int, void*); //类型选择-回调函数   22void on_ElementSizeChange(int, void*); //内核大小变换-回调函数   23void ShowHelpText();    //帮助文字显示   24void Process(); //对应的形态学操作   25void ErodeProcess();    //腐蚀操作   26void DilateProcess();   //膨胀操作   27void OpenProcess(); //开运算操作   28void CloseProcess();    //闭运算操作   29void TopHatProcess();   //顶帽操作   30void BlackHatProcess(); //黑帽操作   31void GradienteProcess();    //形态学梯度操作-即基本梯度   32void InternalGradientProcess(); //内部梯度操作   33void ExternalGradientProcess(); //外部操作   34void xDirectGradientProcess();  //X方向梯度操作   35void yDirectGradientProcess();  //Y方向梯度操作   36                                //--------------------------------【主函数】------------------------------   37int main() 38{ 39    //载入图像   40    g_srcImage = imread("D:/8.jpg"); 41    if (!g_srcImage.data) 42    { 43        cout << "读取图片错误,请重新输入正确路径!\n"; 44        system("pause"); 45        return -1; 46    } 47    namedWindow("【原始图】", WINDOW_AUTOSIZE); 48    namedWindow(WINDOWNAME, WINDOW_AUTOSIZE); 49    imshow("【原始图】", g_srcImage);//显示原始图   50    ShowHelpText(); 51    //创建轨迹条   52    createTrackbar("类型选择", WINDOWNAME, &g_nTypeChoice, 10, on_TrackbarNumChange); 53    createTrackbar("内核", WINDOWNAME, &g_nStructElementSize, g_nMaxNum, on_ElementSizeChange); 54    //轮询获取按键信息   55    while (1) 56    { 57        //执行回调函数   58        on_TrackbarNumChange(g_nTypeChoice, 0); 59        on_ElementSizeChange(g_nStructElementSize, 0); 60        //获取按键   61        int c; 62        c = waitKey(0); 63        //按下键盘Q键或者ESC,程序退出   64        if ((char)c == 'q' || (char)c == 27 || (char)c == 'Q') 65            break; 66        //按下键盘按键1,使用矩形(Rectangle)结构元素MORPH_RECT      67        if ((char)c == 49)//键盘按键1的ASII码为49   68            g_nElementShape = MORPH_RECT; 69        //按下键盘按键2,使用十字形(Cross)结构元素MORPH_CROSS     70        else if ((char)c == 50)//键盘按键2的ASII码为50   71            g_nElementShape = MORPH_CROSS; 72        //按下键盘按键3,使用椭圆(Elliptic)结构元素MORPH_ELLIPSE   73        else if ((char)c == 51)//键盘按键3的ASII码为51   74            g_nElementShape = MORPH_ELLIPSE; 75    } 76    return 0; 77} 78//------------【on_TrackbarNumChange()函数】-------------   79void on_TrackbarNumChange(int, void*) 80{ 81    //类型之间效果已经切换,回调函数体内需调用一次对应的操作函数,使改变后的效果立即生效并显示出来   82    Process(); 83} 84//------------【on_ElementSizeChange()函数】--------------   85void on_ElementSizeChange(int, void*) 86{ 87    //内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来   88    Process(); 89} 90//------------【进行对应的形态学操作】--------------   91void Process() 92{ 93    switch (g_nTypeChoice) 94    { 95    case 0:ErodeProcess(); break; 96    case 1:DilateProcess(); break; 97    case 2:OpenProcess(); break; 98    case 3:CloseProcess(); break; 99    case 4:TopHatProcess(); break;100    case 5:BlackHatProcess(); break;101    case 6:GradienteProcess(); break;102    case 7:InternalGradientProcess(); break;103    case 8:ExternalGradientProcess(); break;104    case 9:xDirectGradientProcess(); break;105    case 10:yDirectGradientProcess(); break;106    }107}108//-----------【描述:进行腐蚀操作】-----------  109void ErodeProcess()110{111    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核  112    erode(g_srcImage, g_dstImage, element); //进行腐蚀或膨胀操作  113    imshow(WINDOWNAME, g_dstImage); //显示效果图  114}115//-----------【描述:进行膨胀操作】-----------  116void DilateProcess()117{118    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1));//获取自定义核   119    dilate(g_srcImage, g_dstImage, element);//进行膨胀操作  120    imshow(WINDOWNAME, g_dstImage); //显示效果图  121}122//-----------【描述:进行开运算操作】-----------  123void OpenProcess()124{125    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核  126    morphologyEx(g_srcImage, g_dstImage, MORPH_OPEN, element);  //进行开运算操作:先腐蚀后膨胀  127    imshow(WINDOWNAME, g_dstImage); //显示效果图  128}129//-----------【描述:进行闭运算操作】-----------  130void CloseProcess()131{132    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1));//获取自定义核   133    morphologyEx(g_srcImage, g_dstImage, MORPH_CLOSE, element);//进行闭运算操作:先膨胀再腐蚀  134    imshow(WINDOWNAME, g_dstImage); //显示效果图  135}136//-----------【描述:进行顶帽操作】-----------  137void TopHatProcess()138{139    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核  140    morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT, element);    //进行顶帽操作:原图像与开运算结果图之差  141    imshow(WINDOWNAME, g_dstImage); //显示效果图  142}143//-----------【描述:进行黑帽操作】-----------  144void BlackHatProcess()145{146    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核  147    morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);  //进行黑帽操作:闭运算结果图与原图像之差  148    imshow(WINDOWNAME, g_dstImage); //显示效果图  149}150//-----------【描述:进行形态学梯度操作】-----------  151void GradienteProcess()152{153    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核  154    morphologyEx(g_srcImage, g_dstImage, MORPH_GRADIENT, element);  //进行形态学梯度操作:膨胀图像与腐蚀图像的之差  155    imshow(WINDOWNAME, g_dstImage); //显示效果图  156}157//-----------【描述:进行内部梯度操作】-----------  158void InternalGradientProcess()159{160    Mat erode_ouput;161    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核  162    morphologyEx(g_srcImage, erode_ouput, MORPH_ERODE, element);//进行腐蚀操作  163    subtract(g_srcImage, erode_ouput, g_dstImage, Mat());//计算内部梯度:原图像减去腐蚀之后的图像  164    imshow(WINDOWNAME, g_dstImage); //显示效果图  165}166//-----------【描述:进行外部梯度操作】-----------  167void ExternalGradientProcess()168{169    Mat dilate_output;170    Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核  171    morphologyEx(g_srcImage, dilate_output, MORPH_DILATE, element);//进行膨胀操作  172    subtract(dilate_output, g_srcImage, g_dstImage, Mat());//计算外部梯度:膨胀后的图像减去原图像  173    imshow(WINDOWNAME, g_dstImage); //显示效果图  174}175//-----------【描述:进行X方向梯度操作】-----------  176void xDirectGradientProcess()177{178    if (g_nStructElementSize == 0)179    {180        imshow(WINDOWNAME, g_srcImage); //显示原图  181    }182    else183    {184        Mat hse = getStructuringElement(g_nElementShape, Size(g_srcImage.cols / g_nStructElementSize, 1));185        Mat erode_direct, dilate_direct;186        erode(g_srcImage, erode_direct, hse);187        dilate(g_srcImage, dilate_direct, hse);188        subtract(dilate_direct, erode_direct, g_dstImage, Mat()); // X 方向梯度:膨胀与腐蚀之后得到图像求差值    189        imshow(WINDOWNAME, g_dstImage); //显示效果图  190    }191}192//-----------【描述:进行Y方向梯度操作】-----------  193void yDirectGradientProcess()194{195    if (g_nStructElementSize == 0)196    {197        imshow(WINDOWNAME, g_srcImage); //显示原图  198    }199    else200    {201        Mat vse = getStructuringElement(g_nElementShape, Size(1, g_srcImage.rows / g_nStructElementSize));202        Mat erode_direct, dilate_direct;203        erode(g_srcImage, erode_direct, vse);204        dilate(g_srcImage, dilate_direct, vse);205        subtract(dilate_direct, erode_direct, g_dstImage, Mat()); // Y 方向梯度:膨胀与腐蚀之后得到图像求差值    206        imshow(WINDOWNAME, g_dstImage); //显示效果图  207    }208}209//------------------【程序一些提示操作信息】-----------------  210void ShowHelpText()211{212    cout << "-----------------------------------------------------------" << endl;213    cout << "\t请调整滚动条观察效果\n" << endl;214    cout << "\t按键操作说明:" << endl;215    cout << "\t\t键盘按键【Esc】或者【Q】-退出程序" << endl;216    cout << "\t\t键盘按键【1】--使用矩形<Rectangle>结构内核" << endl;217    cout << "\t\t键盘按键【2】--使用十字形<Cross-shaped>结构内核" << endl;218    cout << "\t\t键盘按键【3】--使用椭圆<Elliptic>结构内核" << endl;219    cout << "-----------------------------------------------------------" << endl;220    cout << "\t类型选择说明:" << endl;221    cout << "\t\t0——腐蚀" << endl;222    cout << "\t\t1——膨胀" << endl;223    cout << "\t\t2——开运算" << endl;224    cout << "\t\t3——闭运算" << endl;225    cout << "\t\t4——顶帽操作" << endl;226    cout << "\t\t5——黑帽操作" << endl;227    cout << "\t\t6——形态学梯度(基本梯度)" << endl;228    cout << "\t\t7——内部梯度" << endl;229    cout << "\t\t8——外部梯度" << endl;230    cout << "\t\t9——X方向梯度" << endl;231    cout << "\t\t10——Y方向梯度" << endl;232}

效果图:

image

原图:

image

矩形核 --- 腐蚀:

image

消除不相关的细节(注意黄色部分的星星)

十字形核 --- 腐蚀:

image

相比矩形和下面的椭圆形图像的失真程度更低,星星部分同等消失

椭圆核 --- 腐蚀:

image

效果比矩形好(失真程度低)

膨胀:

image.gif

细节部分被破坏,同时亮度区域被获大,更好看出轮廓(膨胀腿变粗了,腐蚀腿变细了)

开运算:

image.gif

可以用来消除小物体,比起腐蚀,效果更好

闭运算:

image

填充物体内细小空洞,用来排除小型黑洞(乌鸦嘴巴上的鼻子消失,更贴近原图)

顶帽运算:

image

得到的效果图突出了比原图轮廓周围的区域更明亮的区域(放大了裂缝或者局部低亮度的区域。)

黑帽操作:

image.gif

突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。

梯度上面早已介绍,这里就不多说

image.gif
  1. 本人是抱着玩一玩的心态,学习opencv(其实深度学习没有外界说的这么高深,小嗷是白板,而且有工作在身并且于代码无关)

  2. 大家可以把我的数学水平想象成初中水平,毕竟小嗷既不是代码靠吃饭又不是靠数学吃饭,毕业N年

  3. 写文章主要是为了后人少走点弯路,多交点朋友,一起学习

  4. 如果有好的图像识别群拉我进去QQ:631821577

  5. 就我一个白板,最后还是成的,你们别怕,慢慢来把

image.gif

分享可以无数次,转载成自己文章QQ邮箱通知一下,未经授权请勿转载。

  • 邮箱:631821577@qq.com

  • QQ群:736854977

  • 有什么疑问公众号提问,下班或者周六日回答,ths

推荐文章:

6.图像的数学运算(图像运算法则+ROI特征项提取(叠化效果) --- OpenCV从零开始到图像(人脸 + 物体)识别系列【没有排版好】

(公众号底下的文章分类 -> 编程 -> 查看第四篇文章)【已经排版好,建议PC电脑看】

25.消除不相关的细节/裂缝桥接(形态学 --膨胀与腐蚀详解 )--- OpenCV从零开始到图像(人脸 + 物体)识别系列

为啥写这么详细?小嗷是打算以后处理图像时,直接看回本篇的效果内容

最后,附上思维导图

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 谨以此桃花献给我喜爱的作家张爱玲女士,愿她来生幸福,如愿! 于千万人之中,遇见你所以遇见的人,于千万年之中,时间的...
    蓝默蝶阅读 364评论 0 2
  • 2018-04·-11 姓名 :李宏清(单位)扬州市方圆建筑工程有限公司 哈尔滨363期反省二组 【日精进打卡第 ...
    李宏清阅读 129评论 0 0