opencv基于轮廓的模板匹配

〇.算法效果展示

0.1要定位的模板一

image.png

找到的匹配

image.png

在有污损情况下找到的匹配

image.png


0.2要定位的模板2

image.png

找到的匹配

image.png


一. 理论部分

模板匹配的算法包括基于灰度的匹配,基于特征的匹配,基于组件的匹配,基于相关性的匹配以及局部变形匹配。基于灰度的匹配一般应用在没有缩放和旋转,颜色变化不大的场合。基于特征的匹配一般应用在具有缩放和旋转,颜色变化较大的场合。在模板各个组件有相对位移的情况下,使用基于组件的匹配算法。在图像模糊,目标定位不依赖于边缘的情况下一般使用基于相关性的匹配。目标有局部变形的情况使用局部变形匹配算法。基于特征的匹配算法通常使用线特征即轮廓特征,也有使用点特征(ICP算法和RANSAC算法)和面特征(基于区域,基于纹理等)的方法。本文主要介绍基于轮廓特征的模板匹配。

基于轮廓的模板匹配其相似度分布比基于灰度的模板匹配更加有区分度,因而可以进行较高精度的定位。同时,由于只对轮廓进行处理,因而提高了处理速度,因此在处理带有旋转和缩放的情况时具有优势。另外,基于轮廓特征的模板可以不从现场获取,而从CAD数据生成。


二.实验部分:

2.1实验一:边缘模板创建

(Canny算法+非极大值已知+滞后阈值)


在opencv中有函数findcontour可以找到二值图中的边缘,但需要人为控制二值化参数并观察结果,对于灰度区域较多的模板,并不能找到所有边缘,因此使用Canny边缘检测方法,然后进行非极大值抑制和滞后阈值处理得到的边缘。

原图:

image.png

找到的边缘点


image.png

代码

int myfindcontour(Mat templateArr, double maxContrast, double minContrast)
{
	Mat src = templateArr.clone();
	
	//变量定义
	Size Ssize;
	Ssize.width = src.cols;
	Ssize.height = src.rows;
	modelHeight = src.rows;	
	modelWidth = src.cols;
	noOfCordinates = 0;
	cordinates = new Point[modelWidth *modelHeight];
	edgeMagnitude = new double[modelWidth *modelHeight];
	edgeDerivativeX = new double[modelWidth *modelHeight];
	edgeDerivativeY = new double[modelWidth *modelHeight];
	
	//使用Sobel算子计算梯度
	GaussianBlur(src, src, Size(5,5), 1);	
	Mat gx, gy;
	gx = Mat(Ssize.height, Ssize.width, CV_16SC1);
	gy = Mat(Ssize.height, Ssize.width, CV_16SC1);
	Sobel(src, gx, CV_16S, 1, 0, 3);		
	Sobel(src, gy, CV_16S, 0, 1, 3);

	Mat nmsEdges = Mat(Ssize.height, Ssize.width, CV_32F);	
	const short* _sdx;
	const short* _sdy;
	double fdx, fdy;
	double MagG, DirG;
	double MaxGradient = -99999.99;
	double direction;
	int *orients = new int[Ssize.height *Ssize.width];
	int count = 0, i, j; 
	double **magMat;
	CreateDoubleMatrix(magMat, Ssize);
	for (i = 1; i < Ssize.height - 1; i++)
	{
		for (j = 1; j < Ssize.width - 1; j++)
		{
			fdx = gx.at<short>(i, j);
			fdy = gy.at<short>(i, j);
			MagG = sqrt((float)(fdx*fdx) + (float)(fdy*fdy));
			direction = fastAtan2((float)fdy, (float)fdx);	
			magMat[i][j] = MagG;

			if (MagG > MaxGradient)
				MaxGradient = MagG; 
			if ((direction > 0 && direction < 22.5) || (direction > 157.5 && direction < 202.5) || (direction > 337.5 && direction < 360))
				direction = 0;
			else if ((direction > 22.5 && direction < 67.5) || (direction > 202.5 && direction < 247.5))
				direction = 45;
			else if ((direction > 67.5 && direction < 112.5) || (direction > 247.5 && direction < 292.5))
				direction = 90;
			else if ((direction > 112.5 && direction < 157.5) || (direction > 292.5 && direction < 337.5))
				direction = 135;
			else
				direction = 0;
			orients[count] = (int)direction;
			count++;
		}
	}

	//非极大值抑制
	count = 0;
	double leftPixel, rightPixel;
	for (i = 1; i < Ssize.height - 1; i++)
	{
		for (j = 1; j < Ssize.width - 1; j++)
		{
			switch (orients[count])
			{
			case 0:
				leftPixel = magMat[i][j - 1];
				rightPixel = magMat[i][j + 1];
				break;
			case 45:
				leftPixel = magMat[i - 1][j + 1];
				rightPixel = magMat[i + 1][j - 1];
				break;
			case 90:
				leftPixel = magMat[i - 1][j];
				rightPixel = magMat[i + 1][j];
				break;
			case 135:
				leftPixel = magMat[i - 1][j - 1];
				rightPixel = magMat[i + 1][j + 1];
				break;
			}
			
			if ((magMat[i][j] < leftPixel) || (magMat[i][j] < rightPixel))
			{
				nmsEdges.at<int>(i,j) = 0;
			}
			else
			{
				nmsEdges.at<int>(i,j) = (uchar)(magMat[i][j] / MaxGradient * 255);
			}
			count++;
		}
	}

	//滞后阈值
	int RSum = 0, CSum = 0;
	int curX, curY;
	int flag = 1;
	for (i = 1; i < Ssize.height - 1; i++)
	{
		for (j = 1; j < Ssize.width; j++)
		{
			fdx = gx.at<short>(i, j);
			fdy = gy.at<short>(i, j);

			MagG = sqrt(fdx*fdx + fdy*fdy);
			DirG = fastAtan2((float)fdy, (float)fdx);

			flag = 1;
			if(nmsEdges.at<int>(i, j) < maxContrast)
			{
				if(nmsEdges.at<int>(i, j) < minContrast)
				{
					nmsEdges.at<int>(i, j) = 0;
					flag = 0; //移除
				}
				else
				{
					//8邻域中任意一点
					if (nmsEdges.at<int>(i - 1, j - 1) < maxContrast &&
						nmsEdges.at<int>(i - 1, j) < maxContrast &&
						nmsEdges.at<int>(i - 1, j + 1) < maxContrast &&
						nmsEdges.at<int>(i, j - 1) < maxContrast &&
						nmsEdges.at<int>(i, j + 1) < maxContrast &&
						nmsEdges.at<int>(i + 1, j - 1) < maxContrast &&
						nmsEdges.at<int>(i + 1, j) < maxContrast &&
						nmsEdges.at<int>(i + 1, j + 1) < maxContrast)
					{
						nmsEdges.at<int>(i, j) = 0;
						flag = 0;
					}
				}

			}

			//保存找到的边缘点
			curX = i;	curY = j;
			if (flag != 0)
			{
				if (fdx != 0 || fdy != 0)
				{
					RSum = RSum + curX;	CSum = CSum + curY; //累加准备计算重心
					cordinates[noOfCordinates].x = curX;
					cordinates[noOfCordinates].y = curY;
					edgeDerivativeX[noOfCordinates] = fdx;
					edgeDerivativeY[noOfCordinates] = fdy;
					if (MagG != 0)
						edgeMagnitude[noOfCordinates] = 1 / MagG;  
					else
						edgeMagnitude[noOfCordinates] = 0;
					noOfCordinates++;
				}
			}
		}
	}

	//计算重心
	centerOfGravity.x = RSum / noOfCordinates;
	centerOfGravity.y = CSum / noOfCordinates;

	//偏移重心
	for (int m = 0; m < noOfCordinates; m++)
	{
		int temp;
		temp = cordinates[m].x;
		cordinates[m].x = temp - centerOfGravity.x;
		temp = cordinates[m].y;
		cordinates[m].y = temp - centerOfGravity.y;
	}

	delete[] orients;
	modelDefined = true;
	return 1;
}



2.2实验二:使用滑动窗口的边缘模板匹配

接上述实验一,在找到模板的边缘后,使用同样的方法找到待检图片中边缘。然后使模板在待检测图片中滑动,只对模板中找到的边缘点进行计算,进行基于边缘的匹配。
待检测图片:

image.png

检测结果:

image.png


代码:

	for (i = 0; i < Ssize.height; i++)
	{
		for (j = 0; j < Ssize.width; j++)
		{
			partialSum = 0; 
			for (m = 0; m < noOfCordinates; m++)
			{
				curX = i + cordinates[m].x;	
				curY = j + cordinates[m].y;
				iTx = edgeDerivativeX[m];
				iTy = edgeDerivativeY[m];

				if (curX<0||curY<0 || curX>Ssize.height - 1 || curY>Ssize.width - 1)
					continue;

				iSx = Sdx.at<short>(curX, curY);
				iSy = Sdy.at<short>(curX, curY);

				if ((iSx != 0 || iSy != 0) && (iTx != 0 || iTy != 0))
				{
					partialSum = partialSum + ((iSx*iTx) + (iSy*iTy))*(edgeMagnitude[m] * matGradMag[curX][curY]);
				}
				sumOfCoords = m + 1;
				partialScore = partialSum / sumOfCoords;
				if (partialScore < (MIN((minScore - 1) + normGreediness*sumOfCoords, normMinScore*  sumOfCoords)))
					break;
			}
			if (partialScore > resultScore) //更新结果
			{
				resultScore = partialScore;
				resultPoint->x = i;
				resultPoint->y = j;
			}
		}
	}

该算法对待检图片有污损的情况下仍能稳定检测:

image.png

如上图待检图片右上部分轮廓不可见,在进过处理后得到如下结果:

image.png


2.3实验三:基于Hu矩的边缘模板匹配

在模板情况简单,只用外轮廓就可精确定位的前提下,可以使用Hu矩轮廓匹配方法matchShapes,使用匹配到的重心进行定位,对模板轮廓进行缩放和旋转参数搜索,得到目标相对模板旋转缩放参数。
模板:

image.png


待搜索图中目标进行的相应的缩放和旋转:

image.png

搜索结果:

image.png

代码:

	Mat searchImage = imread("pic/contour/Search2.jpg");
	Mat searchImageResult = searchImage.clone();
	Mat graySearchImg;
	cvtColor(searchImage, graySearchImg, COLOR_RGB2GRAY);
	Mat templateImage = imread("pic/contour/Template2.jpg");
	Mat grayTemplateImg;
	cvtColor(templateImage, grayTemplateImg, COLOR_RGB2GRAY);

	//在Template2和Search2中找外轮廓
	Mat bin_search;
	threshold(graySearchImg, bin_search, 128, 255, THRESH_BINARY);
	vector<vector<Point>> contours_search;
	vector<Vec4i> hierarchy_search;
	findContours(bin_search, contours_search, hierarchy_search, RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));

	Mat bin_template;
	threshold(grayTemplateImg, bin_template, 128, 255, THRESH_BINARY);
	vector<vector<Point>> contours_template;
	vector<Vec4i> hierarchy_template;
	findContours(bin_template, contours_template, hierarchy_template, RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));

	drawContours(searchImage, contours_search, -1, Scalar(255, 0, 0));
	drawContours(templateImage, contours_template, -1, Scalar(255, 0, 0));

	//对待检图中的每个轮廓进行判断,可以事先筛选再判断
	for (int i = 0; i < contours_search.size(); i++)
	{
		double score = matchShapes(contours_template[0], contours_search[i], CONTOURS_MATCH_I3,0);
		cout << "score" << score << endl;
		if (score < 0.1)  //认为匹配成功
		{
			//计算重心偏移,缩放和旋转
			double area_template = contourArea(contours_template[0]);
			double area_search = contourArea(contours_search[i]);
			float scale = sqrt(area_search/ area_template);
			cout << "scale:" << scale << endl;

			//由于先验目标最小包围矩形是长方形
			//因此最小包围矩形的中心和重心的向量夹角为旋转
			RotatedRect rect_template = minAreaRect(contours_template[0]);
			RotatedRect rect_search = minAreaRect(contours_search[i]);
			//两个旋转矩阵是否同向
			int sign = (rect_template.size.width - rect_template.size.height) *
				(rect_search.size.width - rect_search.size.height);
			float angle;
			if (sign > 0) //可以直接相减
				angle = rect_search.angle - rect_template.angle;
			else
				angle = (90 + rect_search.angle) - rect_template.angle;
			cout << "angle:" << angle << endl;

			//对contours_template基于重心进行旋转和缩放,
			//然后偏移contours_search[i],绘制到待检图中
			Moments mom_template = moments(contours_template[0]);
			Moments mom_search = moments(contours_search[i]);
			Point gravity_template = Point(mom_template.m10 / mom_template.m00, mom_template.m01 / mom_template.m00);
			Point gravity_search = Point(mom_search.m10 / mom_search.m00, mom_search.m01 / mom_search.m00);

			//对模板轮廓进行平移,旋转和缩放
			for (int j = 0; j < contours_template[0].size(); j++)
			{
				//重心平移到原点
				int x = contours_template[0][j].x;
				int y = contours_template[0][j].y;
				x -= gravity_template.x;
				y -= gravity_template.y;
				//缩放
				x *= scale;
				y *= scale;
				//旋转
				float rad = angle / 180.0*3.1416;
				contours_template[0][j].x = x*cos(rad) - y * sin(rad);
				contours_template[0][j].y = x*sin(rad) + y * cos(rad);
			}

			//画带有偏移的从模板经过缩放和旋转生成的轮廓
			drawContours(searchImageResult, contours_template, -1, Scalar(0, 255, 0),4,8,noArray(),LONG_MAX, gravity_search);
		}
	}

	imshow("templateImage", templateImage);
	imshow("searchImage", searchImage);
	imshow("searchImageResult", searchImageResult);
	waitKey();


总结
该方法由于要找到反映目标外轮廓的曲线,因此对背景干扰较为敏感。但由于不使用滑动窗口的方法,算法复杂度降低,处理时间减少

————————————————

版权声明:本文为CSDN博主「仟人斩」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/iamqianrenzhan/article/details/105040587





本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864