本文作者:小嗷
微信公众号:aoxiaoji
吹比QQ群:736854977
本文你会找到以下问题的答案:
形态学梯度计算(详解)
开运算(Opening Operation)
闭运算(Closing operation)
顶帽运算(Top Hat)
黑帽(Black Hat)
用途:
形态学梯度:形态学梯度来保留物体边缘轮廓。
开运算:可以用来消除小物体,在纤细点处分离物体,并且在平滑较大物体的边界的同时不明显改变其面积
闭运算:可以排除小的黑色区域。
黑帽运算:突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。
顶帽运算:放大了裂缝或者局部低亮度的区域。
(所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。)
2.1 形态学梯度计算(MorphologicalGradient)
当时小嗷在学OpenCV时候,其实对形态学梯度计算这个概念一笔跳过(就是简单运用一下API而已)。
不过,现在写文章打算写细点,方便大家以及自己日后查找和理解。
特点:对二值图进行这一操作可以将团块的边缘突出出来,我们可以用形态学梯度来保留物体的边缘轮廓
2.1.1 概念介绍
梯度用于刻画目标边界或边缘位于图像灰度级剧烈变化的区域.
形态学梯度根据膨胀或者腐蚀与原图作差组合来实现增强结构元素领域中像素的强度,突出高亮区域的外围。
计算图像的形态学梯度是形态学重要操作,常常将膨胀和腐蚀基础操作组合起来一起使用实现一些复杂的图像形态学梯度。可以计算的梯度常见如下四种:
2.1.1.1 基本梯度(保留物体边缘轮廓)
基本梯度是用膨胀后的图像减去腐蚀后的图像得到差值图像,称为梯度图像也是OpenCV中支持的计算形态学梯度的方法,而此方法得到梯度有被称为基本梯度。
形态学梯度(morphological gradient)为膨胀图与腐蚀图之差,表达式如下:
膨胀图与腐蚀图之差,对二值图像进行这一操作可以将团块(blob)的边缘突出出来。形态学梯度来保留物体边缘轮廓
2.1.1.2 内部梯度
是用原图像减去腐蚀之后的图像得到差值图像,称为图像的内部梯度
原图 - 膨胀 = 内部梯度
2.1.1.3 外部梯度
是用图像膨胀之后再减去原来的图像得到的差值图像,称为图像的外部梯度。
膨胀 - 原图 = 外部梯度
2.1.1.4 方向梯度
方向梯度是使用X方向与Y方向的直线作为结构元素之后得到图像梯度,X的结构元素(核)分布膨胀与腐蚀得到图像之后求差值得到称为X方向梯度,用Y方向直线做结构分别膨胀与腐蚀之后得到图像求差值之后称为Y方向梯度。
即:
膨胀 - 腐蚀 = Y方向梯度(使用X方向直线作为结构元素(核)之后得到图像梯度)
腐蚀 - 膨胀 = X方向梯度(使用Y方向直线作为结构元素(核)之后得到图像梯度)
效果图如下:
原图
基本梯度
内部梯度
外部梯度
XY方向效果图如下:
处理方法很简单,第25篇写了膨胀与腐蚀 + 第6篇写了图像的数学运算
简单来说,就是对图像进行数学运算操作(小学数学:加减乘除)。
只有方向梯度有点难理解。不过,相信大家看完代码后,应该懂。不懂的话,QQ邮箱call小嗷。
这里就简单写写实现方法(基本梯度):
首先,膨胀和腐蚀函数用到API(当然,必要时小嗷会手写算法。)
erode();
dilate();
getStructuringElement();
翻开小嗷写的第6篇文章,获取运算公式如下信息:
(有条件就PC打开,PC打开排版好看点,也可以去公众号底下的文章分类 -> 编程 -> 查看第四篇文章)
代码如下:
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
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关
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}
任务:
简单调用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}
效果图:
原图:
矩形核 --- 腐蚀:
消除不相关的细节(注意黄色部分的星星)
十字形核 --- 腐蚀:
相比矩形和下面的椭圆形图像的失真程度更低,星星部分同等消失
椭圆核 --- 腐蚀:
效果比矩形好(失真程度低)
膨胀:
细节部分被破坏,同时亮度区域被获大,更好看出轮廓(膨胀腿变粗了,腐蚀腿变细了)
开运算:
可以用来消除小物体,比起腐蚀,效果更好
闭运算:
填充物体内细小空洞,用来排除小型黑洞(乌鸦嘴巴上的鼻子消失,更贴近原图)
顶帽运算:
得到的效果图突出了比原图轮廓周围的区域更明亮的区域(放大了裂缝或者局部低亮度的区域。)
黑帽操作:
突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。
梯度上面早已介绍,这里就不多说
本人是抱着玩一玩的心态,学习opencv(其实深度学习没有外界说的这么高深,小嗷是白板,而且有工作在身并且于代码无关)
大家可以把我的数学水平想象成初中水平,毕竟小嗷既不是代码靠吃饭又不是靠数学吃饭,毕业N年
写文章主要是为了后人少走点弯路,多交点朋友,一起学习
如果有好的图像识别群拉我进去QQ:631821577
就我一个白板,最后还是成的,你们别怕,慢慢来把
分享可以无数次,转载成自己文章QQ邮箱通知一下,未经授权请勿转载。
QQ群:736854977
有什么疑问公众号提问,下班或者周六日回答,ths
推荐文章:
6.图像的数学运算(图像运算法则+ROI特征项提取(叠化效果) --- OpenCV从零开始到图像(人脸 + 物体)识别系列【没有排版好】
(公众号底下的文章分类 -> 编程 -> 查看第四篇文章)【已经排版好,建议PC电脑看】
25.消除不相关的细节/裂缝桥接(形态学 --膨胀与腐蚀详解 )--- OpenCV从零开始到图像(人脸 + 物体)识别系列
为啥写这么详细?小嗷是打算以后处理图像时,直接看回本篇的效果内容
最后,附上思维导图