我的OpenCV学习笔记(16):利用工具条调用基本的形态学操作

这次主要介绍两方面的内容,一部分是形态学操作,另一部分是工具条。

先说形态学操作。这里只介绍4种简单的:腐蚀、膨胀、开、闭。最基本的形态学操作是腐蚀和膨胀。其他的操作可以通过腐蚀和膨胀推导出来。

用集合论的观点介绍他们非常麻烦。这里换一种思路:我们先做一定的假设:对于一幅图像:前景(我们感兴趣的部分)是白色的;背景(不感兴趣的部分)是黑色的。然后就可以望文生义一下了:腐蚀操作会使得前景变小,而膨胀会使得前景变大。这主要是当结构元(用来对图像处理的基本模版)作用于图像的边沿时,两种操作的定义引起的。腐蚀操作时只有当整个结构元都在图像边沿内时,锚点(结构元与图像中每个像素对齐的点,通常取作结构元的几何中心)对准的像素才会被保留,判为前景;否则这个点判为背景。膨胀操作则是只要结构元与图像有交集时,锚点对准的像素就会被保留,判为前景。腐蚀可以用来消除一些小的误检测的前景;而膨胀则可以填充一些小洞。

注意到,用3*3的模板腐蚀3次与用7*7的模板腐蚀一次效果是相同的。膨胀的结果可以类推。

有了这两个运算的基本定义,我们就可以定义开运算和闭运算了:开运算就是膨胀以后再腐蚀,而闭运算是腐蚀以后再膨胀。闭运算使得小洞被填上,临近的目标连接到了一起(任何结构元容纳不下的小洞或者缝隙都会被填充)。而开运算则会除去一些小的斑点。

对一幅图像多次使用开运算而闭运算是没有意义的。这与腐蚀和膨胀不同。

在OpenCV中,使用erode和dilate实现腐蚀与膨胀。要实现开运算或者闭运算,当然可以通过腐蚀完以后膨胀之类的方法完成,但是OpenCV通过了一个函数morphologyEx,通过改变函数中的参数来实现不同的形态学运算。下面的代码对这4种运算进行了简单的说明:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
int main( void )
{
	Mat image = imread("D:/picture/images/binary.bmp");
	if(!image.data)
		return -1;	
	imshow("源图像",image);

	//腐蚀操作
	Mat eroded;
	//默认情况下,结构元为3*3
	erode(image,eroded,Mat());
	erode(eroded,eroded,Mat());
	erode(eroded,eroded,Mat());
	imshow("腐蚀结果",eroded);

	//使用自定义的结构元
	Mat element(7,7,CV_8U,Scalar(1));
	erode(image,eroded,element);
	imshow("7*7结构元腐蚀结果",eroded);
	
	//使用默认的结构元重复操作也能得到相同的结果
	erode(image,eroded,Mat(),Point(-1,-1),3);
	imshow("使用默认结构元重复3次",eroded);

	//膨胀操作
	Mat dilated;
	dilate(image,dilated,Mat());
	imshow("膨胀结果",dilated);

	//使用闭运算
	Mat element5(5,5,CV_8U,Scalar(1));
	Mat closed;
	morphologyEx(image,closed,cv::MORPH_CLOSE,element5);
	imshow("5*5结构元闭运算",closed);

	//通过先膨胀再腐蚀的方法得到闭运算
	Mat result;
	dilate(image,result,element5);
	erode(result,result,element5);
	imshow("先膨胀,再腐蚀等于闭运算",result);

	//使用开运算
	Mat opened;
	morphologyEx(image,opened,cv::MORPH_OPEN,element5);
	imshow("5*5结构元开运算",opened);

	//先闭运算再开运算
	morphologyEx(image,result,cv::MORPH_CLOSE,element5);
	morphologyEx(result,result,cv::MORPH_OPEN,element5);
	imshow("先闭运算,在开运算",result);
	
	waitKey(0);
	return 0;
}


下面我们着重看如何使用工具条。

在OpenCV中,使用createTrackbar函数来创建工具条。第一个参数是工具条的名字,第二个参数是工具条要放到哪个窗口上(说来惭愧,我以前一直觉得namedWindow这句话很多余,直接在imshow的时候填上窗口名就行了,干嘛非要namedWindow呢?现在才发现了它的作用),第三个参数是工具条改变的是哪个值(顺带设定了初始位置也是这个值),第四个参数是改变的值的最大值。第五个参数是这个值是作用于哪个函数的。第六个值是用户传给回调函数的数据。不过因为我这里使用的是全局变量,所以没有使用最后一个参数。

下面看看程序:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>

using namespace cv;
using namespace std;

//定义全局变量:
Mat image;
Mat eroded;
Mat dilated;
Mat opened;
Mat closed;

//测试的形态学运算:腐蚀、膨胀、开、闭
//为每个形态学运算创建一个控件
//控件需要的参数:
//每种形态学操作需要单独确定的参数:
//结构元的种类:
int erosion_element = 0;
int dilation_element = 0;
int open_element = 0;
int close_element = 0;
//结构元的大小
int erosion_size = 0;
int dilation_size = 0;
int open_size = 0;
int close_size = 0;

//四种形态学操作共用的参数
//结构元种类的最大值:一个3种:正方形、十字、椭圆
int const max_elements = 2;

//结构元的最大值(四种共用一个)
int const max_kernel_size = 5;


//工具条的回调函数
void Erosion(int,void*);
void Dilation(int,void*);
void Open(int, void*);
void Close(int,void*);

int main()
{
	cout<<"结构元类型:0-矩形,1-十字,2-椭圆"<<endl;
	cout<<"结构元大小:2*n+1"<<endl;
	image = imread("D:/picture/images/binary.bmp");
	if(!image.data)
		return -1;	
	imshow("源图像",image);

	eroded.create(image.rows,image.cols,image.type());
	dilated.create(image.rows,image.cols,image.type());

	namedWindow("腐蚀");
	namedWindow("膨胀");
	namedWindow("开运算");
	namedWindow("闭运算");

	//创建工具条
	//腐蚀操作:
	createTrackbar("核函数类型","腐蚀",&erosion_element,max_elements,Erosion);
	createTrackbar("核函数大小","腐蚀",&erosion_size,max_kernel_size,Erosion);
	//调用腐蚀函数
	Erosion(0,0);

	//膨胀操作
	createTrackbar("结构元类型","膨胀",&dilation_element,max_elements,Dilation);
	createTrackbar("结构元大小","膨胀",&dilation_size,max_kernel_size,Dilation);
	Dilation(0,0);

	//开操作
	createTrackbar("结构元类型","开运算",&open_element,max_elements,Open);
	createTrackbar("核函数大小","开运算",&open_size,max_kernel_size,Open);
	Open(0,0);

	//闭操作
	createTrackbar("结构元类型","闭运算",&close_element,max_elements,Close);
	createTrackbar("核函数大小","闭运算",&close_size,max_kernel_size,Close);
	Close(0,0);
	waitKey(0);
	return 0;
}

//回调函数:
//腐蚀函数:不需要输入参数
void Erosion(int,void*)
{
	int erosion_type;
	if(erosion_element == 0)
	{
		erosion_type = MORPH_RECT;
	}
	else if(erosion_element == 1)
	{
		erosion_type = MORPH_CROSS;
	}
	else if(erosion_element == 2)
	{
		erosion_type = MORPH_ELLIPSE;
	}
	//获取腐蚀元素:参数为:类型、大小、锚点
	Mat element = getStructuringElement(erosion_type,Size(2*erosion_size+1,2*erosion_size+1),Point(-1,-1));
	//进行腐蚀运算:参数为:原图像、结果、腐蚀元素
	erode(image,eroded,element);
	//显示腐蚀结果
	imshow("腐蚀",eroded);
}

//膨胀回调函数
void Dilation(int,void*)
{
	int dilation_type;
	if(dilation_element == 0)
	{
		dilation_type = MORPH_RECT;
	}
	else if(dilation_element == 1)
	{
		dilation_type = MORPH_CROSS;
	}
	else if(dilation_element == 2)
	{
		dilation_type = MORPH_ELLIPSE;
	}
	//获取腐蚀元素:参数为:类型、大小、锚点
	Mat element = getStructuringElement(dilation_type,Size(2*dilation_size+1,2*dilation_size+1),Point(-1,-1));
	//进行腐蚀运算:参数为:原图像、结果、腐蚀元素
	dilate(image,dilated,element);
	//显示腐蚀结果
	imshow("膨胀",dilated);
}
void Open(int, void*)
{
	int open_type;
	if(open_element == 0)
	{
		open_type = MORPH_RECT;
	}
	else if(open_element == 1)
	{
		open_type = MORPH_CROSS;
	}
	else if(open_element == 2)
	{
		open_type = MORPH_ELLIPSE;
	}
	//获取腐蚀元素:参数为:类型、大小、锚点
	Mat element = getStructuringElement(open_type,Size(2*open_size+1,2*open_size+1),Point(-1,-1));
	//进行开运算
	morphologyEx(image,opened,cv::MORPH_OPEN,element);
	imshow("开运算",opened);
}

//闭操作
void Close(int,void*)
{
	int close_type;
	if(close_element == 0)
	{
		close_type = MORPH_RECT;
	}
	else if(close_element == 1)
	{
		close_type = MORPH_CROSS;
	}
	else if(close_element == 2)
	{
		close_type = MORPH_ELLIPSE;
	}
	//获取腐蚀元素:参数为:类型、大小、锚点
	Mat element = getStructuringElement(close_type,Size(2*close_size+1,2*close_size+1),Point(-1,-1));
	//进行开运算
	morphologyEx(image,closed,cv::MORPH_CLOSE,element);
	imshow("闭运算",closed);
}


美中不足的是,为了改变形态学操作的结构元种类和大小,我定义了许多的全局变量。

如果想知道形态学操作的更多内容,还是看冈萨雷斯的那本经典的《数字图像处理》吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值