OpenCV_描述和匹配兴趣点

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_30241709/article/details/78823363

为了进行基于兴趣点的图像分析,我们需要构建能够为一地描述关键点地展现方式,即从兴趣点提取描述子。这些描述子通常是 二值类型、整数型或浮点型组成地一维或二维向量,描述了一个关键点和它的邻域。好的描述子要具有足够地独特性,能唯一地表示图像中地每个关键点。它还要有足够地鲁棒性,在照度变化或视角变动时仍能较好地体现同一批点集。

图像匹配是关键点地常用功能之一。它的作用包括关联同一场景地两个图像、检测图像中事物发生地点等。

*局部模板匹配

最常见地图像块是边长为奇数地正方形,关键点位置就是正方形中心。可以通过比较块内像素地强度值,来衡量两个正方形图像块地相似度。

常见的方案是采用简单的差的平方和(Sum of Squared Differences,SSD)算法。

效果:


代码:

int main()
{
	cv::Mat mouse1 = cv::imread("mouse1.jpg");
	cv::resize(mouse1, mouse1, cv::Size(480, 320));
	cv::Mat mouse2 = cv::imread("mouse2.jpg");
	cv::resize(mouse2, mouse2, cv::Size(480, 320));
	std::vector<cv::KeyPoint> keypoints1;
	std::vector<cv::KeyPoint> keypoints2;
	cv::Ptr<cv::FastFeatureDetector> fastD = cv::FastFeatureDetector::create(80);
	fastD->detect(mouse1, keypoints1);
	fastD->detect(mouse2, keypoints2);

	//定义正方形领域
	const int nsize(11);
	cv::Rect neighborhood(0, 0, nsize, nsize);
	cv::Mat patch1, patch2;
	
	//针对第一幅图像中的每个关键点,在第二幅图像中找出最佳匹配
	cv::Mat result;
	std::vector<cv::DMatch> matches;
	for (int i = 0; i < keypoints1.size(); i++)
	{
		neighborhood.x = keypoints1[i].pt.x - nsize / 2;
		neighborhood.y = keypoints1[i].pt.y - nsize / 2;
		//如果邻域超出图像范围,就继续处理下一个点
		if (neighborhood.x < 0 || neighborhood.y < 0 || neighborhood.x + nsize >= mouse1.cols || neighborhood.y + nsize >= mouse1.rows)
		{
			continue;
		}
		patch1 = mouse1(neighborhood);
		//复位最佳匹配的值
		cv::DMatch bestMatch;
		//针对图像二的全部关键点
		for (int j = 0; j < keypoints2.size(); j++)
		{
			neighborhood.x = keypoints2[j].pt.x - nsize / 2;
			neighborhood.y = keypoints2[j].pt.y - nsize / 2;
			if (neighborhood.x < 0 || neighborhood.y < 0 || neighborhood.x + nsize >= mouse2.cols || neighborhood.y + nsize >= mouse2.rows)
				continue;
			patch2 = mouse2(neighborhood);
			//匹配两个图像块
			cv::matchTemplate(patch1, patch2, result, CV_TM_SQDIFF_NORMED);
			//检查是否最佳匹配
			if (result.at<float>(0.0) < bestMatch.distance)
			{
				bestMatch.distance = result.at<float>(0, 0);
				bestMatch.queryIdx = i;
				bestMatch.trainIdx = j;
			}
		}
		//添加最佳匹配
		matches.push_back(bestMatch);
	}
	//提取25个最佳匹配
	std::nth_element(matches.begin(), matches.begin() + 25, matches.end());
	matches.erase(matches.begin() + 25, matches.end());
	cv::Mat matchImage;
	cv::drawMatches(mouse1, keypoints1, mouse2, keypoints2, matches, matchImage);
	cv::imshow("Matches", matchImage);
	cvWaitKey();
}
这里用了cv::matchTemplate函数来计算图像的相似度,找到了可能的匹配项后用cv::DMatch对象来表示,该对象存储了两个被匹配的关键点的 序号和相似度

std::nth_element:

第n个元素一定是序列第n大的元素,但是其余部分没有特殊的次序,唯一可以确定的是比它小的元素在它前面,比它大的元素在它后面。


这样的结果并不理想,但是通过观察这些点集的匹配效果,也能发现一些成功匹配项。

实现原理:


*模板匹配

执行这个操作的函数是cv::matchTemplate

函数的输入对象是一个小图像模板和一个被搜索的图像。结果是一个浮点数型的cv::Mat函数,表示每个像素位置上的相似度。

假设模板尺寸为M*N,图像尺寸为W*H,那么结果矩阵的尺寸是(W-M+1)*(H-N+1)

效果:

提取兴趣区作为模板:


检测结果:


代码:

int main()
{
	cv::Mat templateImage = cv::imread("mouse2.jpg");
	cv::resize(templateImage, templateImage, cv::Size(480, 320));
	cv::imshow("original image", templateImage);
	cv::Mat roi(templateImage, cv::Rect(90,30,200,160));
	cv::imshow("ROI", roi);
	cv::Mat image = cv::imread("mouse1.jpg");
	cv::resize(image, image, cv::Size(480, 320));
	cv::Mat result;
	cv::matchTemplate(image, roi, result, CV_TM_SQDIFF);

	//找到相似位置
	double minVal, maxVal;
	cv::Point minPt, maxPt;
	cv::minMaxLoc(result, &minVal, &maxVal, &minPt, &maxPt);

	cv::rectangle(image, cv::Rect(minPt.x, minPt.y, roi.cols, roi.rows),cv::Scalar(0,255,0));
	cv::imshow("detected", image);
	cvWaitKey();
}

*描述局部强度值模式

在图像分析中,可以用邻域包含的视觉信息来标识每个特征点,以便区分各个特征点。特征描述子通常是一个N维的向量,在光照变化和拍摄角度微小扭曲时,它描述特征点的方式不会发生改变,通常可以用简单的差值矩阵来比较描述子,例如欧几里德距离

效果:


代码:

int main()
{
	cv::Mat image1 = cv::imread("mouse1.jpg");
	cv::resize(image1, image1, cv::Size(480, 320));
	cv::Mat image2 = cv::imread("mouse2.jpg");
	cv::resize(image2, image2, cv::Size(480, 320));
	std::vector<cv::KeyPoint> keypoints1, keypoints2;
	cv::Ptr<cv::xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create(1500.0);
	detector->detect(image1, keypoints1);
	detector->detect(image2, keypoints2);
	cv::Ptr<cv::DescriptorExtractor> descriptor = detector;
	cv::Mat descriptor1, descriptor2;
	descriptor->compute(image1, keypoints1, descriptor1);
	descriptor->compute(image2, keypoints2, descriptor2);
	cv::BFMatcher matcher(cv::NORM_L2);
	//匹配两幅图像的描述子
	std::vector<cv::DMatch> matches;
	matcher.match(descriptor1, descriptor2, matches);
	cv::Mat result;
	cv::drawMatches(image1, keypoints1, image2, keypoints2, matches, result);
	cv::imshow("result", result);
	cvWaitKey();
}
其中,descriptor1和descriptor2是函数返回的矩阵,矩阵的行数等于关键点向量的元素个数,每行是一个N维的描述子向量,对于SURF描述子,它的默认的尺寸是64.这个向量用于区分特征点周围的强度值图案, 两个特征点越相似,它们的描述子向量就会越接近

使用SURF和SIFT的描述可以进行尺度无关的匹配

*匹配改进方法

1、交叉检查匹配项

即重新进行同一个匹配过程。第二次匹配时,将第二幅图像的每个关键点逐个与第一幅图像的全部关键点进行比较。只有在两个方向都匹配了同一对关键点,才认为是一个有效的匹配项。

函数cv::BFMatcher matcher提供了一个选项来使用这个策略。把有关标志设置为true,函数就会对匹配进行双向的交叉检查。

效果:


可以看出,此次匹配效果较上次好。

只要在构造匹配器时定义为:

cv::BFMatcher matcher(cv::NORM_L2,true)

2、比率检验法

一个关键点会与多个关键点很好地匹配,选中错误匹配地可能性很大,比较可取地做法是排除此类匹配。

要使用这种策略,需要为每个关键点找到两个最佳匹配项。

效果:


实践证明,优化方式1和优化方式2不能同时使用。

代码:

int main()
{
	cv::Mat image1 = cv::imread("mouse1.jpg");
	cv::resize(image1, image1, cv::Size(480, 320));
	cv::Mat image2 = cv::imread("mouse2.jpg");
	cv::resize(image2, image2, cv::Size(480, 320));
	std::vector<cv::KeyPoint> keypoints1, keypoints2;
	cv::Ptr<cv::xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create(1500.0);
	detector->detect(image1, keypoints1);
	detector->detect(image2, keypoints2);
	cv::Ptr<cv::DescriptorExtractor> descriptor = detector;
	cv::Mat descriptor1, descriptor2;
	descriptor->compute(image1, keypoints1, descriptor1);
	descriptor->compute(image2, keypoints2, descriptor2);
	cv::BFMatcher matcher(cv::NORM_L2);
	//匹配两幅图像的描述子
	std::vector<cv::DMatch> matches;
	std::vector<std::vector<cv::DMatch>> matches2;
	matcher.knnMatch(descriptor1, descriptor2, matches2, 2);//找到两个最佳匹配
	//排除与第二个匹配项非常接近的全部最佳匹配项
	double ratio = 0.85;
	std::vector<std::vector<cv::DMatch>>::iterator it;
	for ( it = matches2.begin(); it != matches2.end(); it++)
	{
		if ((*it)[0].distance / (*it)[1].distance < ratio)
			matches.push_back((*it)[0]);
	}
	cv::Mat result;
	cv::drawMatches(image1, keypoints1, image2, keypoints2, matches, result);
	cv::imshow("result", result);
	cvWaitKey();
}

提出疑问:

排除错误匹配不应该是排除两个匹配项差的非常远的才对吗?

可我试着将代码改为:

if ((*it)[0].distance / (*it)[1].distance < 1.05 && (*it)[0].distance / (*it)[1].distance > 0.95)

结果显然不正确:


↑↑↑错误结果↑↑↑

*匹配差值的阈值化

还有一种更加简单的策略,就是把描述子之间的差值太大的匹配项排除,实现此功能的是cv::DescriptorMatcher类的radiusMatch方法

效果:


关键代码:

matcher.radiusMatch(descriptor1, descriptor2, matches2, 1);


*用二值特征描述关键点

提取自图像强度值梯度的丰富的描述子来描述关键点所消耗的资源特别大,为了减少内存使用,降低计算量,人们引入了二值描述子的概念。

效果:

额。。。有点暴力。。

代码:

int main()
{
	cv::Mat image1 = cv::imread("mouse1.jpg");
	cv::resize(image1, image1, cv::Size(480, 320));
	cv::Mat image2 = cv::imread("mouse2.jpg");
	cv::resize(image2, image2, cv::Size(480, 320));
	std::vector<cv::KeyPoint> keypoints1, keypoints2;
	cv::Ptr<cv::FeatureDetector> detector = cv::ORB::create(1500.0);
	detector->detect(image1, keypoints1);
	detector->detect(image2, keypoints2);
	cv::Ptr<cv::DescriptorExtractor> descriptor = detector;
	cv::Mat descriptor1, descriptor2;
	descriptor->compute(image1, keypoints1, descriptor1);
	descriptor->compute(image2, keypoints2, descriptor2);
	cv::BFMatcher matcher(cv::NORM_HAMMING);
	//匹配两幅图像的描述子
	std::vector<cv::DMatch> matches;
	matcher.match(descriptor1, descriptor2, matches);
	cv::Mat result;
	cv::drawMatches(image1, keypoints1, image2, keypoints2, matches, result);
	cv::imshow("result", result);
	cvWaitKey();
}
BRISK是另一个常见的二值特征检测器。

FREAK也是一种二值描述子,全称Fast Retia Keypoint(快速视网膜关键点)



猜你喜欢

转载自blog.csdn.net/qq_30241709/article/details/78823363
今日推荐