OpenCV实现求解单目相机位姿

这篇具有很好参考价值的文章主要介绍了OpenCV实现求解单目相机位姿。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        单目相机通过对极约束来求解相机运动的位姿。参考了ORBSLAM中单目实现的代码,这里用opencv来实现最简单的位姿估计.

OpenCV实现求解单目相机位姿,OpenCV,图像视频处理,opencv,数码相机,人工智能

 


mLeftImg = cv::imread(lImg, cv::IMREAD_GRAYSCALE);
mRightImg = cv::imread(rImg, cv::IMREAD_GRAYSCALE);
cv::Ptr<ORB> OrbLeftExtractor = cv::ORB::create();
cv::Ptr<ORB> OrbRightExtractor = cv::ORB::create();

OrbLeftExtractor->detectAndCompute(mLeftImg, noArray(), mLeftKps, mLeftDes);
OrbRightExtractor->detectAndCompute(mRightImg, noArray(), mRightKps, mRightDes);
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create(DescriptorMatcher::BRUTEFORCE_HAMMING);
matcher->match(mLeftDes, mRightDes, mMatches);

 首先通过ORB特征提取,获取两幅图像的匹配度,我将其连线出来的效果:

OpenCV实现求解单目相机位姿,OpenCV,图像视频处理,opencv,数码相机,人工智能

 

RANSAC的算法原理可以google,很容易理解。先看看ORBSLAM中的实现:

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
    // Frame2 特征点
    mvKeys2 = CurrentFrame.mvKeysUn;

    // mvMatches12记录匹配上的特征点对
    mvMatches12.clear();
    mvMatches12.reserve(mvKeys2.size());
    // mvbMatched1记录每个特征点是否有匹配的特征点,
    // 这个变量后面没有用到,后面只关心匹配上的特征点
    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]));
            mvbMatched1[i]=true;
        }
        else
            mvbMatched1[i]=false;
    }

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

    // Indices for minimum set selection
    // 新建一个容器vAllIndices,生成0到N-1的数作为特征点的索引
    vector<size_t> vAllIndices;
    vAllIndices.reserve(N);
    vector<size_t> vAvailableIndices;

    for(int i=0; i<N; i++)
    {
        vAllIndices.push_back(i);
    }

    // Generate sets of 8 points for each RANSAC iteration
    // **步骤2:在所有匹配特征点对中随机选择8对匹配特征点为一组,共选择mMaxIterations组 **
     // 用于FindHomography和FindFundamental求解
    // mMaxIterations:200
    mvSets = vector< vector<size_t> >(mMaxIterations,vector<size_t>(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++)
        {
            // 产生0到N-1的随机数
            int randi = DUtils::Random::RandomInt(0,vAvailableIndices.size()-1);
            // idx表示哪一个索引对应的特征点被选中
            int idx = vAvailableIndices[randi];

            mvSets[it][j] = idx;

            // randi对应的索引已经被选过了,从容器中删除
            // randi对应的索引用最后一个元素替换,并删掉最后一个元素
            vAvailableIndices[randi] = vAvailableIndices.back();
            vAvailableIndices.pop_back();
        }
    }

    // Launch threads to compute in parallel a fundamental matrix and a homography
    // 步骤3:调用多线程分别用于计算fundamental matrix和homography
    vector<bool> vbMatchesInliersH, vbMatchesInliersF;
    float SH, SF; // score for H and F
    cv::Mat H, F; // H and F

    // ref是引用的功能:http://en.cppreference.com/w/cpp/utility/functional/ref
    // 计算homograpy并打分
    thread threadH(&Initializer::FindHomography,this,ref(vbMatchesInliersH), ref(SH), ref(H));
    // 计算fundamental matrix并打分
    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
    // 步骤4:计算得分比例,选取某个模型
    float RH = SH/(SH+SF);

    // Try to reconstruct from homography or fundamental depending on the ratio (0.40-0.45)
    // 步骤5:从H矩阵或F矩阵中恢复R,t
    if(RH>0.40)
        return ReconstructH(vbMatchesInliersH,H,mK,R21,t21,vP3D,vbTriangulated,1.0,50);
    else //if(pF_HF>0.6)
        return ReconstructF(vbMatchesInliersF,F,mK,R21,t21,vP3D,vbTriangulated,1.0,50);

    return false;
}

 

orbslam首先是从配对特征中随机迭代mMaxIterations次,每一次都从配对点中选出8个点用来计算homography和fundamental矩阵,都是用SVD来计算的,如下:

FindFundamental:


void Initializer::FindFundamental(vector<bool> &vbMatchesInliers, float &score, cv::Mat &F21)
{
    // Number of putative matches
    const int N = vbMatchesInliers.size();

    // 分别得到归一化的坐标P1和P2
    vector<cv::Point2f> vPn1, vPn2;
    cv::Mat T1, T2;
    Normalize(mvKeys1,vPn1, T1);
    Normalize(mvKeys2,vPn2, T2);
    cv::Mat T2t = T2.t();

    // Best Results variables
    score = 0.0;
    vbMatchesInliers = vector<bool>(N,false);

    // Iteration variables
    vector<cv::Point2f> vPn1i(8);
    vector<cv::Point2f> vPn2i(8);
    cv::Mat F21i;
    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++)
    {
        // Select a minimum set
        for(int j=0; j<8; j++)
        {
            int idx = mvSets[it][j];

            vPn1i[j] = vPn1[mvMatches12[idx].first];
            vPn2i[j] = vPn2[mvMatches12[idx].second];
        }

        cv::Mat Fn = ComputeF21(vPn1i,vPn2i);

        F21i = T2t*Fn*T1;  //解除归一化

        // 利用重投影误差为当次RANSAC的结果评分
        currentScore = CheckFundamental(F21i, vbCurrentInliers, mSigma);

        if(currentScore>score)
        {
            F21 = F21i.clone();
            vbMatchesInliers = vbCurrentInliers;
            score = currentScore;
        }
    }
}

通过ComputeF21计算本质矩阵,

cv::Mat Initializer::ComputeF21(const vector<cv::Point2f> &vP1,const vector<cv::Point2f> &vP2)
{
    const int N = vP1.size();

    cv::Mat A(N,9,CV_32F); // N*9

    for(int i=0; i<N; i++)
    {
        const float u1 = vP1[i].x;
        const float v1 = vP1[i].y;
        const float u2 = vP2[i].x;
        const float v2 = vP2[i].y;

        A.at<float>(i,0) = u2*u1;
        A.at<float>(i,1) = u2*v1;
        A.at<float>(i,2) = u2;
        A.at<float>(i,3) = v2*u1;
        A.at<float>(i,4) = v2*v1;
        A.at<float>(i,5) = v2;
        A.at<float>(i,6) = u1;
        A.at<float>(i,7) = v1;
        A.at<float>(i,8) = 1;
    }

    cv::Mat u,w,vt;

    cv::SVDecomp(A,w,u,vt,cv::SVD::MODIFY_A | cv::SVD::FULL_UV);

    cv::Mat Fpre = vt.row(8).reshape(0, 3); // v的最后一列

    cv::SVDecomp(Fpre,w,u,vt,cv::SVD::MODIFY_A | cv::SVD::FULL_UV);

    w.at<float>(2)=0; // 秩2约束,将第3个奇异值设为0 //强迫约束

    return  u*cv::Mat::diag(w)*vt;
}

 

看到用的是线性SVD解。

通过重投影来评估本质矩阵的好坏。


float Initializer::CheckFundamental(const cv::Mat &F21, vector<bool> &vbMatchesInliers, float sigma)
{
    const int N = mvMatches12.size();

    const float f11 = F21.at<float>(0,0);
    const float f12 = F21.at<float>(0,1);
    const float f13 = F21.at<float>(0,2);
    const float f21 = F21.at<float>(1,0);
    const float f22 = F21.at<float>(1,1);
    const float f23 = F21.at<float>(1,2);
    const float f31 = F21.at<float>(2,0);
    const float f32 = F21.at<float>(2,1);
    const float f33 = F21.at<float>(2,2);

    vbMatchesInliers.resize(N);

    float score = 0;

    // 基于卡方检验计算出的阈值(假设测量有一个像素的偏差)
    const float th = 3.841;  //置信度95%,自由度1
    const float thScore = 5.991;//置信度95%,自由度2

    const float invSigmaSquare = 1.0/(sigma*sigma);

    for(int i=0; i<N; i++)
    {
        bool bIn = true;

        const cv::KeyPoint &kp1 = mvKeys1[mvMatches12[i].first];
        const cv::KeyPoint &kp2 = mvKeys2[mvMatches12[i].second];

        const float u1 = kp1.pt.x;
        const float v1 = kp1.pt.y;
        const float u2 = kp2.pt.x;
        const float v2 = kp2.pt.y;

        // Reprojection error in second image
        // l2=F21x1=(a2,b2,c2)
        // F21x1可以算出x1在图像中x2对应的线l
        const float a2 = f11*u1+f12*v1+f13;
        const float b2 = f21*u1+f22*v1+f23;
        const float c2 = f31*u1+f32*v1+f33;

        // x2应该在l这条线上:x2点乘l = 0 
        const float num2 = a2*u2+b2*v2+c2;

        const float squareDist1 = num2*num2/(a2*a2+b2*b2); // 点到线的几何距离 的平方

        const float chiSquare1 = squareDist1*invSigmaSquare;

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

        // Reprojection error in second image
        // l1 =x2tF21=(a1,b1,c1)

        const float a1 = f11*u2+f21*v2+f31;
        const float b1 = f12*u2+f22*v2+f32;
        const float c1 = f13*u2+f23*v2+f33;

        const float num1 = a1*u1+b1*v1+c1;

        const float squareDist2 = num1*num1/(a1*a1+b1*b1);

        const float chiSquare2 = squareDist2*invSigmaSquare;

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

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

    return score;
}

最后回到Initializer::Initialize,将单映矩阵和本质矩阵的得分进行比对,选出最合适的,就求出RT了。

ORBSLAM2同时考虑了单应和本质,SLAM14讲中也说到,工程实践中一般都讲两者都计算出来选择较好的,不过效率上会影响比较多感觉。

opencv实现就比较简单了,思路和上面的类似,只是现在只考虑本质矩阵。在之前获取到特征点之后,

/*add ransac method for accurate match*/
          vector<Point2f> vLeftP2f;
          vector<Point2f> vRightP2f;
          for(auto& each:mMatches)
          {
              vLeftP2f.push_back(mLeftKps[each.queryIdx].pt);
              vRightP2f.push_back(mRightKps[each.trainIdx].pt);
          }
          vector<unsigned char> vTemp(vLeftP2f.size());
            /*计算本质矩阵,用RANSAC*/
          Mat transM = findEssentialMat(vLeftP2f, vRightP2f, cameraMatrix,RANSAC, 0.999, 1.0, vTemp);
          vector<DMatch> optimizeM;
          for(int i = 0; i < vTemp.size(); i++)
          {
             if(vTemp[i])
              {
                  optimizeM.push_back(mMatches[i]);
              }
          }
          mMatches.swap(optimizeM);
          cout << transM<<endl;
         Mat optimizeP;
          drawMatches(mLeftImg, mLeftKps, mRightImg, mRightKps, mMatches, optimizeP);
          imshow("output5", optimizeP);

看下结果:

OpenCV实现求解单目相机位姿,OpenCV,图像视频处理,opencv,数码相机,人工智能

 确实效果好多了,匹配准确度比之前的要好,之后我们就可以通过这个本质矩阵来计算RT了。

Mat R, t, mask;
recoverPose(transM, vLeftP2f, vRightP2f, cameraMatrix, R, t, mask);

一个接口搞定。最后我们可以通过验证对极约束,来看看求出的位姿是否准确。

定义检查函数:

Mat cameraMatrix = (Mat_<double>(3,3) << CAM_FX, 0.0, CAM_CX, 0.0, CAM_FY, CAM_CY, 0.0, 0.0, 1.0);
 
  bool epipolarConstraintCheck(Mat CameraK, vector<Point2f>& p1s, vector<Point2f>& p2s, Mat R, Mat t)
  {
      for(int i = 0; i < p1s.size(); i++)
      {
          Mat y1 = (Mat_<double>(3,1)<<p1s[i].x, p1s[i].y, 1);
         Mat y2 = (Mat_<double>(3,1)<<p2s[i].x, p2s[i].y, 1);
          //T 的凡对称矩阵
          Mat t_x = (Mat_<double>(3,3)<< 0, -t.at<double>(2,0), t.at<double>(1,0),
                    t.at<double>(2,0), 0, -t.at<double>(0,0),
                    -t.at<double>(1,0),t.at<double>(0,0),0);
          Mat d = y2.t() * cameraMatrix.inv().t() * t_x * R * cameraMatrix.inv()* y1;
          cout<<"epipolar constraint = "<<d<<endl;
      }
  }

最后可以看到结果都是趋近于0的,证明位姿还是比较准确的。文章来源地址https://www.toymoban.com/news/detail-741910.html

到了这里,关于OpenCV实现求解单目相机位姿的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于openCV实现的单目相机行人和减速带检测

    用户在运行 calibration.py 时,会遵循清晰的操作指南逐步完成整个标定过程。首先,程序会在用户的指示下自动或选择性地清空标定板图片文件夹,以便收集新的图像数据。接下来,用户可以通过capture阶段预设拍照数量,确保获取足够丰富的标定样本。这些样本将用于后续的

    2024年03月19日
    浏览(42)
  • 单目相机测距(3米范围内)二维码实现方案(python代码 仅仅依赖opencv)

    总体思路:先通过opencv 识别二维码的的四个像素角位置,然后把二维码的物理位置设置为 ,相当于这是一个任意找的物体上的四个点,对应的我们找到了在图像中对应的像素坐标。这就解决了世界坐标系与像素坐标系之间的对应问题,然后再通过PNP求解的方式,就可以通过

    2024年02月04日
    浏览(47)
  • OpenCV​处理相机图像卡顿

    OpenCV 处理相机图像卡顿,卡顿的原因可能是处理图像的算法复杂度过高,或者硬件性能不足。以下是一些可能帮助减少卡顿的方法和示例代码: 降低图像分辨率:可以通过调整相机的分辨率或使用 opencv 的 resize 函数来降低图像分辨率,减少处理图像的像素数量。 2、使用多

    2024年02月19日
    浏览(35)
  • 自学SLAM(6)相机与图像实践:OpenCV处理图像与图像拼接(点云)

    如果写过SLAM14讲第一次的作业,或者看过我之前的运行ORB_SLAM2教程应该都安装过OpenCV了,如果没有安装,没关系,可以看我之前的博客,里面有如何安装OpenCV。 链接: 运行ORB-SLAM2(含OpenCV的安装) 让我们先来看一段代码,学习一下OpenCV的函数调用。 改代码中,演示了如下几

    2024年02月06日
    浏览(41)
  • 棋盘格测距-单目相机(OpenCV/C++)

    1’ 通过cv::findChessboardCorners寻找棋盘格角点 2‘ 用cv::solvePnP计算旋转向量rvec和平移向量tvec 3’ 通过公式计算相机到棋盘格的距离   已完成单目相机标定的情况下: (可以参考  虽然是我很久之前写的python的,但实现是没啥问题) 需要以下内容: 1、已知相机的内参矩阵 c

    2024年02月08日
    浏览(49)
  • opencv 单目相机pnp测距(Cpp)

    单目相机pnp测距是通过单目相机拍摄的一张2d图片,来测量图片中某物与相机的距离。 需要知道被测物的实际尺寸 测距前 需要先 相机标定 ,需要使用哪个相机进行测距就标定哪个。一旦换成了其他相机, 就要重新标定最终相机。 为什么要相机标定? 相机标定是为了得到从

    2024年02月04日
    浏览(41)
  • Python OpenCV 单目相机标定、坐标转换相关代码(包括鱼眼相机)

      本文不讲原理,只关注代码,有很多博客是讲原理的,但是代码最多到畸变矫正就结束了,实际上就是到 OpenCV 官方示例涉及的部分。   在官方示例中使用黑白棋盘格求解了相机的内外参和畸变系数,并对图像做了畸变矫正,但在实际使用时还缺少很多功能,以下是本

    2024年02月02日
    浏览(36)
  • 基于OpenCV的单目相机标定与三维定位

           相机是产生图像数据的硬件,广泛应用于消费电子、汽车、安防等领域。围绕着相机衍生出一系列的研究与应用领域,包括传统的图像处理和基于深度学习的智能应用等。目前大火的自动驾驶中相机也是重要的硬件组成,如环视用鱼眼相机,adas用周视相机。    

    2024年02月09日
    浏览(37)
  • OpenCV快速入门:相机标定——单目视觉和双目视觉

    在当今科技日益发展的时代,计算机视觉作为人工智能的重要分支,已经深入到我们生活的各个领域。在这个广阔的领域中,相机标定是一个基础且关键的步骤,它直接影响到视觉系统的精度和效能。尤其是在单目视觉和双目视觉的应用中,准确的相机标定成为了实现高效和

    2024年02月05日
    浏览(49)
  • Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 视频图像处理基础操作 之 视频捕获/存储/提取/合成/合并

    目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 视频图像处理基础操作 之 视频捕获/存储/提取/合成/合并 一、简单介绍 二、视频处理流程和原理 三、视频的捕获和存储 四、提取视频中的某些帧 五、将图片合成为视频 六、多个视频合并 Python是一种跨平台的计算机程序设计

    2024年04月10日
    浏览(123)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包