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

2023-05-16

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^\(x_{2}\)=\(x^{T}_{2}\)t^\(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}\)^\(Rx_{2}+\)\(x_{1}\)^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 \n", max_dist);
  printf("-- Min dist : %f \n", 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})
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

视觉十四讲:第七讲_2D-2D:对极几何估计姿态 的相关文章

  • 组原7_程序查询和程序中断方式

    目录 1 I O 方式简介 2 程序查询方式 3 程序中断方式 1 I O 方式简介 2 程序查询方式 程序查询方式流程图 程序查询方式接口结构 一次只能传送一个字的原因在于 xff1a 这种方式下 xff0c 外设传过来的数据是放在寄存器
  • k-近邻实现手写数字识别

    1 k 近邻工作原理 简单地说 xff0c K近邻算法采用测量不同特征值之间的距离方法进行分类 该算法具有一下特点 优点 xff1a 精度高 对异常值不敏感 无数据输入假定 缺点 xff1a 计算复杂度高 空间复杂度高 K近邻算法的工作原理
  • 吴恩达教你读论文:持续而缓慢的学习,才是正道

    转载这篇文章 xff0c 主要是有两句话特别认同 xff1a 持续而缓慢的学习 xff0c 而不是临时抱佛脚 xff0c 才能带来长久的成长 吴恩达 智慧不是学校教育的产物 xff0c 而是终身学习的产物 阿尔伯特 爱因斯坦 以下为原文 x
  • The Lighting Handbook, Tenth Edition 读书札记

    这本照明书可以说是经典了 xff0c 对照明从微观到宏观 xff0c 从理性到感性 xff0c 从物理到艺术洋洋洒洒做了比较充分和严谨的说明 xff0c 书中引用的论文也是浩浩荡荡 xff0c 可以说是一本集结了众多头脑 xff0c 闪烁着
  • 国家电网公司标准化建设成果应用目录(2015)参考书目

    国家电网标准化图集有很多 xff0c 但有时找的时候又很难记住书名 xff0c 因此把2015年之前的书籍目录摘录出来以便查找 目前是最先的版本了 xff0c 另外有一些06年的废止的分册不再列出了 2016年又增加了一本 国家电网公司输变
  • pyecharts——WordCloud词云图

    转自 xff1a pyecharts学习笔记 WordCloud词云图 码农家园 基本 词云图 注意数据格式 xff0c word1 count1 word2 count2 xff0c 可使用 counter 做词频统计 xff0c 生成这
  • 一个中文词云项目的使用总结

    一个中文词云项目的使用总结 用一个pa wordcloud项目来生成词云图的时候碰到了好几个问题 xff0c 一个pillow库安装问题 xff0c 卸载重新安装了最新版本 xff0c 就是numpy版本不匹配问题 xff0c 安装了对应p
  • 圆周率怎么计算来的?教你利用欧拉恒等式,生成圆周率万能公式!

    原文链接 xff1a http www twoeggz com news 4791962 html 在古代 xff0c 缺少数学技巧的情况下 xff0c 圆周率的计算是相当困难的 xff0c 我们国家伟大的数学家 xff0c 天文学家祖冲之
  • 影片avi转rmvb教程

    昨天被迫压制 MS IGLOO 正好学习了下影片avi转rmvb的技术 找来找去发现篇文章似乎不错 现在根据自己的操作过程 xff0c 加点操作心得再内 xff0c 保留一篇备用 xff01 首先还是要有专门压制的的软件 xff0c 之前我
  • 手机摄像头的等效焦距

    笔者随意拿出一张最近评测文章中的样张 xff0c EXIF信息就位于照片的下方 我们看到 xff0c 光圈 ISO感光度 曝光时间 曝光补偿这样的参数都比较好理解 xff0c 唯独这个焦距确实让不少人生疑 焦距 4mm 光圈 f 2 4 I
  • 关于3D打印文件格式:STL、OBJ、AMF、3MF的详解

    很多人对3D打印的数据格式颇有微词 xff0c 辛辛苦苦用三维软件设计好的作品 xff0c 一转换成3D打印格式 xff0c 基本就从白天鹅变成丑小鸭了 xff0c 既没有颜色 xff0c 数据也不完整 xff0c 形状重叠表面破损那是常有
  • 在线绘制函数图像和在线图标绘制网址

    经过寻找 xff0c 找到了几个在线绘制函数图像的网址 xff0c 可以不用matlab和geogebra软件绘制了 数学函数图像 xff1a 第一个 xff1a Desmos 首推 第二个 xff1a fooplot 可以绘制分段函数比如
  • geogebra中函数的定义域的输入

    ggb中函数的输入有如下几种方式 xff1a 一 如果if做法 1 区间函数 xff1a 做出函数在某区间上的图象 xff1a f x 61 if x gt 61 0 amp amp x lt 61 2 x 2 43 2x 1 2 分段函数
  • 升级Ubuntu内核

    自己下载deb或使用某些其他工具 xff0c 无脑dpkg deb会导致Depends libc6 gt 61 2 33 but 2 31 ubuntu9 2 is to be installed的错误 xff08 猜测该错误产生的原因是没
  • 在ROS的noetic版本中通过rosrun运行python文件

    xff08 1 xff09 不要将python文件放入scr目录中 xff0c 否则后续编译工作空间会报如图所示的错误 首先要在功能包文件夹 xff08 catkin ws src learnning topic xff09 中创建一个sc
  • linux音量调节

    转自 xff1a https www jianshu com p fc8c8cad67d6 一 alsa设置默认声卡 alsa设置默认声卡 理解和使用Alsa的配置文件 alsa的配置文件是alsa conf位于 usr share als
  • FutureTask实际应用案例

    GetResultTask java package com cwp data service service task import com cwp data intelligence common exception RRExcepti
  • 异常检测算法综述

    一 异常检测 随着人工智能的火热 xff0c 运维人员也开始考虑将算法引入运维领域 xff0c 对传统DevOps的核心功能进行优化改进 异常检测是运维不可或缺的重要要功能模块之一 xff0c 可以提升企业运维能力和效率 xff0c 释放运
  • 每日一书丨嵌入式C语言自我修养:从芯片、编译器到操作系统

    最近 xff0c 阅读了王工 xff08 王利涛 xff09 赠送的一本由他编著的书籍 嵌入式C语言自我修养 xff0c 感觉写的挺不错 今天分享一下这本书籍 嵌入式C语言自我修养 xff1a 从芯片 编译器到操作系统 从芯片 编译器到操作
  • JSP提交仍然停留在当前页面

    在C S结构中 xff0c 用户提交内容以后 xff0c 系统任停留在当前页面上 xff0c 直到服务返回处理成功或者失败的提示 而用户录入的信息 xff0c 除非程序清除 xff0c 否则不会自动消失 xff0c 方便用户修改 为了解决这

随机推荐

  • FreeRTOS Queue

    变量定义 span class token keyword typedef span span class token keyword void span span class token operator span QueueHandle
  • 专门讲解无人机航拍图像处理的书【包括图像拼接!!!】

    最近正式开始做课设啦 xff0c 博主在网上搜集到有专门的书讲解无人机航拍图像的处理 xff0c 包括图像拼接 xff01 xff01 xff01 更非常激动的是博主在图书馆把两本书都找到了 xff0c 俺滴学校i了i了 两本书如下所示 x
  • 1.2 向量与线性代数

    向量与线性代数 图形学基础向量向量点乘向量叉乘矩阵 图形学基础 基础数学 xff1a 线性代数 统计学 微积分基础物理 xff1a 其他课程 xff1a 信号处理 数学分析一点点 xff1a 美学课程 向量 方向长度单位向量向量加法 向量点
  • 2.1 变换

    矩阵变换 二维变换齐次坐标齐次坐标下的二维变换矩阵逆变换 xff08 逆矩阵 xff09 复合变换三维空间仿射变换 modeling and viewing 模型变换和视角变换 二维变换 尺度变换 Scale 镜像变换 切变变换 旋转变换
  • 2.2 变换(模型、视图、投影)

    变换 xff08 模型 视图 投影 xff09 三维变换观测变换 xff08 Viewing transformation xff09 视图 xff08 View xff09 定义相机如何将相机移动到约定俗成位置 投影 xff08 Proj
  • 四轴飞行器入门——基础知识

    引言 从2016年起 xff0c 细细数来入门无人机已经有两年时间 两年期间 xff0c 自己边学边摸索 xff0c 组装过机架四轴无人机 xff0c 也修改过开源飞控的代码 xff0c 但是因为种种原因 xff0c 始终没有写过相关博客记
  • Linux系统下搭建PX4/Pixhawk原生固件编译环境

    简介 PX4固件是Pixhawk飞行控制器的官方固件 xff0c Pixhawk官网也给出了Linux windows下搭建开发环境的方法 由于种种原因 xff0c 搭建开发环境时总会遇到各种各样的bug xff0c 致使PX4固件编译失败
  • main(int argc, char *argv[])

    这是UNIX和Linux中的标准主函数 argc 用来统计运行时发送给main函数的命令行参数的个数 argv 其中每个元素都是上述参数 以字符串形式存储 的首地址 argv 0 指向程序运行的全路径名 argv 1 指向程序名后的第一个参
  • 为PX4添加串口通讯模块(模块结构)

    主要讲模块的结构 不贴代码 从最外层开始 执行read uart main start dev ttyS1 read uart main int argc char argv 入口函数 判断任务进程read uart task是否存在 根据
  • C++抽象基类与虚基类(C++ primer)

    c 43 43 primer plus P508 xff0c 抽象基类 c 43 43 primer plus P556 xff0c 虚基类 抽象基类 xff08 abstract base class xff0c ABC xff09 抽象
  • MFC学习笔记(二)处理命令行选项

    目标 让应用程序处理这里所见的命令行标志 gt XXX exe c d 策略 一个MFC应用程序可以用CCommandLineInfo类的成员函数ParseParam 处理一些标准标 志 要添加我们自己的标志 xff0c 而仍然能够支持另外
  • C++ expection异常类、捕获所有异常(C++ primer,P639)

    expection类 头文件 lt expection gt stdexcept类 C 43 43 primer plus xff0c P632 包含以下异常 xff1a domain errorinvalid argumentlength
  • 5.1 运输层协议

    运输层协议 运输层的复用与常见端口常用端口 UDP协议特点UDP帧格式 TCP协议特点socket套接字可靠传输工作原理TCP帧首部重要字段 TCP可靠传输以字节为单位的滑动窗口选择超时重传时间选择确认SACK xff08 未经常使用P22
  • Linux上VScode + cmake + gcc开发环境搭建

    VScode 43 cmake 43 gcc 下载 安装vscode安装插件cmake文件结构vscode修改json文件编译 调试的过程 下载 安装 span class token comment cmake gcc 安装都很简单 sp
  • 软件测试面试04:实战项目介绍

    4 1 简单介绍下最近做过的项目 根据自己的项目整理完成 xff0c 要点 xff1a 1 xff09 项目背景 业务 需求 核心业务的流程 2 xff09 项目架构 xff0c B S还是C 5 xff0c 数据库用的什么 中间件用的什么
  • 一张图搞定SDF的概念

    本文仅代表个人理解 xff0c 谬误之处请指正 SDF Signed Distance Field 译为有向距离场 xff0c 有向 距离 场 这三个词非常精确的描述了 sdf 究竟是个什么东西 GPU Gems 3 中是这么描述 sdf
  • Ubuntu Windows双系统切换最简方法!!!

    安装完Ubuntu windows双系统后的第一个问题 xff1a 该怎么在两个系统间快速自由切换呢 xff1f 本文给出两种无需命令行的实用易上手方式 一 什么 xff0c 你要快快快快速切换 xff1f 这里直接给出答案 xff0c F
  • C++primer(第五版)习题答案

    前两章习题较简单 xff0c 这里就不作整理了 xff0c 直接从第三章开始 持续更新 xff1a Chapter 3 Strings Vectors and Arrays Exercise 3 1 part 1 include lt io
  • PX4源码地址和wiki

    源码 https github com 987419640 Firmware wiki https dev px4 io v1 9 0 zh concept architecture html
  • 视觉十四讲:第七讲_2D-2D:对极几何估计姿态

    1 对极几何 从2张图片中 得到若干个配对好的2d特征点 就可以运用对极几何来恢复出两帧之间的运动 设P的空间坐标为 P 61 X Y Z T 两个像素点 p 1 p 2 的像素坐标为 s 1 p 1 61 KP s 2 p 2 61 K