(01)ORB-SLAM2源码无死角解析-(15) 单目初始化MonocularInitialization()→Initializer

讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件):
(01)ORB-SLAM2源码无死角解析-(00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/123092196
 
文 末 正 下 方 中 心 提 供 了 本 人 联 系 方 式 , 点 击 本 人 照 片 即 可 显 示 W X → 官 方 认 证 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证} WX
 

一、前言

上一篇博客对 Tracking::MonocularInitialization() 进行了讲解。现在我们再来回顾一下,其中比较重要的函数有如下几个:

	// 由当前帧构造初始器 sigma:1.0 iterations:200
	mpInitializer =  new Initializer(mCurrentFrame,1.0,200);
	
    // Step 3 在mInitialFrame与mCurrentFrame中找匹配的特征点对
    ORBmatcher matcher(0.9,0.7true);
        
    // 对 mInitialFrame,mCurrentFrame 进行特征点匹配
    int nmatches = matcher.SearchForInitialization(mInitialFrame,mCurrentFrame,mvbPrevMatched,mvIniMatches,100);       

    mpInitializer->Initialize(
            mCurrentFrame,      //当前帧
            mvIniMatches,       //当前帧和参考帧的特征点的匹配关系
            Rcw, tcw,           //初始化得到的相机的位姿
            mvIniP3D,           //进行三角化得到的空间点集合
            vbTriangulated)           
      
    // Step 7 将初始化的第一帧作为世界坐标系,因此第一帧变换矩阵为单位矩阵
    mInitialFrame.SetPose(cv::Mat::eye(4,4,CV_32F));
    
    // Step 8 创建初始化地图点MapPoints
	CreateInitialMapMonocular();

对于特征匹配,三角化,内容,在后面的内容会有详细的讲解。目前我们先来看看其上的 Initializer,类对象为mpInitializer,也就是初始器的相关内容。首先来看看他的构造函数,位于sec/Initializer.cc文件中,代码内容如下:

/**
 * @brief 根据参考帧构造初始化器
 * 
 * @param[in] ReferenceFrame        参考帧
 * @param[in] sigma                 测量误差
 * @param[in] iterations            RANSAC迭代次数
 */
Initializer::Initializer(const Frame &ReferenceFrame, float sigma, int iterations)
{
	//从参考帧中获取相机的内参数矩阵
    mK = ReferenceFrame.mK.clone();
	
	// 从参考帧中获取去畸变后的特征点
    mvKeys1 = ReferenceFrame.mvKeysUn;
	
	//获取估计误差
    mSigma = sigma;
    mSigma2 = sigma*sigma;

	//最大迭代次数
    mMaxIterations = iterations;
}

可以看到其中的操作可谓是简单至极,仅仅是简单赋值而已。其还是还是在 mpInitializer->Initialize() 函数中,也就是 Initializer::Initializer(const Frame &ReferenceFrame, float sigma, int iterations)。那么我们就来看看吧。

 

二、代码流程

关于 Initializer::Initializer 的主要代码流程如下:

	1、核心关键变量为mvMatches12,每个元素存在两个值,第一个存储的是帧一(参考帧)特征索引,
	第二个存储帧二(当前帧)特征索引,该对索引值是具备匹配关系的。

	2、从mvMatches12中随机选取(不重复)8对特征点

	3、开启两个线程,使用八点法分别计算 Homography 与 Fundamental 矩阵。

	4、对比两个矩阵的值,决定使用 Homography 还是 Fundamental 恢复位姿。

 

三、源码注释

在代码中可以看到 RH = SH/(SH+SF); if(RH>0.40),可以得知其更倾向于使用 H 矩阵恢复位姿。

/**
 * @brief 计算基础矩阵和单应性矩阵,选取最佳的来恢复出最开始两帧之间的相对姿态,并进行三角化得到初始地图点
 * Step 1 重新记录特征点对的匹配关系
 * Step 2 在所有匹配特征点对中随机选择8对匹配特征点为一组,用于估计H矩阵和F矩阵
 * Step 3 计算fundamental 矩阵 和homography 矩阵,为了加速分别开了线程计算 
 * Step 4 计算得分比例来判断选取哪个模型来求位姿R,t
 * 
 * @param[in] CurrentFrame          当前帧,也就是SLAM意义上的第二帧
 * @param[in] vMatches12            当前帧(2)和参考帧(1)图像中特征点的匹配关系
 *                                  vMatches12[i]解释:i表示帧1中关键点的索引值,vMatches12[i]的值为帧2的关键点索引值
 *                                  没有匹配关系的话,vMatches12[i]值为 -1
 * @param[in & out] R21                   相机从参考帧到当前帧的旋转
 * @param[in & out] t21                   相机从参考帧到当前帧的平移
 * @param[in & out] vP3D                  三角化测量之后的三维地图点
 * @param[in & out] vbTriangulated        标记三角化点是否有效,有效为true
 * @return true                     该帧可以成功初始化,返回true
 * @return false                    该帧不满足初始化条件,返回false
 */
bool Initializer::Initialize(const Frame &CurrentFrame, const vector<int> &vMatches12, cv::Mat &R21, cv::Mat &t21,
                             vector<cv::Point3f> &vP3D, vector<bool> &vbTriangulated)
{
    // Fill structures with current keypoints and matches with reference frame
    // Reference Frame: 1, Current Frame: 2

    //获取当前帧的去畸变之后的特征点
    mvKeys2 = CurrentFrame.mvKeysUn;

    // mvMatches12记录匹配上的特征点对,记录的是帧2在帧1的匹配索引
    mvMatches12.clear();
	// 预分配空间,大小和关键点数目一致mvKeys2.size()
    mvMatches12.reserve(mvKeys2.size());

    // 记录参考帧1中的每个特征点是否有匹配的特征点
    // 这个成员变量后面没有用到,后面只关心匹配上的特征点 	
    mvbMatched1.resize(mvKeys1.size());

    // Step 1 重新记录特征点对的匹配关系存储在mvMatches12,是否有匹配存储在mvbMatched1
    // 将vMatches12(有冗余) 转化为 mvMatches12(只记录了匹配关系)
    for(size_t i=0, iend=vMatches12.size();i<iend; i++)
    {
		//vMatches12[i]解释:i表示帧1中关键点的索引值,vMatches12[i]的值为帧2的关键点索引值
        //没有匹配关系的话,vMatches12[i]值为 -1
        if(vMatches12[i]>=0)
        {
			//mvMatches12 中只记录有匹配关系的特征点对的索引值
            //i表示帧1中关键点的索引值,vMatches12[i]的值为帧2的关键点索引值
            mvMatches12.push_back(make_pair(i,vMatches12[i]));
			//标记参考帧1中的这个特征点有匹配关系
            mvbMatched1[i]=true;
        }
        else
			//标记参考帧1中的这个特征点没有匹配关系
            mvbMatched1[i]=false;
    }

    // 有匹配的特征点的对数
    const int N = mvMatches12.size();
    // Indices for minimum set selection
    // 新建一个容器vAllIndices存储特征点索引,并预分配空间
    vector<size_t> vAllIndices;
    vAllIndices.reserve(N);

	//在RANSAC的某次迭代中,还可以被抽取来作为数据样本的特征点对的索引,所以这里起的名字叫做可用的索引
    vector<size_t> vAvailableIndices;
	//初始化所有特征点对的索引,索引值0到N-1
    for(int i=0; i<N; i++)
    {
        vAllIndices.push_back(i);
    }

    // Generate sets of 8 points for each RANSAC iteration
    // Step 2 在所有匹配特征点对中随机选择8对匹配特征点为一组,用于估计H矩阵和F矩阵
    // 共选择 mMaxIterations (默认200) 组
    //mvSets用于保存每次迭代时所使用的向量
    mvSets = vector< vector<size_t> >(mMaxIterations,		//最大的RANSAC迭代次数
									  vector<size_t>(8,0));	//这个则是第二维元素的初始值,也就是第一维。这里其实也是一个第一维的构造函数,第一维vector有8项,每项的初始值为0.

	//用于进行随机数据样本采样,设置随机数种子
    DUtils::Random::SeedRandOnce(0);

	//开始每一次的迭代 
    for(int it=0; it<mMaxIterations; it++)
    {
		//迭代开始的时候,所有的点都是可用的
        vAvailableIndices = vAllIndices;

        // Select a minimum set
		//选择最小的数据样本集,使用八点法求,所以这里就循环了八次
        for(size_t j=0; j<8; j++)
        {
            // 随机产生一对点的id,范围从0到N-1
            int randi = DUtils::Random::RandomInt(0,vAvailableIndices.size()-1);
            // idx表示哪一个索引对应的特征点对被选中
            int idx = vAvailableIndices[randi];
			
			//将本次迭代这个选中的第j个特征点对的索引添加到mvSets中
            mvSets[it][j] = idx;

            // 由于这对点在本次迭代中已经被使用了,所以我们为了避免再次抽到这个点,就在"点的可选列表"中,
            // 将这个点原来所在的位置用vector最后一个元素的信息覆盖,并且删除尾部的元素
            // 这样就相当于将这个点的信息从"点的可用列表"中直接删除了
            vAvailableIndices[randi] = vAvailableIndices.back();
			vAvailableIndices.pop_back();
        }//依次提取出8个特征点对
    }//迭代mMaxIterations次,选取各自迭代时需要用到的最小数据集

    

    // Launch threads to compute in parallel a fundamental matrix and a homography
    // Step 3 计算fundamental 矩阵 和homography 矩阵,为了加速分别开了线程计算 
 
    //这两个变量用于标记在H和F的计算中哪些特征点对被认为是Inlier
    vector<bool> vbMatchesInliersH, vbMatchesInliersF;
	//计算出来的单应矩阵和基础矩阵的RANSAC评分,这里其实是采用重投影误差来计算的
    float SH, SF; //score for H and F
    //这两个是经过RANSAC算法后计算出来的单应矩阵和基础矩阵
    cv::Mat H, F; 

    // 构造线程来计算H矩阵及其得分
    // thread方法比较特殊,在传递引用的时候,外层需要用ref来进行引用传递,否则就是浅拷贝
    thread threadH(&Initializer::FindHomography,	//该线程的主函数
				   this,							//由于主函数为类的成员函数,所以第一个参数就应该是当前对象的this指针
				   ref(vbMatchesInliersH), 			//输出,特征点对的Inlier标记
				   ref(SH), 						//输出,计算的单应矩阵的RANSAC评分
				   ref(H));							//输出,计算的单应矩阵结果
    // 计算fundamental matrix并打分,参数定义和H是一样的,这里不再赘述
    thread threadF(&Initializer::FindFundamental,this,ref(vbMatchesInliersF), ref(SF), ref(F));
    // Wait until both threads have finished
	//等待两个计算线程结束
    threadH.join();
    threadF.join();

    // Compute ratio of scores
    // Step 4 计算得分比例来判断选取哪个模型来求位姿R,t
	//通过这个规则来判断谁的评分占比更多一些,注意不是简单的比较绝对评分大小,而是看评分的占比
    float RH = SH/(SH+SF);			//RH=Ratio of Homography

    // Try to reconstruct from homography or fundamental depending on the ratio (0.40-0.45)
    // 注意这里更倾向于用H矩阵恢复位姿。如果单应矩阵的评分占比达到了0.4以上,则从单应矩阵恢复运动,否则从基础矩阵恢复运动
    if(RH>0.40)
		//更偏向于平面,此时从单应矩阵恢复,函数ReconstructH返回bool型结果
        return ReconstructH(vbMatchesInliersH,	//输入,匹配成功的特征点对Inliers标记
							H,					//输入,前面RANSAC计算后的单应矩阵
							mK,					//输入,相机的内参数矩阵
							R21,t21,			//输出,计算出来的相机从参考帧1到当前帧2所发生的旋转和位移变换
							vP3D,				//特征点对经过三角测量之后的空间坐标,也就是地图点
							vbTriangulated,		//特征点对是否成功三角化的标记
							1.0,				//这个对应的形参为minParallax,即认为某对特征点的三角化测量中,认为其测量有效时
												//需要满足的最小视差角(如果视差角过小则会引起非常大的观测误差),单位是角度
							50);				//为了进行运动恢复,所需要的最少的三角化测量成功的点个数
    else //if(pF_HF>0.6)
        // 更偏向于非平面,从基础矩阵恢复
        return ReconstructF(vbMatchesInliersF,F,mK,R21,t21,vP3D,vbTriangulated,1.0,50);

	//一般地程序不应该执行到这里,如果执行到这里说明程序跑飞了
    return false;
}

 

四、结语

该片博客,我们了解 Initializer::Initialize 流程,得知其主要采用八点法来求解 Homography 以及 Fundamental 矩阵,然后选择其中的一个来恢复位姿。所以下面我们讲解的内容就是 Initializer::FindHomography 与 Initializer::FindFundamental两个函数了
 
 
本文内容来自计算机视觉life ORB-SLAM2 课程课件

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
本文主要介绍ORB-SLAM2中的一些关键实现细节,包括词袋建立、关键帧选择策略、词袋检索和位姿估计。此外还详细介绍了视觉里程计、闭环检测、地图维护等模块的实现细节。 首先,ORB-SLAM2通过建立词袋的方式实现了特征点的高效匹配。ORB-SLAM2采用了二叉树的结构生成了一个层次化的词袋,该词袋可以快速地检索到最相似的词,并将该词作为当前帧所属的类别。在后续的帧匹配过程中,ORB-SLAM2只需要搜索与当前帧类别相同的关键帧中的点即可,大大加快了匹配的效率。 其次,ORB-SLAM2采用了一种称为“闭线性三角测量”的方式估计位姿。该方法将两个视角下的匹配点转化为视差向量,并通过求解一组线性方程组来估计相邻帧之间的相对位姿。同时,该方法还能有效地处理重复区域和遮挡等问题,具有较高的鲁棒性。 此外,在关键帧选择方面,ORB-SLAM2采用了一种基于路标点的策略,即当当前帧与地图中的路标点距离较远时,就将当前帧作为新的关键帧。这种策略可以确保全局地图的均匀性和关键帧的稠密性。 最后,ORB-SLAM2采用了基于基础矩阵的闭环检测方法,该方法可以在时间和空间复杂度上达到较好的平衡。同时,ORB-SLAM2还采用了一种优化地图点云的方式,通过通过图优化的方式优化地图中的点云位置,确保了地图的准确性和一致性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值