ORB-SLAM3 PnpSolver.cc分析与理解
2021SC@SDUSC
前言
这篇了解PnpSolver.cc文件通道的Epnp算法,和相关代码分析一、EPnP算法
PnP求解:已知世界坐标系下的3D点与图像坐标系对应的2D点,求解相机的外参(R t),即从世界坐标系到相机坐标系的变换。
EPnP的思想是:将世界坐标系所有的3D点用四个虚拟的控制点来表示,将图像上对应的特征点转化为相机坐标系下的四个控制点,根据世界坐标系下的四个控制点与相机坐标系下对应的四个控制点(与世界坐标系下四个控制点有相同尺度)即可恢复出(R t)
首先选取4个非共面的虚拟点作为控制点,然后根据空间点和控制点的位置关系以及空间点图像,求解控制点在摄像机坐标系下的坐标,从而得到摄像机位姿。
二、代码分析
// 在大体的pipeline上和Sim3Solver差不多,都是 构造->设置RANSAC参数->外部调用迭代函数,进行计算->得到计算的结果
// pcs表示3D点在camera坐标系下的坐标
// pws表示3D点在世界坐标系下的坐标
// us表示图像坐标系下的2D点坐标
// alphas为真实3D点用4个虚拟控制点表达时的系数
// 构造函数
PnPsolver::PnPsolver(const Frame &F, const vector<MapPoint*> &vpMapPointMatches):
pws(0), us(0), alphas(0), pcs(0), //这里的四个变量都是指针啊,直接这样子写的原因可以参考函数 set_maximum_number_of_correspondences()
maximum_number_of_correspondences(0), number_of_correspondences(0), mnInliersi(0),
mnIterations(0), mnBestInliers(0), N(0)
{
// 根据点数初始化容器的大小
mvpMapPointMatches = vpMapPointMatches; //匹配关系
mvP2D.reserve(F.mvpMapPoints.size()); //2D特征点
mvSigma2.reserve(F.mvpMapPoints.size()); //特征点金字塔层级
mvP3Dw.reserve(F.mvpMapPoints.size()); //世界坐标系下的3D点
mvKeyPointIndices.reserve(F.mvpMapPoints.size()); //记录被使用特征点在原始特征点容器中的索引,因为有些3D点不一定存在,所以索引是不连续的
mvAllIndices.reserve(F.mvpMapPoints.size()); //记录被使用特征点的索引,是连续的
// 生成地图点、对应2D特征点,记录一些索引坐标
int idx=0;
// 遍历给出的每一个地图点
for(size_t i=0, iend=vpMapPointMatches.size(); i<iend; i++)
{
MapPoint* pMP = vpMapPointMatches[i];//依次获取每个地图点
if(pMP)
{
if(!pMP->isBad())
{
const cv::KeyPoint &kp = F.mvKeysUn[i];//得到2维特征点, 将KeyPoint类型变为Point2f
mvP2D.push_back(kp.pt); //存放2维特征点
mvSigma2.push_back(F.mvLevelSigma2[kp.octave]); //记录特征点是在哪一层提取出来的
cv::Mat Pos = pMP->GetWorldPos(); //世界坐标系下的3D点
mvP3Dw.push_back(cv::Point3f(Pos.at<float>(0),Pos.at<float>(1), Pos.at<float>(2)));
mvKeyPointIndices.push_back(i); //记录被使用特征点在原始特征点容器中的索引, mvKeyPointIndices是跳跃的
mvAllIndices.push_back(idx); //记录被使用特征点的索引, mvAllIndices是连续的
idx++;
}
}
} // 遍历给出的每一个地图点
// Set camera calibration parameters
fu = F.fx;
fv = F.fy;
uc = F.cx;
vc = F.cy;
// 设置默认的RANSAC参数,这个和Sim3Solver中的操作是相同的
SetRansacParameters();
}
未完待续