特征检测+SIFT点匹配+PNP位姿确定

SIFT特征检测+FLANN点匹配+PNP位姿确定

软件环境

  1. windows 10

  2. vs2013

  3. opencv3.1.0+opencv3.1.0 contrib

基本原理

1.SIFT 特征点检测+匹配

2. PNP位姿确定

代码实现

1. 参考博客

2. 修改

  • 首先是更换库,3.x以上版本的SIFT算法detect和compute的调用和opencv2.x版本的不太一样,使用时如果包含库或者报错记得及时修改
  • 使用的匹配算法和剔除算法都可以更改,相应的实例有很多
  • 能力有限,findHomography已经能返回一个单应性矩阵,如果加以利用理论上也可以恢复位姿?但是我不太会用,所以把perspectiveTransform获得的四个目标像素角点重新输入了olvePnP解算相机和检测平面间的平移旋转
  • 这个思路和用aruco或者二维码码块检测本质上是一致的,都是匹配检测获得四个角点然后用P3P解决位姿估计问题,差别是如果可以获得任意一个物体的正面图,我们就能实现对这个物体初步的检测和估计,而摆脱了对标记的需求。

3. 源码:

#include "stdafx.h"
#include <ctime>
#include <iostream>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include <opencv2/opencv.hpp> 
#include<opencv2/core/core.hpp>
#include<opencv_modules.hpp>
#include <opencv2/xfeatures2d.hpp>	//包含 SiftFeatureDetector 的头文件
#include "opencv2/features2d/features2d.hpp"	//包含 FlannBasedMatcher 的头文件
#include "opencv2/calib3d/calib3d.hpp"			//包含 findHomography 的头文件

using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;

vector<Vec3d> usePnP(vector<Point2f> scenePoints);


int main()
{
	
	//读图,匹配对象,待搜寻图
	cv::Mat imgObject = cv::imread("D:/Celeste/VS Code/SiftandFlann/101202.jpg", CV_LOAD_IMAGE_GRAYSCALE);
	cv::Mat imgScene = cv::imread("D:/Celeste/VS Code/SiftandFlann/101203.jpg", CV_LOAD_IMAGE_GRAYSCALE);

	if (imgObject.empty() || imgScene.empty())
	{
		std::cout << " --(!) Error reading images " << std::endl;
		return -1;
	}

	double begin = clock();


	Ptr<Feature2D> f2d = xfeatures2d::SIFT::create();
	///-- Step 1: 使用SIFT算子检测特征点
	//cv::SiftFeatureDetector detector;//( minHessian );
	//opencv3之后都使用对象指针
	std::vector<cv::KeyPoint> keypointsObject, keypointsScene;
	f2d->detect(imgObject, keypointsObject);
	f2d->detect(imgScene, keypointsScene);
	std::cout << "object--number of keypoints: " << keypointsObject.size() << std::endl;
	std::cout << "scene--number of keypoints: " << keypointsScene.size() << std::endl;

	///-- Step 2: 使用SIFT算子提取特征(计算特征向量)
	//cv::SiftDescriptorExtractor extractor;
	cv::Mat descriptorsObject, descriptorsScene;
	f2d->compute(imgObject, keypointsObject, descriptorsObject);
	f2d->compute(imgScene, keypointsScene, descriptorsScene);

	///-- Step 3: 使用FLANN法进行匹配
	cv::FlannBasedMatcher matcher;
	std::vector< cv::DMatch > allMatches;
	matcher.match(descriptorsObject, descriptorsScene, allMatches);
	std::cout << "number of matches before filtering: " << allMatches.size() << std::endl;

	//-- 计算关键点间的最大最小距离
	double maxDist = 0;
	double minDist = 100;
	for (int i = 0; i < descriptorsObject.rows; i++)
	{
		double dist = allMatches[i].distance;
		if (dist < minDist)
			minDist = dist;
		if (dist > maxDist)
			maxDist = dist;
	}
	printf("	max dist : %f \n", maxDist);
	printf("	min dist : %f \n", minDist);

	//-- 过滤匹配点,保留好的匹配点(这里采用的标准:distance<3*minDist)
	//也可以用RANSAC剔除
	std::vector< cv::DMatch > goodMatches;
	for (int i = 0; i < descriptorsObject.rows; i++)
	{
		if (allMatches[i].distance < 2 * minDist)
			goodMatches.push_back(allMatches[i]);
	}
	std::cout << "number of matches after filtering: " << goodMatches.size() << std::endl;

	//-- 显示匹配结果
	cv::Mat resultImg;
	drawMatches(imgObject, keypointsObject, imgScene, keypointsScene,
		goodMatches, resultImg, cv::Scalar::all(-1), cv::Scalar::all(-1), std::vector<char>(),
		cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS //不显示未匹配的点
		);
	//-- 输出匹配点的对应关系
	//for (size_t i = 0; i < goodMatches.size(); i++)
		//printf("	good match %d: keypointsObject [%d]  -- keypointsScene [%d]\n", i, goodMatches[i].queryIdx, goodMatches[i].trainIdx);

	///-- Step 4: 使用findHomography找出相应的透视变换
	//object和scene的赋值循环可以放到goodmatch剔点中直接写
	std::vector<cv::Point2f> object;
	std::vector<cv::Point2f> scene;
	for (size_t i = 0; i < goodMatches.size(); i++)
	{
		//-- 从好的匹配中获取关键点: 匹配关系是关键点间具有的一 一对应关系,可以从匹配关系获得关键点的索引
		//-- e.g. 这里的goodMatches[i].queryIdx和goodMatches[i].trainIdx是匹配中一对关键点的索引
		object.push_back(keypointsObject[goodMatches[i].queryIdx].pt);
		scene.push_back(keypointsScene[goodMatches[i].trainIdx].pt);
	}
	cv::Mat H = findHomography(object, scene, CV_RANSAC);
	//返回的H矩阵就是两个平面变换的单应性矩阵

	///-- Step 5: 使用perspectiveTransform映射点群,在场景中获取目标位置
	std::vector<cv::Point2f> objCorners(4);
	objCorners[0] = cvPoint(0, 0);
	objCorners[1] = cvPoint(imgObject.cols, 0);
	objCorners[2] = cvPoint(imgObject.cols, imgObject.rows);
	objCorners[3] = cvPoint(0, imgObject.rows);
	std::vector<cv::Point2f> sceneCorners(4);//目标的像素坐标
	cv::perspectiveTransform(objCorners, sceneCorners, H);
	

	//-- 在被检测到的目标四个角之间划线(左上第一个点,顺时针)
	//sceneCorners和像素点位置之间的关系,差一个参考图列数
	line(resultImg, sceneCorners[0] + cv::Point2f(imgObject.cols, 0), sceneCorners[1] + cv::Point2f(imgObject.cols, 0), cv::Scalar(0, 0, 0), 4);
	line(resultImg, sceneCorners[1] + cv::Point2f(imgObject.cols, 0), sceneCorners[2] + cv::Point2f(imgObject.cols, 0), cv::Scalar(0, 0, 0), 4);
	line(resultImg, sceneCorners[2] + cv::Point2f(imgObject.cols, 0), sceneCorners[3] + cv::Point2f(imgObject.cols, 0), cv::Scalar(0, 255, 0), 4);
	line(resultImg, sceneCorners[3] + cv::Point2f(imgObject.cols, 0), sceneCorners[0] + cv::Point2f(imgObject.cols, 0), cv::Scalar(255, 255, 255), 4);

	//-- 显示检测结果
	cv::imshow("detection result", resultImg);
	vector<int> compression_params;
	compression_params.push_back(IMWRITE_PNG_COMPRESSION);
	compression_params.push_back(3);
	//存图
	imwrite("D:/Celeste/VS Code/SiftandFlann/good.png", resultImg, compression_params);
	double end = clock();
	std::cout << "\nSIFT--elapsed time: " << (end - begin) / CLOCKS_PER_SEC * 1000 << " ms\n";

	//--计算位置姿态
	Vec3d rvec, tvec;	
	vector<Vec3d> res(2);
	//rvec是旋转向量
	//tvec是平移向量
	res=usePnP(sceneCorners);
	rvec = res[0];
	tvec = res[1];	
	cout << "rvec:" <<rvec<< endl;
	cout << "tvec" << tvec << endl;

	//后续计算矩阵转换关系、获得目标对象的世界坐标系

	cv::waitKey(0);
	return 0;

}

//使用pnp求解位置,输入是四个顶点的像素坐标,输出是旋转平移向量
vector<Vec3d> usePnP(vector<Point2f> scenePoints)
{
	//参考物体在实际空间中的位置,以参考物体左上角为原点,顺时针方向,point3d用mm单位真实数据,pnp解得得R\T就是相机相对参考物体的位姿旋转
	//根据实际物体修改参数
	vector<Point3f> objectPoints(4);
	objectPoints[0] = CvPoint3D32f(0, 0, 0.0f);
	objectPoints[1] = CvPoint3D32f(230.0f, 0.0f, 0.0f);
	objectPoints[2] = CvPoint3D32f(230.0f, 289.0f, 0.0f);
	objectPoints[3] = CvPoint3D32f(0.0f, 289.0f, 0.0f);
	//相机内参、畸变矩阵重新填写
	cv::Mat cameraMatrix, distCoeffs;
	cameraMatrix = (Mat_<double>(3, 3) << 598.29493, 0, 304.76898, 0, 597.56086, 233.34673, 0, 0, 1);
	distCoeffs = (Mat_<double>(5, 1) << -0.53572, 1.35993, -0.00244, 0.00620, 0.00000);
	//参数返回值
	Vec3d rvec, tvec;
	//计算rvec\tvec;采用p3p模式
	bool IsSolve=cv::solvePnP(objectPoints, scenePoints, cameraMatrix, distCoeffs, rvec, tvec, false, CV_P3P);
	vector<Vec3d> res(2);
	if (IsSolve)
	{
		cout << "solvePnP successfully!" << endl;
		res[0] = rvec;
	    res[1] = tvec;

	}
	else
	{
		cout << " solvePnP unsuccessfully……check your data again" << endl;
		res[0] = Vec3d(0, 0, 0);
		res[1] = Vec3d(0, 0, 0);
	}
	return res;

}

4. 效果演示

匹配和目标检测

控制台返回

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: PnP算法(Perspective-n-Point算法)是一种在计算机视觉中用于估计相机的位姿(即相机的位置和朝向)的方法。PnP算法的输入是已知的三维和它们在图像上对应的二维,输出是相机的位姿变换。 以下是使用PnP算法计算位姿变换的一般步骤: 1. 收集三维和它们在图像上对应的二维。这些可以通过多种方式获得,例如使用激光扫描仪或结构光扫描仪获取3D云数据,或使用摄像机捕捉物体的图像来获取二维。 2. 确定相机的内部参数,例如相机的焦距、主和畸变系数。这些参数可以通过标定相机获得,标定方法包括张氏标定法和Tsai-Lenz标定法等。 3. 使用PnP算法,将三维和它们在图像上对应的二维作为输入,并使用相机的内部参数,计算相机的位姿变换。PnP算法的原理是通过三个或更多在图像中的位置和它们在空间中的位置来计算相机的位姿。 4. 对于大多数PnP算法,需要知道至少4个三维和它们在图像上对应的二维。这些需要满足一个特定的几何约束条件,例如共面或非共面等条件。根据使用的算法,可能需要进一步的处理或优化来提高位姿估计的精度。 5. 计算得到相机的位姿变换矩阵。这个变换矩阵可以将相机坐标系下的转换到世界坐标系下,或将世界坐标系下的转换到相机坐标系下。 总的来说,使用PnP算法计算位姿变换需要收集3D云和2D图像对应的确定相机的内部参数,并使用PnP算法计算相机的位姿变换矩阵。这个变换矩阵可以用来将相机坐标系下的转换到世界坐标系下,或将世界坐标系下的转换到相机坐标系下。 ### 回答2: PnP算法(Perspective-n-Point)是一种计算相机位姿变换的算法,通常用于计算相机的位置和姿态。下面是使用PnP算法计算位姿变换的步骤: 1. 特征提取:从图像中提取特征,可以使用SIFT、SURF、ORB等算法进行特征检测和描述。 2. 特征匹配:将待定特征与模型特征进行匹配,可以使用最近邻算法或RANSAC算法来筛选匹配对。 3. 求解位姿:选择足够数量的匹配对,根据匹配对的2D-3D关系进行PnP求解。可以使用EPnP、UPnP或APnP算法进行求解。 4. 姿态优化:使用非线性优化算法(例如Levenberg-Marquardt)对求得的初始位姿进行优化,以获得更准确的位姿估计结果。 5. 可选步骤:根据需要,还可以进行相机姿态的迭代改进,通过递归或优化方法获得更精确的位姿解。 PnP算法可以用于计算相机在世界坐标系中的位置和姿态,适用于许多计算机视觉任务,例如增强现实、姿态估计、机器人导航等。然而,PnP算法的有效性和准确性受到输入特征的质量和匹配准确性的影响,因此在应用中需要对算法进行适当的调优和判断。 ### 回答3: PnP(Perspective-n-Point)算法是一种用于计算相机位姿变换的方法,适用于计算从相机坐标系到世界坐标系的变换。 首先,我们需要确定至少3个在世界坐标系中已知的和它们对应的在图像坐标系中的投影。这些可以通过目标物体上的特征或者标定板上的角来获取。 接下来,需要选取一个合适的PnP解算方法,其中比较常用的方法有EPnP和UPnP。EPnP使用最小化重投影误差的方法来求解,并且已经有很多现成的实现。UPnP则通过将PnP问题转化为最小化多项式问题来求解。 然后,根据选定的解算方法,我们将已知的3D和它们在图像中的投影坐标传入算法中。这些信息将作为输入参数来计算相机的旋转矩阵和平移向量。 最后,根据计算得到的旋转矩阵和平移向量,我们可以得到相机的位姿变换关系。利用这个变换关系,我们可以将图像坐标系中的转化为世界坐标系中的,或者实现相机的位姿估计等应用。 需要注意的是,由于PnP算法是一种非线性优化问题,所以可能存在多个解或者无解的情况。另外,为了提高PnP算法的鲁棒性,还可以采用RANSAC等方法来进一步剔除误匹配,提高算法的精度和稳定性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值