OpenCV4学习笔记(23)——基于Hu矩的轮廓匹配

上次的笔记中,整理记录了有关轮廓发现及轮廓信息提取的一部分内容,同时还记录了Hu矩的计算方式,今天就来记录一下Hu矩的一个应用——轮廓匹配。
OpenCV学习笔记(19)中,我们提到了使用模板匹配来实现对图像中的目标物体进行寻找的方法,但是由于模板匹配的工作条件比较苛刻,而且当模板和目标物体的大小或角度出现偏差时就无法达到比较好的效果,所以模板匹配这种方法只能作为一种入门级别的模式识别方法。

今天我们使用基于Hu矩的轮廓匹配来实现对图像中目标物体的寻找。
之前我们通过moment()求得了轮廓的几何矩、中心距和归一化矩,然后使用轮廓的x、y方向的两个一阶几何矩和零阶几何矩来求取轮廓的质心坐标,而今天要计算的Hu矩,也是通过几何矩来进行计算的。
首先一起了解一下图像的各种矩是怎么得来的,以及这些矩有什么用处。
几何矩:假如一幅图像的坐标点是(x,y),用行列来表示的话就是(col,row),其灰度值是f(x,y),那么该图像的(p+q)阶几何矩mpq可以用下面这段代码来计算,其中height、width分别为图像的高和宽。

int p, q;
int mpq = 0;
for(int row = 0; row < height; row++)
{
	for(int col = 0; col < width; col++)
	{
		mpq += pow(col, p) * pow(row, q) * f(x , y);
	}
}
	

当使用不同的p和q进行计算后,就能得到不同阶的几何矩,而不同阶几何矩的含义如下:
零阶矩(m00):与图像或某个轮廓的面积相关;
一阶矩(m01,m10):与图像或某个轮廓的质心相关;
二阶矩(m02,m11,m20):与图像或某个轮廓的旋转半径相关;
三阶矩(m03,m12,m21,m30):与图像或某个轮廓的斜度或扭曲程度相关。
可见几何矩是与图像息息相关的,当图像的尺寸或者角度发生变化时,其几何矩也会随之变化,也就是说几何矩不具有空间不变性、尺度不变性和旋转不变性,所以几何矩难以用来表示一幅图像的特征。

那么就需要做进一步改进,利用之前求得的质心坐标来计算图像的( p+q )阶中心距mupq,可以用以下代码计算,其中质心坐标为(x0,y0):

int p, q;
//求取一阶几何矩和零阶几何矩
int m10 = 0;
int m01 = 0;
int m00 = 0;
for(int row = 0; row < height; row++)
{
	for(int col = 0; col < width; col++)
	{
		m10 += pow(col, 1) * pow(row, 0) * f(x , y);
		m01 += pow(col, 0) * pow(row, 1) * f(x , y);
		m00 += pow(col, 0) * pow(row, 0) * f(x , y);
	}
}
//计算质心坐标
int x0 = m10 / m00;
int y0 = m01 / m00;
//计算(p+q)阶中心距
int mupq;
for(int row = 0; row < height; row++)
{
	for(int col = 0; col < width; col++)
	{
		mupq += pow( (col-x0) , p) * pow( (row-y0) , q) * f(x , y);
	}
}

可见中心距的结果,会一直围绕质心为中心来计算,也就是说会以图像或轮廓中的每个点到质心的距离来计算,所以当图像或轮廓发生平移时,中心距不会发生改变。即中心距具有空间不变性,针对只进行平移的图像或者轮廓,可以用中心距来作为它的一个特征。

但是仅仅具有空间不变性仍是不够的,很多时候都会涉及到图像尺寸的变换,而这时中心距仍会随着图像尺寸的变化而变化。因此,可以利用零阶中心矩来对各阶中心距进行归一化,从而抵消尺度变化带来的影响,这就是归一化矩。(p+q)阶归一化矩nupq可以通过以下代码来计算:

int p, q;
//求取一阶几何矩和零阶几何矩
int m10 = 0;
int m01 = 0;
int m00 = 0;
for(int row = 0; row < height; row++)
{
	for(int col = 0; col < width; col++)
	{
		m10 += pow(col, 1) * pow(row, 0) * f(x , y);
		m01 += pow(col, 0) * pow(row, 1) * f(x , y);
		m00 += pow(col, 0) * pow(row, 0) * f(x , y);
	}
}
//计算质心坐标
int x0 = m10 / m00;
int y0 = m01 / m00;
//计算零阶中心距
int mu00;
for(int row = 0; row < height; row++)
{
	for(int col = 0; col < width; col++)
	{
		mu00 += pow( (col-x0) , 0) * pow( (row-y0) , 0) * f(x , y);
	}
}
//计算(p+q)阶归一化矩
int nupq;
for(int row = 0; row < height; row++)
{
	for(int col = 0; col < width; col++)
	{
		 mupq += pow( (col-x0) , p) * pow( (row-y0) , q) * f(x , y);
	}
}
nupq = mupq / pow(mu00, (p+q)/2 );

通过归一化抵消了图像尺度变化带来的影响,所以归一化矩不仅具有空间不变性,而且具有尺度不变性。对于只进行平移或缩放的图像或者轮廓,可以使用归一化矩来表示它的特征。

但是当图像或轮廓存在旋转角度时,上述三种矩都会随图像的旋转而发生变化,所以就需要今天整理记录的主角——Hu矩来解决这个问题。
Hu矩其实是一个包含了七个不变矩的集合,这七个不变矩分别由多个二、三阶中心距组合计算而成,至于公式这里就不做记录了,毕竟网上一查就能查得到的(主要是我懒得记。。。真的太长了。。。),根据公式,我们只要计算出所需要的二、三阶中心距,就可以将这七个不变矩分别计算出来,最终得到所需要的Hu矩。Hu矩就可以比较完善的表示一幅图像或者轮廓的特征,因为其具有空间不变性、尺度不变性和旋转不变性这三大特点,当图像或轮廓发生平移、缩放、旋转时Hu矩都不会因此而发生改变。
因此,可以使用Hu矩来进行轮廓匹配。主要代码如下:

	Mat tem, src;
	tem = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\a.png");
	src = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\abc.png");

	Mat tem_gaus, src_gaus;
	GaussianBlur(tem, tem_gaus, Size(), 1, 1, 4);
	GaussianBlur(src, src_gaus, Size(), 1, 1, 4);

	Mat tem_gray, src_gray;
	cvtColor(tem_gaus, tem_gray, COLOR_BGR2GRAY);
	cvtColor(src_gaus, src_gray, COLOR_BGR2GRAY);
	Mat tem_binary, src_binary;
	threshold(tem_gray, tem_binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
	threshold(src_gray, src_binary, 0, 255, THRESH_BINARY | THRESH_OTSU);

	//寻找两幅图像中的轮廓;模板图像只返回最外层轮廓,被匹配图像返回所有轮廓
	vector<vector<Point>> contours_tem, contours_src;
	vector<Vec4i> hierarchy_tem, hierarchy_src;
	findContours(tem_binary, contours_tem, hierarchy_tem, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	findContours(src_binary, contours_src, hierarchy_src, RETR_TREE, CHAIN_APPROX_SIMPLE);

	//计算模板图像的轮廓的几何矩
	Moments tem_moment;
	tem_moment = moments(contours_tem[0]);
	//计算模板图像的轮廓的Hu矩
	Mat tem_hu_moment;					
	HuMoments(tem_moment, tem_hu_moment);			//通过几何矩计算Hu矩,输出的Hu矩为7个double类型的值

	//遍历被匹配图像中的轮廓
	for (int i = 0; i < contours_src.size(); i++)
	{
		//计算被匹配图像的轮廓的几何矩和Hu矩
		Moments src_moment;
		src_moment = moments(contours_src[i]);
		Mat src_hu_moment;
		HuMoments(src_moment, src_hu_moment);

		//计算两个Hu矩之间的差异值
		double different;
		different = matchShapes(tem_hu_moment, src_hu_moment, CONTOURS_MATCH_I1, 0);
		
		cout << "Humoment  different:  " << different << endl;

		if (different < 1)
		{
			drawContours(src, contours_src, i, Scalar(0, 0, 255), 2, 8);
		}
	}
	imshow("src", src);
	imshow("tem", tem);

首先读取一张模板图像和一张匹配图像,通过预处理后将这两幅图像都转变为二值图像,然后分别进行轮廓提取。注意对于模板图像我们只需要它的外层轮廓,而匹配图像则需要找到它的全部轮廓。
然后对模板轮廓进行矩的计算,tem_moment = moments(contours_tem[0]),输出的是它的几何矩(mpq)、中心距(mupq)和归一化矩(nupq)。再进行Hu矩的计算,利用APIHuMoments(tem_moment, tem_hu_moment),其中第一个参数是上面计算矩后输出的moment对象,第二个参数就是输出的Hu矩,这里使用Mat类型定义Hu矩便于后续进行轮廓匹配,Hu矩中的每一行就是一个不变矩。
随后对匹配图像中的轮廓进行遍历,分别求每个轮廓的Hu矩,再通过different = matchShapes(tem_hu_moment, src_hu_moment, CONTOURS_MATCH_I1, 0)来计算每个轮廓的Hu矩和模板轮廓Hu矩的差异值。

matchShapes()这个API是用来对两个轮廓进行比较差异的,其返回值是一个double类型的差异值,其主要参数如下:
前两个参数contour1、contour2:输入的需要匹配的两个轮廓或者两个轮廓的Hu矩,不推荐直接传入轮廓,因为轮廓受其他因素影响变化大;而Hu矩具有空间不变性、尺度不变性和旋转不变性,以Hu矩作为参数传入可以使得计算结果较为稳定,也比直接传入轮廓计算得到的结果更准确;
第三个参数:可选的比较方法,一般使用CONTOURS_MATCH_I;
第四个参数:OpenCV3.X版本后已经被废弃,直接输入0即可。

最后对差异值进行阈值判断,由于当两个轮廓相似时,其Hu矩之间的差异会很小,所以差异值的阈值一般取0~1,如果该轮廓和模板轮廓的Hu矩之间差异值小于阈值,就将该轮廓在匹配图像中绘制出来。
至此,我们就通过两个轮廓Hu矩的比较,实现了在图像中寻找匹配目标的效果。其匹配效果如下:
在这里插入图片描述
从上图可见,当我把原图中的 “A” 扣出来后又将宽和高缩小到了原来的二分之一,并且旋转了180°后再进行基于Hu矩的轮廓匹配,结果仍能够准确得在原图像中找到 “A” 这个轮廓。通过所有轮廓的不同差异值可见,只有一个是处于0~1之间的,这个差异值也就是匹配到的目标轮廓和模板轮廓的Hu矩的差异值。

好的,本次笔记到此结束,谢谢阅读~

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

发布了36 篇原创文章 · 获赞 43 · 访问量 1803

猜你喜欢

转载自blog.csdn.net/weixin_45224869/article/details/104800128
今日推荐