本文作者:小嗷
微信公众号:aoxiaoji
吹比QQ群:736854977
简书链接:https://www.jianshu.com/u/45da1fbce7d0
形态学梯度:形态学梯度来保留物体边缘轮廓。
开运算:可以用来消除小物体,在纤细点处分离物体,并且在平滑较大物体的边界的同时不明显改变其面积
闭运算:可以排除小的黑色区域。
黑帽运算:突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。
顶帽运算:放大了裂缝或者局部低亮度的区域。
(所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。)
当时小嗷在学OpenCV时候,其实对形态学梯度计算这个概念一笔跳过(就是简单运用一下API而已)。
不过,现在写文章打算写细点,方便大家以及自己日后查找和理解。
特点:对二值图进行这一操作可以将团块的边缘突出出来,我们可以用形态学梯度来保留物体的边缘轮廓
梯度用于刻画目标边界或边缘位于图像灰度级剧烈变化的区域.
形态学梯度根据膨胀或者腐蚀与原图作差组合来实现增强结构元素领域中像素的强度,突出高亮区域的外围。
计算图像的形态学梯度是形态学重要操作,常常将膨胀和腐蚀基础操作组合起来一起使用实现一些复杂的图像形态学梯度。可以计算的梯度常见如下四种:
基本梯度是用膨胀后的图像减去腐蚀后的图像得到差值图像,称为梯度图像也是OpenCV中支持的计算形态学梯度的方法,而此方法得到梯度有被称为基本梯度。
形态学梯度(morphological gradient)为膨胀图与腐蚀图之差,表达式如下:
膨胀图与腐蚀图之差,对二值图像进行这一操作可以将团块(blob)的边缘突出出来。形态学梯度来保留物体边缘轮廓
是用原图像减去腐蚀之后的图像得到差值图像,称为图像的内部梯度
原图 - 膨胀 = 内部梯度
是用图像膨胀之后再减去原来的图像得到的差值图像,称为图像的外部梯度。
膨胀 - 原图 = 外部梯度
方向梯度是使用X方向与Y方向的直线作为结构元素之后得到图像梯度,X的结构元素(核)分布膨胀与腐蚀得到图像之后求差值得到称为X方向梯度,用Y方向直线做结构分别膨胀与腐蚀之后得到图像求差值之后称为Y方向梯度。
即:
效果图如下:
XY方向效果图如下:
处理方法很简单,第25篇写了膨胀与腐蚀 + 第6篇写了图像的数学运算
简单来说,就是对图像进行数学运算操作(小学数学:加减乘除)。
只有方向梯度有点难理解。不过,相信大家看完代码后,应该懂。不懂的话,QQ邮箱call小嗷。
首先,膨胀和腐蚀函数用到API(当然,必要时小嗷会手写算法。)
翻开小嗷写的第6篇文章,获取运算公式如下信息:
(有条件就PC打开,PC打开排版好看点,也可以去公众号底下的文章分类 -> 编程 -> 查看第四篇文章)
代码如下:
/*
功能:实现4中形态学梯度:基本梯度、内部梯度、外部梯度、方向梯度
*/
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat srcImage, grayImage; //源图像,输出图像,灰度图像
//---------【1】读取源图像并检查图像是否读取成功---------
srcImage = imread("D:\\OutPutResult\\ImageTest\\ju.jpg");
if (!srcImage.data)
{
cout << "读取图片错误,请重新输入正确路径!\n";
system("pause");
return -1;
}
imshow("【源图像】", srcImage);
//---------【2】获取自定义核及对源图像进行腐蚀与膨胀---------
Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
Mat erode_ouput, dilate_output;
erode(srcImage, erode_ouput, element); //腐蚀
dilate(srcImage, dilate_output, element); //膨胀
//---------【3】计算基本梯度:膨胀后的图像减去腐蚀后的图像----------
Mat basicGradient;
subtract(dilate_output, erode_ouput, basicGradient, Mat());
imshow("【基本梯度】", basicGradient);
//---------【4】计算内部梯度:原图像减去腐蚀之后的图像----------
Mat internalGradientImg;
subtract(srcImage, erode_ouput, internalGradientImg, Mat());
imshow("【内部梯度】", internalGradientImg);
//---------【5】计算外部梯度:膨胀后的图像减去原图像----------
Mat externalGradientImg;
subtract(dilate_output, srcImage, externalGradientImg, Mat());
imshow("【外部梯度】", externalGradientImg);
//---------【6】方向梯度:使用X方向与Y方向的直线作为结构元素---------
Mat hse = getStructuringElement(MORPH_RECT, Size(srcImage.cols / 16, 1));
Mat vse = getStructuringElement(MORPH_RECT, Size(1, srcImage.rows / 16));
Mat erode_direct, dilate_direct;
Mat binImg, xDirectImg, yDirectImg;
// 转为灰度图
cvtColor(srcImage, grayImage, CV_BGR2GRAY);
// 将灰度图二值化
threshold(grayImage, binImg, 0, 255, CV_THRESH_OTSU);
// X 方向梯度:膨胀与腐蚀之后得到图像求差值
erode(binImg, erode_direct, hse);
dilate(binImg, dilate_direct, hse);
subtract(dilate_direct, erode_direct, xDirectImg, Mat());
imshow("【X 方向梯度】", xDirectImg);
// Y 方向梯度:膨胀与腐蚀之后得到图像求差值
erode(binImg, erode_direct, vse);
dilate(binImg, dilate_direct, vse);
subtract(dilate_direct, erode_direct, yDirectImg, Mat());
imshow("【Y 方向梯度】", yDirectImg);
waitKey(0);
return 0;
}
这里就不上效果图,基本和上面差不多,大家可以自己试试
用途:先腐蚀再膨胀,可以去掉目标外的孤立点
先腐蚀后膨胀的过程称为开运算。它具有消除细小物体,在纤细处分离物体和平滑较大物体边界的作用。
数学表达式:
dst = open(src,element) = dilate(erode(src, element))
开运算可以用来消除小物体,在纤细点处分离物体,并且在平滑较大物体的边界的同时不明显改变其面积。
用途:先膨胀再腐蚀,可以去掉目标内的孔。
先膨胀后腐蚀的过程称为闭运算。它具有填充物体内细小空洞,连接邻近物体和平滑边界的作用。
数学表达式:
dst = open(src,element) = erode(dilate(src, element))
闭运算可以用来排除小型黑洞(黑色区域)
用途:原图与开运算结果图之差,顶帽运算往往用来分离比临近点亮一些的斑块,在一幅图像具有大幅的背景,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
数学表达式:
dst = tophat(src,element) = src - open(src,element)
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域。因此从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作与选择的核的的大小相关。
“闭运算”的结果图与原图像之差,用来分离比临近点暗一些的斑块,效果图有着非常完美的轮廓
数学表达式:
dst = blackhat(src,element) = close(src,element) - src
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关
void 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类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
参数2:OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
参数3:int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
另有CV版本的标识符也可选择,如CV_MOP_CLOSE,CV_MOP_GRADIENT,CV_MOP_TOPHAT,CV_MOP_BLACKHAT等,这应该是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()函数,一般我们只需要填前面的四个参数,后面的四个参数都有默认值。
//-----------------------------------【erode()函数中文注释版源代码】----------------------------
// 说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码
// 源码路径:…\opencv\sources\modules\imgproc\src\morph.cpp
// 源文件中如下代码的起始行数:1369行
//--------------------------------------------------------------------------------------------------------
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函数其实就是内部一个大switch而已。根据不同的标识符取不同的操作。(对于,看小嗷文章各位,代码简单)
比如开运算MORPH_OPEN,按我们上文中讲解的数学表达式,就是先腐蚀后膨胀,即依次调用erode和dilate函数,为非常简明干净的代码。
void usage_high_level_erode_dilate(char* which)
{
Mat image = imread("./smimage/1.jpg");
Mat ele = getStructuringElement(MORPH_RECT, Size(5, 5));
Mat res;
//which
//open close gradient tophat blackhat
//o c g t b
switch (which[0]){
case 'o':
morphologyEx(image, res, MORPH_OPEN, ele);
break;
case 'c':
morphologyEx(image, res, MORPH_CLOSE, ele);
break;
case 'g':
morphologyEx(image, res, MORPH_GRADIENT, ele);
break;
case 't':
morphologyEx(image, res, MORPH_TOPHAT, ele);
break;
case 'b':
morphologyEx(image, res, MORPH_BLACKHAT, ele);
break;
}
//show the image
imshow("SRC", image);
imshow(which, res);
waitKey(0);
}
任务:
简单调用API函数就OK(估计以后实战中,经常使用形态学图像处理问题)
代码如下:
/*
功能:综合示例——形态学滤波
一共包含11中操作:腐蚀、膨胀、开运算、闭运算、顶帽、黑帽
形态学梯度(又为基本梯度)、内部梯度、外部梯度、X方向梯度、Y方向梯度
*/
#include
#include
#include
#include
using namespace cv;
using namespace std;
#define WINDOWNAME "【形态学滤波-效果图】"
//-------------------【全局变量声明部分】------------------------
Mat g_srcImage; //源图像
Mat g_dstImage; //得到的效果图
int g_nElementShape = MORPH_RECT; //元素结构的形状
int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
int g_nMaxNum = 21; //内核最大值
int g_nTypeChoice = 0; //形态学操作类型选择
//-------------------【全局函数声明部分】------------------------
void on_TrackbarNumChange(int, void*); //类型选择-回调函数
void on_ElementSizeChange(int, void*); //内核大小变换-回调函数
void ShowHelpText(); //帮助文字显示
void Process(); //对应的形态学操作
void ErodeProcess(); //腐蚀操作
void DilateProcess(); //膨胀操作
void OpenProcess(); //开运算操作
void CloseProcess(); //闭运算操作
void TopHatProcess(); //顶帽操作
void BlackHatProcess(); //黑帽操作
void GradienteProcess(); //形态学梯度操作-即基本梯度
void InternalGradientProcess(); //内部梯度操作
void ExternalGradientProcess(); //外部操作
void xDirectGradientProcess(); //X方向梯度操作
void yDirectGradientProcess(); //Y方向梯度操作
//--------------------------------【主函数】------------------------------
int main()
{
//载入图像
g_srcImage = imread("D:/8.jpg");
if (!g_srcImage.data)
{
cout << "读取图片错误,请重新输入正确路径!\n";
system("pause");
return -1;
}
namedWindow("【原始图】", WINDOW_AUTOSIZE);
namedWindow(WINDOWNAME, WINDOW_AUTOSIZE);
imshow("【原始图】", g_srcImage);//显示原始图
ShowHelpText();
//创建轨迹条
createTrackbar("类型选择", WINDOWNAME, &g_nTypeChoice, 10, on_TrackbarNumChange);
createTrackbar("内核", WINDOWNAME, &g_nStructElementSize, g_nMaxNum, on_ElementSizeChange);
//轮询获取按键信息
while (1)
{
//执行回调函数
on_TrackbarNumChange(g_nTypeChoice, 0);
on_ElementSizeChange(g_nStructElementSize, 0);
//获取按键
int c;
c = waitKey(0);
//按下键盘Q键或者ESC,程序退出
if ((char)c == 'q' || (char)c == 27 || (char)c == 'Q')
break;
//按下键盘按键1,使用矩形(Rectangle)结构元素MORPH_RECT
if ((char)c == 49)//键盘按键1的ASII码为49
g_nElementShape = MORPH_RECT;
//按下键盘按键2,使用十字形(Cross)结构元素MORPH_CROSS
else if ((char)c == 50)//键盘按键2的ASII码为50
g_nElementShape = MORPH_CROSS;
//按下键盘按键3,使用椭圆(Elliptic)结构元素MORPH_ELLIPSE
else if ((char)c == 51)//键盘按键3的ASII码为51
g_nElementShape = MORPH_ELLIPSE;
}
return 0;
}
//------------【on_TrackbarNumChange()函数】-------------
void on_TrackbarNumChange(int, void*)
{
//类型之间效果已经切换,回调函数体内需调用一次对应的操作函数,使改变后的效果立即生效并显示出来
Process();
}
//------------【on_ElementSizeChange()函数】--------------
void on_ElementSizeChange(int, void*)
{
//内核尺寸已改变,回调函数体内需调用一次Process函数,使改变后的效果立即生效并显示出来
Process();
}
//------------【进行对应的形态学操作】--------------
void Process()
{
switch (g_nTypeChoice)
{
case 0:ErodeProcess(); break;
case 1:DilateProcess(); break;
case 2:OpenProcess(); break;
case 3:CloseProcess(); break;
case 4:TopHatProcess(); break;
case 5:BlackHatProcess(); break;
case 6:GradienteProcess(); break;
case 7:InternalGradientProcess(); break;
case 8:ExternalGradientProcess(); break;
case 9:xDirectGradientProcess(); break;
case 10:yDirectGradientProcess(); break;
}
}
//-----------【描述:进行腐蚀操作】-----------
void ErodeProcess()
{
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核
erode(g_srcImage, g_dstImage, element); //进行腐蚀或膨胀操作
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行膨胀操作】-----------
void DilateProcess()
{
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1));//获取自定义核
dilate(g_srcImage, g_dstImage, element);//进行膨胀操作
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行开运算操作】-----------
void OpenProcess()
{
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核
morphologyEx(g_srcImage, g_dstImage, MORPH_OPEN, element); //进行开运算操作:先腐蚀后膨胀
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行闭运算操作】-----------
void CloseProcess()
{
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1));//获取自定义核
morphologyEx(g_srcImage, g_dstImage, MORPH_CLOSE, element);//进行闭运算操作:先膨胀再腐蚀
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行顶帽操作】-----------
void TopHatProcess()
{
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核
morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT, element); //进行顶帽操作:原图像与开运算结果图之差
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行黑帽操作】-----------
void BlackHatProcess()
{
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核
morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element); //进行黑帽操作:闭运算结果图与原图像之差
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行形态学梯度操作】-----------
void GradienteProcess()
{
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核
morphologyEx(g_srcImage, g_dstImage, MORPH_GRADIENT, element); //进行形态学梯度操作:膨胀图像与腐蚀图像的之差
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行内部梯度操作】-----------
void InternalGradientProcess()
{
Mat erode_ouput;
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核
morphologyEx(g_srcImage, erode_ouput, MORPH_ERODE, element);//进行腐蚀操作
subtract(g_srcImage, erode_ouput, g_dstImage, Mat());//计算内部梯度:原图像减去腐蚀之后的图像
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行外部梯度操作】-----------
void ExternalGradientProcess()
{
Mat dilate_output;
Mat element = getStructuringElement(g_nElementShape, Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize + 1)); //获取自定义核
morphologyEx(g_srcImage, dilate_output, MORPH_DILATE, element);//进行膨胀操作
subtract(dilate_output, g_srcImage, g_dstImage, Mat());//计算外部梯度:膨胀后的图像减去原图像
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
//-----------【描述:进行X方向梯度操作】-----------
void xDirectGradientProcess()
{
if (g_nStructElementSize == 0)
{
imshow(WINDOWNAME, g_srcImage); //显示原图
}
else
{
Mat hse = getStructuringElement(g_nElementShape, Size(g_srcImage.cols / g_nStructElementSize, 1));
Mat erode_direct, dilate_direct;
erode(g_srcImage, erode_direct, hse);
dilate(g_srcImage, dilate_direct, hse);
subtract(dilate_direct, erode_direct, g_dstImage, Mat()); // X 方向梯度:膨胀与腐蚀之后得到图像求差值
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
}
//-----------【描述:进行Y方向梯度操作】-----------
void yDirectGradientProcess()
{
if (g_nStructElementSize == 0)
{
imshow(WINDOWNAME, g_srcImage); //显示原图
}
else
{
Mat vse = getStructuringElement(g_nElementShape, Size(1, g_srcImage.rows / g_nStructElementSize));
Mat erode_direct, dilate_direct;
erode(g_srcImage, erode_direct, vse);
dilate(g_srcImage, dilate_direct, vse);
subtract(dilate_direct, erode_direct, g_dstImage, Mat()); // Y 方向梯度:膨胀与腐蚀之后得到图像求差值
imshow(WINDOWNAME, g_dstImage); //显示效果图
}
}
//------------------【程序一些提示操作信息】-----------------
void ShowHelpText()
{
cout << "-----------------------------------------------------------" << endl;
cout << "\t请调整滚动条观察效果\n" << endl;
cout << "\t按键操作说明:" << endl;
cout << "\t\t键盘按键【Esc】或者【Q】-退出程序" << endl;
cout << "\t\t键盘按键【1】--使用矩形结构内核" << endl;
cout << "\t\t键盘按键【2】--使用十字形结构内核" << endl;
cout << "\t\t键盘按键【3】--使用椭圆结构内核" << endl;
cout << "-----------------------------------------------------------" << endl;
cout << "\t类型选择说明:" << endl;
cout << "\t\t0——腐蚀" << endl;
cout << "\t\t1——膨胀" << endl;
cout << "\t\t2——开运算" << endl;
cout << "\t\t3——闭运算" << endl;
cout << "\t\t4——顶帽操作" << endl;
cout << "\t\t5——黑帽操作" << endl;
cout << "\t\t6——形态学梯度(基本梯度)" << endl;
cout << "\t\t7——内部梯度" << endl;
cout << "\t\t8——外部梯度" << endl;
cout << "\t\t9——X方向梯度" << endl;
cout << "\t\t10——Y方向梯度" << endl;
}
效果图:
原图:
矩形核 — 腐蚀:
消除不相关的细节(注意黄色部分的星星)
十字形核 — 腐蚀:
相比矩形和下面的椭圆形图像的失真程度更低,星星部分同等消失
椭圆核 — 腐蚀:
效果比矩形好(失真程度低)
膨胀:
细节部分被破坏,同时亮度区域被获大,更好看出轮廓(膨胀腿变粗了,腐蚀腿变细了)
开运算:
可以用来消除小物体,比起腐蚀,效果更好
闭运算:
填充物体内细小空洞,用来排除小型黑洞(乌鸦嘴巴上的鼻子消失,更贴近原图)
顶帽运算:
得到的效果图突出了比原图轮廓周围的区域更明亮的区域(放大了裂缝或者局部低亮度的区域。)
黑帽操作:
突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。
梯度上面早已介绍,这里就不多说
分享可以无数次,转载成自己文章QQ邮箱通知一下,未经授权请勿转载。
推荐文章:
6.图像的数学运算(图像运算法则+ROI特征项提取(叠化效果) — OpenCV从零开始到图像(人脸 + 物体)识别系列【没有排版好】
(公众号底下的文章分类 -> 编程 -> 查看第四篇文章)【已经排版好,建议PC电脑看】
25.消除不相关的细节/裂缝桥接(形态学 –膨胀与腐蚀详解 )— OpenCV从零开始到图像(人脸 + 物体)识别系列
为啥写这么详细?小嗷是打算以后处理图像时,直接看回本篇的效果内容
最后,附上思维导图