• 视觉十四讲:第七讲_2D-2D:对极几何估计姿态


    1.对极几何

    从2张图片中,得到若干个配对好的2d特征点,就可以运用对极几何来恢复出两帧之间的运动.

    设P的空间坐标为: (P=[X,Y,Z]^{T})
    两个像素点(p_{1},p_{2})的像素坐标为: (s_{1}p_{1}=KP, s_{2}p_{2}=K(RP+t))
    K为相机内参,R,t为图像1到图像2的旋转矩阵和平移矩阵.

    • (x_{1}=k^{-1}p_{1}, x_{2}=k^{-1}p_{2}) (x1,x2是两个像素坐标在归一化平面上的坐标)
    • (x_{2}=Rx_{1}+t),两侧同时左乘(x^{T}_{2})t^
    • (x^{T}_{2})t</sup>$x_{2}$=$x^{T}_{2}$t<sup>(Rx_{1}),等式左边为0
    • (x^{T}_{2})t^(Rx_{1}=0)
    • 带入(p_{1},p_{2})(p_{2}^{T}K^{-T})t^(RK^{-1}p_{1} = 0)
    • 取基础矩阵(F=K^{-T}EK^{-1}),取本质矩阵(E=)t^(R)
    • (x_{2}^{T}Ex_{1} = p_{2}^{T}Fp_{1} = 0)

    相机姿态估计问题变成以下两步:

    • 根据配对点的像素位置求出R或者F
    • 根据E或F求出R,t

    2.本质矩阵

    根据本质矩阵(E=)t^(R)定义,这是一个3*3的矩阵,经典是使用8点法来求解.求解出E后,可通过奇异值分解得到相机的运动R和t.

    注意:求出的E和t具有尺度一致性,通常把t进行归一化.

    3.尺度不确定性

    对t的长度归一化,直接导致单目视觉的尺度不确定性.解决办法可以通过SLAM的初始化来解决,初始时,使机器人平移一段距离,然后以此距离作为平移的单位.初始化之后,就可以使用3D-2D来计算相机运动了

    工程中,通常匹配的点比较多,这时可以通过构造最小二乘法来进行求解E,但是由于存在误匹配的情况,所以更多的是使用随机采样一致性(RANSAC)来求解

    4.三角测距来测量深度

    根据对极几何的定义,(x_{1},x_{2})为两个特征点归一化的坐标,则满足:

    • (s_{1}x_{1}=s_{2}Rx_{2}+t),两边同时左乘(x_{1})^
    • (s_{2})(x_{1})</sup>$Rx_{2}+$$x_{1}$<sup>t = 0
    • 其中R和t在上面已经求出,故该式为(s_{2}的)方程.
    • 由于噪声存在,通常可以使用最小二乘法来求解(s_{2}),从而(s_{1})也能求出
    #include <iostream>
    #include <opencv2/opencv.hpp>
    // #include "extra.h" // used in opencv2
    using namespace std;
    using namespace cv;
    
    void find_feature_matches(
      const Mat &img_1, const Mat &img_2,
      std::vector<KeyPoint> &keypoints_1,
      std::vector<KeyPoint> &keypoints_2,
      std::vector<DMatch> &matches);
    
    void pose_estimation_2d2d(
      const std::vector<KeyPoint> &keypoints_1,
      const std::vector<KeyPoint> &keypoints_2,
      const std::vector<DMatch> &matches,
      Mat &R, Mat &t);
    
    void triangulation(
      const vector<KeyPoint> &keypoint_1,
      const vector<KeyPoint> &keypoint_2,
      const std::vector<DMatch> &matches,
      const Mat &R, const Mat &t,
      vector<Point3d> &points
    );
    
    /// 作图用
    inline cv::Scalar get_color(float depth) {
      float up_th = 50, low_th = 10, th_range = up_th - low_th;
      if (depth > up_th) depth = up_th;
      if (depth < low_th) depth = low_th;
      return cv::Scalar(255 * depth / th_range, 0, 255 * (1 - depth / th_range));
    }
    
    // 像素坐标转相机归一化坐标
    Point2f pixel2cam(const Point2d &p, const Mat &K);
    
    int main(int argc, char **argv) {
      if (argc != 3) {
        cout << "usage: triangulation img1 img2" << endl;
        return 1;
      }
      //-- 读取图像
      Mat img_1 = imread(argv[1], CV_LOAD_IMAGE_COLOR);
      Mat img_2 = imread(argv[2], CV_LOAD_IMAGE_COLOR);
    
      vector<KeyPoint> keypoints_1, keypoints_2;
      vector<DMatch> matches;
      find_feature_matches(img_1, img_2, keypoints_1, keypoints_2, matches);
      cout << "一共找到了" << matches.size() << "组匹配点" << endl;
    
      //-- 估计两张图像间运动
      Mat R, t;
      pose_estimation_2d2d(keypoints_1, keypoints_2, matches, R, t);
    
      //-- 三角化
      vector<Point3d> points;
      triangulation(keypoints_1, keypoints_2, matches, R, t, points);
    
      //-- 验证三角化点与特征点的重投影关系
      Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
      Mat img1_plot = img_1.clone();
      Mat img2_plot = img_2.clone();
      for (int i = 0; i < matches.size(); i++) {
        // 第一个图
        float depth1 = points[i].z;
        cout << "depth: " << depth1 << endl;
        Point2d pt1_cam = pixel2cam(keypoints_1[matches[i].queryIdx].pt, K);
        cv::circle(img1_plot, keypoints_1[matches[i].queryIdx].pt, 2, get_color(depth1), 2);
    
        // 第二个图
        Mat pt2_trans = R * (Mat_<double>(3, 1) << points[i].x, points[i].y, points[i].z) + t;
        float depth2 = pt2_trans.at<double>(2, 0);
        cv::circle(img2_plot, keypoints_2[matches[i].trainIdx].pt, 2, get_color(depth2), 2);
      }
      cv::imshow("img 1", img1_plot);
      cv::imshow("img 2", img2_plot);
      cv::waitKey();
    
      return 0;
    }
    
    void find_feature_matches(const Mat &img_1, const Mat &img_2,
                              std::vector<KeyPoint> &keypoints_1,
                              std::vector<KeyPoint> &keypoints_2,
                              std::vector<DMatch> &matches) {
      //-- 初始化
      Mat descriptors_1, descriptors_2;
      // used in OpenCV3
      Ptr<FeatureDetector> detector = ORB::create();
      Ptr<DescriptorExtractor> descriptor = ORB::create();
      // use this if you are in OpenCV2
      // Ptr<FeatureDetector> detector = FeatureDetector::create ( "ORB" );
      // Ptr<DescriptorExtractor> descriptor = DescriptorExtractor::create ( "ORB" );
      Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
      //-- 第一步:检测 Oriented FAST 角点位置
      detector->detect(img_1, keypoints_1);
      detector->detect(img_2, keypoints_2);
    
      //-- 第二步:根据角点位置计算 BRIEF 描述子
      descriptor->compute(img_1, keypoints_1, descriptors_1);
      descriptor->compute(img_2, keypoints_2, descriptors_2);
    
      //-- 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离
      vector<DMatch> match;
      // BFMatcher matcher ( NORM_HAMMING );
      matcher->match(descriptors_1, descriptors_2, match);
    
      //-- 第四步:匹配点对筛选
      double min_dist = 10000, max_dist = 0;
    
      //找出所有匹配之间的最小距离和最大距离, 即是最相似的和最不相似的两组点之间的距离
      for (int i = 0; i < descriptors_1.rows; i++) {
        double dist = match[i].distance;
        if (dist < min_dist) min_dist = dist;
        if (dist > max_dist) max_dist = dist;
      }
    
      printf("-- Max dist : %f 
    ", max_dist);
      printf("-- Min dist : %f 
    ", min_dist);
    
      //当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
      for (int i = 0; i < descriptors_1.rows; i++) {
        if (match[i].distance <= max(2 * min_dist, 30.0)) {
          matches.push_back(match[i]);
        }
      }
    }
    
    void pose_estimation_2d2d(
      const std::vector<KeyPoint> &keypoints_1,
      const std::vector<KeyPoint> &keypoints_2,
      const std::vector<DMatch> &matches,
      Mat &R, Mat &t) {
      // 相机内参,TUM Freiburg2
      Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
    
      //-- 把匹配点转换为vector<Point2f>的形式
      vector<Point2f> points1;
      vector<Point2f> points2;
    
      for (int i = 0; i < (int) matches.size(); i++) {
        points1.push_back(keypoints_1[matches[i].queryIdx].pt);
        points2.push_back(keypoints_2[matches[i].trainIdx].pt);
      }
    
      //-- 计算本质矩阵
      Point2d principal_point(325.1, 249.7);        //相机主点, TUM dataset标定值
      int focal_length = 521;            //相机焦距, TUM dataset标定值
      Mat essential_matrix;
      essential_matrix = findEssentialMat(points1, points2, focal_length, principal_point);
    
      //-- 从本质矩阵中恢复旋转和平移信息.
      recoverPose(essential_matrix, points1, points2, R, t, focal_length, principal_point);
    }
    
    void triangulation(
      const vector<KeyPoint> &keypoint_1,
      const vector<KeyPoint> &keypoint_2,
      const std::vector<DMatch> &matches,
      const Mat &R, const Mat &t,
      vector<Point3d> &points) {
      Mat T1 = (Mat_<float>(3, 4) <<
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0);
      Mat T2 = (Mat_<float>(3, 4) <<
        R.at<double>(0, 0), R.at<double>(0, 1), R.at<double>(0, 2), t.at<double>(0, 0),
        R.at<double>(1, 0), R.at<double>(1, 1), R.at<double>(1, 2), t.at<double>(1, 0),
        R.at<double>(2, 0), R.at<double>(2, 1), R.at<double>(2, 2), t.at<double>(2, 0)
      );
    
      Mat K = (Mat_<double>(3, 3) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1);
      vector<Point2f> pts_1, pts_2;
      for (DMatch m:matches) {
        // 将像素坐标转换至相机坐标
        pts_1.push_back(pixel2cam(keypoint_1[m.queryIdx].pt, K));
        pts_2.push_back(pixel2cam(keypoint_2[m.trainIdx].pt, K));
      }
    
      Mat pts_4d;
    //得到深度点,为4维齐次方程
    //输入是两个图片的位姿,以及特征点在两个相机中的坐标,归一化坐标
    //输出是第一个图片的特征点在相机中的坐标
      cv::triangulatePoints(T1, T2, pts_1, pts_2, pts_4d);
    
      // 转换成非齐次坐标
      for (int i = 0; i < pts_4d.cols; i++) {
        Mat x = pts_4d.col(i);  //取列信息
        x /= x.at<float>(3, 0); // 归一化
        Point3d p(
          x.at<float>(0, 0),  //得到非齐次的3D点
          x.at<float>(1, 0),
          x.at<float>(2, 0)
        );
        points.push_back(p);
      }
    }
    
    Point2f pixel2cam(const Point2d &p, const Mat &K) {
      return Point2f
        (
          (p.x - K.at<double>(0, 2)) / K.at<double>(0, 0),
          (p.y - K.at<double>(1, 2)) / K.at<double>(1, 1)
        );
    }
    
    

    CMakeLists.txt:

    cmake_minimum_required(VERSION 2.8)
    project(orb)
    
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
    list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
    include_directories(inc)
    aux_source_directory(src DIR_SRCS)
    SET(SOUR_FILE ${DIR_SRCS})
    find_package(OpenCV 3 REQUIRED)
    find_package(G2O REQUIRED)
    find_package(Sophus REQUIRED)
    
    include_directories(
            ${OpenCV_INCLUDE_DIRS}
            ${G2O_INCLUDE_DIRS}
            ${Sophus_INCLUDE_DIRS}
            "/usr/include/eigen3/"
    )
    
    
    add_executable(orb ${SOUR_FILE})
    target_link_libraries(orb ${OpenCV_LIBS})
    
    
  • 相关阅读:
    Extjs4.0以上版本 Ext.Ajax.request请求的返回问题
    C# NPOI 操作Excel 案例
    C# Microsoft.Office 操作Excel总结
    asp.net core NLog将日志写到文件
    新装的SSMS一打开就显示VS许可证过期,但VS又运行正常,解决方法。
    sql server 查询log日志 sql语句
    sql server 删除所有表及所有存储过程、所有视图和递归查询、数字类型转为字符串
    C#使用Selenium+PhantomJS抓取数据
    python爬虫实例项目大全
    SQL Server TVPs 批量插入数据
  • 原文地址:https://www.cnblogs.com/penuel/p/13289676.html
Copyright © 2020-2023  润新知