ORB-SLAM2 单目初始化中随机选择点集、归一化原理、求单应阵、基础阵

这篇博客详细介绍了ORB-SLAM2系统中单目初始化的过程,包括特征点匹配、归一化处理、以及如何通过RANSAC算法计算单应矩阵H和基础矩阵F。在匹配不足时,系统会删除初始化器并重新尝试。通过随机选取点集,利用8点法计算H或F,并结合重投影误差评估内点,最终选择最佳的单应矩阵进行地图点初始化。

ORB-SLAM2


ORB-SLAM2 单目初始化中随机选择点集、归一化原理、求单应阵


一、代码

在特征点提取与匹配完成后,回到Tracking.cc中

		if(nmatches<100)//判断,如果特征匹配的数目小于100的话
        {
            delete mpInitializer;//那么就删除这个初始化器。重新开始初始化
            mpInitializer = static_cast<Initializer*>(NULL);
            return;
        }

		v::Mat Rcw; // Current Camera Rotation
        cv::Mat tcw; // Current Camera Translation
        vector<bool> vbTriangulated; // Triangulated Correspondences (mvIniMatches特征点是否成功三角化?

		//H或F进行初始化,得到相对运动,初始化地图点
		if(mpInitializer->Initialize(mCurrentFrame, mvIniMatches, Rcw, tcw, mvIniP3D, vbTriangulated))
        {
            for(size_t i=0, iend=mvIniMatches.size(); i<iend;i++)
            {
                if(mvIniMatches[i]>=0 && !vbTriangulated[i])
                {
                    mvIniMatches[i]=-1;
                    nmatches--;
                }
            }

进入Initialize()函数

bool Initializer::Initialize(const Frame &CurrentFrame, const vector<int> &vMatches12, cv::Mat &R21, cv::Mat &t21, vector<cv::Point3f> &vP3D, vector<bool> &vbTriangulated)
//传入的参数是:当前帧,当前帧与参考帧的匹配、R,t,三角化成功的点
{
    // Fill structures with current keypoints and matches with reference frame
    // Reference Frame: 1, Current Frame: 2
    mvKeys2 = CurrentFrame.mvKeysUn;//将当前帧去畸变后的图像特征点传给mvkeys2

    mvMatches12.clear();//清空mvMatches12,记录匹配特征点对的
    mvMatches12.reserve(mvKeys2.size());//将它的大小转化为mvkeys的大小
    mvbMatched1.resize(mvKeys1.size());//记录参考帧1中是否有匹配的特诊点
    for(size_t i=0, iend=vMatches12.size();i<iend; i++)//遍历匹配完的特征点数量,里面存的是当前帧与第一帧匹配点的索引值
    {
        if(vMatches12[i]>=0)//如果里面有值的化
        {
            mvMatches12.push_back(make_pair(i,vMatches12[i]));//把关键帧1中的索引与帧2中的索引pair起来
            mvbMatched1[i]=true;//;标记帧1中的特征点有匹配关系
        }
        else
            mvbMatched1[i]=false;//否则匹配不上
    }

	const int N = mvMatches12.size();//定义匹配点的个数,也就是F1中与F2中的匹配点的匹配关系的索引

    // Indices for minimum set selection
    vector<size_t> vAllIndices;//存储特征点的索引,并分配大小,8点法
    vAllIndices.reserve(N);
    vector<size_t> vAvailableIndices;//RANSAC中某次迭代,样本索引

    for(int i=0; i<N; i++)
    {
        vAllIndices.push_back(i);//索引对应第一帧与第二帧的匹配索引
    }

    // Generate sets of 8 points for each RANSAC iteration
    mvSets = vector< vector<size_t> >(mMaxIterations,
    vector<size_t>(8,0));//RANSAC最大迭代次数,

    DUtils::Random::SeedRandOnce(0);//随机选取样本。随机种子数

    for(int it=0; it<mMaxIterations; it++)//循环迭代次数<200次
    {
        vAvailableIndices = vAllIndices;//索引对应第一帧与第二帧的匹配索引,都是可用的

        // Select a minimum set
        for(size_t j=0; j<8; j++)//选取8个点
        {
            int randi = DUtils::Random::RandomInt(0,vAvailableIndices.size()-1);//在0到最大匹配点的个数之间随机选取数目
            int idx = vAvailableIndices[randi];//将选择的点的索引传给idx

            mvSets[it][j] = idx;//本次迭代对应的第J个索引存到mvSet中,it表示第几次迭代,j表示选择点的索引

            vAvailableIndices[randi] = vAvailableIndices.back();//用最后一个元素将选中元素进行覆盖
            vAvailableIndices.pop_back();//删除最后一个元素
        }
    }//迭代200次,每次选择8个点

	vector<bool> vbMatchesInliersH, vbMatchesInliersF;//记录哪些点是内点
    float SH, SF;//记录计算后的评分
    cv::Mat H, F;

	//开两个线程来加速计算
    thread threadH(&Initializer::FindHomography,this,ref(vbMatchesInliersH), ref(SH), ref(H));
    thread threadF(&Initializer::FindFundamental,this,ref(vbMatchesInliersF), ref(SF), ref(F));

	//等待线程结束
	threadH.join();
    threadF.join();
	float RH = SH/(SH+SF);//这边是计算评分,
	
	if(RH>0.40)//如果评分大于0.4用H阵来恢复
        return ReconstructH(vbMatchesInliersH,H,mK,R21,t21,vP3D,vbTriangulated,1.0,50);
    else //if(pF_HF>0.6)否则F阵
        return ReconstructF(vbMatchesInliersF,F,mK,R21,t21,vP3D,vbTriangulated,1.0,50);

    return false;
}

如何计算H阵F阵类似

//1.将当前帧与参考帧的特征点进行归一化
//2.选择8个归一化后的点进行迭代
//3.八点法计算
//4.利用重投影误差来计算RANSAC的评分
//5.保存最优结果  
void Initializer::FindHomography(vector<bool> &vbMatchesInliers, float &score, cv::Mat &H21)
{//传参:标记是否为外点?单应阵的得分、单应阵结果
    // Number of putative matches
    const int N = mvMatches12.size();//匹配特诊点的总数

    // Normalize coordinates
    vector<cv::Point2f> vPn1, vPn2;//开始进行归一化
    cv::Mat T1, T2;
    Normalize(mvKeys1,vPn1, T1);//进行归一化
    Normalize(mvKeys2,vPn2, T2);
    cv::Mat T2inv = T2.inv();//反归一化

    // Best Results variables
    score = 0.0;//计算最佳评分
    vbMatchesInliers = vector<bool>(N,false);//最佳评分是特诊点对应的内点,默认是false

    // Iteration variables
    vector<cv::Point2f> vPn1i(8);//某次迭代中的参考帧对应的坐标
    vector<cv::Point2f> vPn2i(8);//某次迭代中当前帧对应的坐标
    cv::Mat H21i, H12i;//H阵与其逆
    vector<bool> vbCurrentInliers(N,false);//当前帧内点 
    float currentScore;

    // Perform all RANSAC iterations and save the solution with highest score
    for(int it=0; it<mMaxIterations; it++)//开始迭代200个
    {
        // Select a minimum set
        for(size_t j=0; j<8; j++)
        {
            int idx = mvSets[it][j];//刚刚选取的八个点

            vPn1i[j] = vPn1[mvMatches12[idx].first];//帧1特征点索引,归一化的点
            vPn2i[j] = vPn2[mvMatches12[idx].second];//帧2的索引
        }

        cv::Mat Hn = ComputeH21(vPn1i,vPn2i);//8点法进行计算,这边是归一化后的点进行单应矩阵的计算
        H21i = T2inv*Hn*T1;//keypoints去除归一化
        H12i = H21i.inv();//计算逆

        currentScore = CheckHomography(H21i, H12i, vbCurrentInliers, mSigma);//利用重投影误差来对这次进行评分,进入

        if(currentScore>score)
        {
            H21 = H21i.clone();
            vbMatchesInliers = vbCurrentInliers;
            score = currentScore;
        }
    }
}

归一化函数

void Initializer::Normalize(const vector<cv::KeyPoint> &vKeys, vector<cv::Point2f> &vNormalizedPoints, cv::Mat &T)
//传参关键点、归一化点的坐标、变换阵
{
    float meanX = 0;
    float meanY = 0;
    const int N = vKeys.size();

    vNormalizedPoints.resize(N);//所有关键点数量大小

    for(int i=0; i<N; i++)
    {
        meanX += vKeys[i].pt.x;//关键点x坐标相加
        
        meanY += vKeys[i].pt.y;//y坐标相加
    }

    meanX = meanX/N;//取均值
    meanY = meanY/N;

    float meanDevX = 0;
    float meanDevY = 0;

    for(int i=0; i<N; i++)
    {
        vNormalizedPoints[i].x = vKeys[i].pt.x - meanX;//所有的x点距离均值的大小
        vNormalizedPoints[i].y = vKeys[i].pt.y - meanY;

        meanDevX += fabs(vNormalizedPoints[i].x);//将距离均值大小都加起来
        meanDevY += fabs(vNormalizedPoints[i].y);
    }

    meanDevX = meanDevX/N;//用这个距离总和再除以特征点数
    meanDevY = meanDevY/N;

    float sX = 1.0/meanDevX;//为变换做准备
    float sY = 1.0/meanDevY;

    for(int i=0; i<N; i++)
    {
        vNormalizedPoints[i].x = vNormalizedPoints[i].x * sX;
        vNormalizedPoints[i].y = vNormalizedPoints[i].y * sY;
    }

    T = cv::Mat::eye(3,3,CV_32F);//转换为矩阵形式
    T.at<float>(0,0) = sX;
    T.at<float>(1,1) = sY;
    T.at<float>(0,2) = -meanX*sX;
    T.at<float>(1,2) = -meanY*sY;
}

在这里插入图片描述

进入CheckHomography()函数选择最佳的H阵

//对给定的H阵来进行评分,需要使用卡方检验
float Initializer::CheckHomography(const cv::Mat &H21, const cv::Mat &H12, vector<bool> &vbMatchesInliers, float sigma)//传参:参考帧到当前帧的H、当前帧到参考帧的H、匹配好的特侦点对的Inliner的标记、估计误差方差,默认为1
{   


/*
输入  H21  H12  匹配点集mvkeys1

for p1(i) p2(i)  mvkeys1     p1  p2是进行逆归一化后的keypoints
e1=||p2(i)-H21*p2(i)||2           重投影误差
e2=||p1(i)-H12*p1(i)||2

w1=1/(sigma*sigma)
w2=1/(sigma*sigma)

if e1<th
score+=th-e1*w1
if e2<th
score+=th-e2*w2

*/ 
    const int N = mvMatches12.size();//特征点匹配的数目

    const float h11 = H21.at<float>(0,0);//获取参考帧到当前帧的H阵的各个元素
    const float h12 = H21.at<float>(0,1);
    const float h13 = H21.at<float>(0,2);
    const float h21 = H21.at<float>(1,0);
    const float h22 = H21.at<float>(1,1);
    const float h23 = H21.at<float>(1,2);
    const float h31 = H21.at<float>(2,0);
    const float h32 = H21.at<float>(2,1);
    const float h33 = H21.at<float>(2,2);

    const float h11inv = H12.at<float>(0,0);//获取当前帧到参考帧的H阵的各个元素
    const float h12inv = H12.at<float>(0,1);
    const float h13inv = H12.at<float>(0,2);
    const float h21inv = H12.at<float>(1,0);
    const float h22inv = H12.at<float>(1,1);
    const float h23inv = H12.at<float>(1,2);
    const float h31inv = H12.at<float>(2,0);
    const float h32inv = H12.at<float>(2,1);
    const float h33inv = H12.at<float>(2,2);
}
 

    vbMatchesInliers.resize(N);//给特诊点的outlier标记预分配的

    float score = 0;

    const float th = 5.991;//卡方检验的阈值(假设有一个像素的偏差)

    const float invSigmaSquare = 1.0/(sigma*sigma);//平方差的倒数

    for(int i=0; i<N; i++)
    {
        bool bIn = true;//默认都是inlier

        const cv::KeyPoint &kp1 = mvKeys1[mvMatches12[i].first];//提取参考帧与当前帧的匹配点对
        const cv::KeyPoint &kp2 = mvKeys2[mvMatches12[i].second];

        const float u1 = kp1.pt.x;//keypoints的坐标
        const float v1 = kp1.pt.y;
        const float u2 = kp2.pt.x;
        const float v2 = kp2.pt.y;

        // Reprojection error in first image
        // x2in1 = H12*x2
      //计算F2到F1的重投影误差
        const float w2in1inv = 1.0/(h31inv*u2+h32inv*v2+h33inv);//H阵的推导计算公式
        const float u2in1 = (h11inv*u2+h12inv*v2+h13inv)*w2in1inv;
        const float v2in1 = (h21inv*u2+h22inv*v2+h23inv)*w2in1inv;

        const float squareDist1 = (u1-u2in1)*(u1-u2in1)+(v1-v2in1)*(v1-v2in1);//重投影

        const float chiSquare1 = squareDist1*invSigmaSquare;//卡方?

        if(chiSquare1>th)//如果大于阈值就算作是离群点
            bIn = false;
        else
            score += th - chiSquare1;//否则内点累计得分,阈值-卡方值,误差越大得分越低

        // Reprojection error in second image
        // x1in2 = H21*x1
		//反向计算
        const float w1in2inv = 1.0/(h31*u1+h32*v1+h33);
        const float u1in2 = (h11*u1+h12*v1+h13)*w1in2inv;
        const float v1in2 = (h21*u1+h22*v1+h23)*w1in2inv;

        const float squareDist2 = (u2-u1in2)*(u2-u1in2)+(v2-v1in2)*(v2-v1in2);

        const float chiSquare2 = squareDist2*invSigmaSquare;

        if(chiSquare2>th)
            bIn = false;
        else
            score += th - chiSquare2;

        if(bIn)
            vbMatchesInliers[i]=true;
        else
            vbMatchesInliers[i]=false;
    }

    return score;
}
### ORB-SLAM单应初始化的方法 在ORB-SLAM中,为了实现有效的单目视觉里程计(VO)和同步定位与建图(SLAM),单应初始化是一个重要环节。该过程主要发生在两个连续图像帧之间,用于估计相机运动并构建初始的地图。 #### 特征点匹配与筛选 参与初始化的两帧各自需拥有超过100个特征点,并且这两帧间至少有100对成功匹配的特征点[^1]。这些匹配点对于后续的基础\( F \)或单应\( H \)计算至关重要。如果场景接近平面,则更倾向于使用单应来描述这种变换;反之则采用基础。 #### 基础 vs 单应的选择依据 记录当前帧与参考帧间的特征匹配关系,在其中随机选取8组匹配点对尝试建立模型——即分别基于这8对点估算出可能的基础\( F \)以及单应\( H \)。通过评估每种假设下其他未被选中的匹配点相对于所提模型的一致程度,可以得出两者各自的得分情况。最终决定是应用基础还是单应取决于哪一个选项能够更好地解释数据内的大多数观测值。 #### 单应的具体取流程 一旦选择了单应路径,接下来便是具体实施: - **坐标归一化**:首先对所有待处理的关键点位置执行标准化操作,使得它们围绕原点分布且平均距离为\(\sqrt{2}\)。 - **迭代优化**:进入循环阶段,每次都挑选不同的8个样本组合重新计算新的候选单应实例。随后利用剩余测试合验证新产生的转换效果好坏,累积分数直至遍历完毕全部可能性为止。最后保留那个累计得票最高者作为全局最优解。 ```cpp // 归一化关键点坐标的伪代码示例 void NormalizePoints(std::vector<cv::Point2f>& points, cv::Mat& T){ // 计算质心 double cx = std::accumulate(points.begin(), points.end(), 0.0, [](double sum, const cv::Point2f &p){return sum+p.x;}) / points.size(); double cy = std::accumulate(points.begin(), points.end(), 0.0, [](double sum, const cv::Point2f &p){return sum+p.y;}) / points.size(); // 平移至中心 for(auto& p : points){ p.x -= cx; p.y -= cy; } // 缩放因子s=√2/mean_distance_to_origin double meanDist = ... ; // 计算均方根距离 double s = sqrt(2)/meanDist; // 构造仿射变换T=[s 0 tx; 0 s ty; 0 0 1] } ``` 此部分涉及到了一些具体的数学运算细节,比如如何正确地完成坐标系下的平移缩放动作以达到理想的规范化状态。上述C++片段展示了简化版的操作逻辑框架供参考。 #### 使用OpenCV进行预处理 值得注意的是,在实际编码实践中往往还需要考虑镜头畸变等因素的影响。因此会先调用`Frame::UndistortKeyPoints()`函数对接收到的原始角点做去扭曲校正工作,从而提高后续算法精度[^2]。 #### 非线性最小二乘法解 当涉及到参数调整时,可能会引入诸如高斯-牛顿(GN), Levenberg-Marquardt (LM), 或 Dogleg 等非线性最优化技术来进行精炼。这类方法通常借助于像g2o这样的图形库辅助完成复杂结构下的高效寻优任务[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值