14、OpenCV形态学操作——开运算、闭运算、形态学梯度、顶帽、黑帽

OpenCV形态学操作——开运算、闭运算、形态学梯度、顶帽、黑帽

一、学习目标

  • 理解什么是开运算、闭运算、形态学梯度、顶帽和黑帽
  • 学会使用OpenCV实现上述的图像形态学操作
  • 使用综合性的例子进行实验

二、各种操作简介

1、开运算

开运算:先腐蚀后膨胀,表达公式为:

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

开运算可以用来消除图像中的细小对象(前提是黑色为类背景,类白色为前景),在纤细点处分离物体和平滑较大物体的边界而又不明显改变其面积和形状。

例如,看看下面的例子。左边的图像是原始图像,右边的图像是应用开运算变换后的结果。我们可以看到白色小点消失了。

在这里插入图片描述
OpenCV中,使用函数cv::morphologyEx实现开运算,其函数原型为:

void cv::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() )	
  • 参数 src: 源图像。信道的数量可以是任意的。深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F之一
  • 参数 dst: 大小和类型与源图像相同的输出图像
  • 参数 op: 指定形态学操作的类型,请参见MorphTypes,实现开操作时指定为MORPH_OPEN
  • 参数 kernel: 结构元素。它可以使用getStructuringElement创建
  • 参数 anchor: 锚定在内核中的位置。负值表示锚点在内核中心
  • 参数 iterations: 开运算(其它运算也是如此)迭代的次数
  • 参数 borderType: 图像边界处像素填充方法,见BorderTypes不支持BORDER_WRAP
  • 参数 borderValue: 指定图像边界像素填充方法为常量时的配套函数

函数cv::morphologyEx可以使用腐蚀和膨胀作为基本操作来执行高级形态学操作。任何操作都可以就地完成。对于多通道图像,每个通道都是独立处理的。通过指定不同的op实现不同的形态学操作,op的取值见下表:

参数取值取值说明
MORPH_OPEN开运算,dst=open(src,element)=dilate(erode(src,element))
MORPH_CLOSE闭运算,dst=close(src,element)=erode(dilate(src,element))
MORPH_GRADIENT形态学梯度,dst=morph_grad(src,element)=dilate(src,element)−erode(src,element)
MORPH_TOPHAT顶帽,dst=tophat(src,element)=src−open(src,element)
MORPH_BLACKHAT黑帽,dst=blackhat(src,element)=close(src,element)−src
MORPH_HITMISS“hit or miss”,只支持CV_8UC1二进制图像

可见,对于形态学操作的其它操作,也只需要改变op的值即可,所以后面的相关操作就只是简单地介绍原理,相应的函数API不再赘述。

注意: 迭代次数是应用腐蚀或膨胀操作的次数。例如,具有2次迭代的开运算(MORPH_OPEN)等效于依次应用:腐蚀->腐蚀->膨胀->膨胀(而不是腐蚀->膨胀->腐蚀->膨胀)。

使用实例:
先上传一张实验用图供大家下载:
在这里插入图片描述
实验代码:

#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main(int argc,char **argv)
{
	char fileName[] = "O:\\CSDN\\MORPH.png";
	Mat src = imread(fileName, IMREAD_COLOR);
	if (src.empty())
	{
		cout << "couldn't open image: " << fileName << ",please check out!\n";
		system("pause");
		return EXIT_FAILURE;
	}
    // 定义结构元素,可以自行尝试不同的SIZE,去除小物体(噪声)的效果不同
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
	Mat dst;
	// 执行开运算
	morphologyEx(src, dst, MORPH_OPEN, kernel);

	namedWindow("Original", WINDOW_AUTOSIZE);
	moveWindow("Original", 200, 200);
	namedWindow("MORPH_OPEN", WINDOW_AUTOSIZE);
	moveWindow("MORPH_OPEN", src.cols + 200, 200);
	imshow("Original", src);
	imshow("MORPH_OPEN", dst);
	waitKey(0);

	system("pause");
	return EXIT_SUCCESS;
}

在这里插入图片描述
对比右边开运算的结果,很多较小的图形已经被去除,当然也还有一些没去掉,这个就与Size的取值有关,可以自行尝试。

2、闭运算

闭运算:先膨胀后腐蚀。其公式为:

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

闭运算可以用来填充目标内部的细小孔洞(fill hole),将断开的邻近目标连接,在不明显改变物体面积和形状的情况下平滑其边界。

如,闭运算可以达到下图的效果:

在这里插入图片描述
OpenCV实现闭运算的函数API不再赘述,直接看实例:

#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main(int argc,char **argv)
{
	char fileName[] = "O:\\CSDN\\MORPH.png";
	Mat src = imread(fileName, IMREAD_COLOR);
	if (src.empty())
	{
		cout << "couldn't open image: " << fileName << ",please check out!\n";
		system("pause");
		return EXIT_FAILURE;
	}

	Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));
	Mat dst;
	morphologyEx(src, dst, MORPH_CLOSE, kernel);

	namedWindow("Original", WINDOW_AUTOSIZE);
	moveWindow("Original", 200, 200);
	namedWindow("MORPH_CLOSE", WINDOW_AUTOSIZE);
	moveWindow("MORPH_CLOSE", src.cols + 200, 200);
	imshow("Original", src);
	imshow("MORPH_CLOSE", dst);
	waitKey(0);

	system("pause");
	return EXIT_SUCCESS;
}

在这里插入图片描述
从上图可以看出,许多的黑色小孔已经被堵上,但比较大的空是难以完全填充上的。

3、形态学梯度

形态学梯度操作能描述图像亮度变化的剧烈程度;当我们想要突出高亮区域的外围时,则可以选用形态学梯度来突出边缘,可以保留物体的边缘轮廓。

常见的几种梯度:

  • 基本梯度:用膨胀后的图像减去腐蚀后的图像得到的差值图像。
  • 内部梯度:用原图像减去腐蚀之后的图像得到的差值图像
  • 外部梯度:用膨胀之后得到的图像减去原图像得到的差值图像
  • 方向梯度:使用X方向与Y方向的直线作为结构元素,X的结构元素分别膨胀与腐蚀得到图像之后求差值为X方向梯度,也可以理解为用X的结构元素进行闭运算。用Y的结构元素分别膨胀与腐蚀得到的图像之后求差值为Y方向梯度,也可以理解为用Y的结构元素进行闭运算

如使用基本梯度可以得到如下效果:

在这里插入图片描述
这相当于提出了目标的外部轮廓或边缘。使用实例如下:

#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main(int argc,char **argv)
{
	char fileName[] = "O:\\CSDN\\MORPH.png";
	Mat src = imread(fileName, IMREAD_COLOR);
	if (src.empty())
	{
		cout << "couldn't open image: " << fileName << ",please check out!\n";
		system("pause");
		return EXIT_FAILURE;
	}

	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
	Mat dst;
	morphologyEx(src, dst, MORPH_GRADIENT, kernel);

	namedWindow("Original", WINDOW_AUTOSIZE);
	moveWindow("Original", 200, 200);
	namedWindow("MORPH_GRADIENT", WINDOW_AUTOSIZE);
	moveWindow("MORPH_GRADIENT", src.cols + 200, 200);
	imshow("Original", src);
	imshow("MORPH_GRADIENT", dst);
	waitKey(0);

	system("pause");
	return EXIT_SUCCESS;
}

在这里插入图片描述

4、顶帽

顶帽是原图与原图的开运算的差值图像。开运算放大了裂缝或者局部低亮度的区域,所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。TopHat运算一般用来分离比邻近点亮一些的斑块,可以使用这个运算提取背景。

其公式为:

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

有如下效果:
在这里插入图片描述
使用实例为:

#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main(int argc,char **argv)
{
	char fileName[] = "O:\\CSDN\\MORPH.png";
	Mat src = imread(fileName, IMREAD_COLOR);
	if (src.empty())
	{
		cout << "couldn't open image: " << fileName << ",please check out!\n";
		system("pause");
		return EXIT_FAILURE;
	}

	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
	Mat dst;
	morphologyEx(src, dst, MORPH_TOPHAT, kernel);

	namedWindow("Original", WINDOW_AUTOSIZE);
	moveWindow("Original", 200, 200);
	namedWindow("MORPH_TOPHAT", WINDOW_AUTOSIZE);
	moveWindow("MORPH_TOPHAT", src.cols + 200, 200);
	imshow("Original", src);
	imshow("MORPH_TOPHAT", dst);
	waitKey(0);

	system("pause");
	return EXIT_SUCCESS;
}

在这里插入图片描述
5、黑帽

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

其公式为:

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

可以达到如下的效果:
在这里插入图片描述
使用实例如下:

#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main(int argc,char **argv)
{
	char fileName[] = "O:\\CSDN\\MORPH.png";
	Mat src = imread(fileName, IMREAD_COLOR);
	if (src.empty())
	{
		cout << "couldn't open image: " << fileName << ",please check out!\n";
		system("pause");
		return EXIT_FAILURE;
	}

	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
	Mat dst;
	morphologyEx(src, dst, MORPH_BLACKHAT, kernel);

	namedWindow("Original", WINDOW_AUTOSIZE);
	moveWindow("Original", 200, 200);
	namedWindow("MORPH_BLACKHAT", WINDOW_AUTOSIZE);
	moveWindow("MORPH_BLACKHAT", src.cols + 200, 200);
	imshow("Original", src);
	imshow("MORPH_BLACKHAT", dst);
	waitKey(0);

	system("pause");
	return EXIT_SUCCESS;
}

在这里插入图片描述

三、综合实例

#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;

string TYPE_BAR = "Morph Type:\n 0:Open\n 1:Close\n 2:Gradient\n 3:TopHat\n 4:BlackHat";
void dealMorphologyEx(int, void* img);

int main(int argc,char **argv)
{
	char fileName[] = "O:\\CSDN\\MORPH.png";
	Mat src = imread(fileName, IMREAD_COLOR);
	if (src.empty())
	{
		cout << "couldn't open image: " << fileName << ",please check out!\n";
		system("pause");
		return EXIT_FAILURE;
	}

	namedWindow("Original", WINDOW_AUTOSIZE);
	moveWindow("Original", 200, 200);
	namedWindow("MORPH", WINDOW_AUTOSIZE);
	moveWindow("MORPH", src.cols + 200, 200);
	int Type = 0;
	int kernelSize = 0;
	int iteration = 0;
	createTrackbar(TYPE_BAR, "MORPH", &Type, 4, 0, 0);
	createTrackbar("Kernel Size:", "MORPH", &kernelSize, 21, dealMorphologyEx, (void*)&src);
	createTrackbar("Iteration times:", "MORPH", &iteration, 4, 0, 0);

	imshow("MORPH", src);
	imshow("Original", src);
	waitKey(0);

	system("pause");
	return EXIT_SUCCESS;
}

void dealMorphologyEx(int, void* img)
{
	int type = getTrackbarPos(TYPE_BAR, "MORPH") + 2;
	int kernelSize = getTrackbarPos("Kernel Size:", "MORPH");
	int iteration = getTrackbarPos("Iteration times:", "MORPH") + 1;
	Mat src = ((Mat*)img)->clone();
	Mat dst;
	Mat kernel = getStructuringElement(MORPH_RECT, Size(2 * kernelSize + 1, 2 * kernelSize + 1));
	morphologyEx(src, dst, type, kernel, Point(-1, -1), iteration);
	imshow("MORPH", dst);
}

在这里插入图片描述
运行上面程序,便可以自由选择形态学操作了,可以根据需要设定kernelSize和迭代次数,当然也可以自行添加结构元素类型。

注意上图红色框中出现了一个问题,跨行的trackbar名称不能正确显示,本人也一直在寻找解决方案,知道问题的小伙伴可以私信我,谢谢。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值