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 \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})

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

  1. 视觉slam十四讲第七章课后习题6

    版权声明:本文为博主原创文章,转载请注明出处: http://www.cnblogs.com/newneul/p/8545450.html 6.在PnP优化中,将第一个相机的观测也考虑进来,程序应如何 ...

  2. 视觉slam十四讲第七章课后习题7

    版权声明:本文为博主原创文章,转载请注明出处:http://www.cnblogs.com/newneul/p/8544369.html  7.题目要求:在ICP程序中,将空间点也作为优化变量考虑进来 ...

  3. 视觉slam学习之路(一)看高翔十四讲所遇到的问题

      目前实验室做机器人,主要分三个方向,定位导航,建图,图像识别,之前做的也是做了下Qt上位机,后面又弄红外识别,因为这学期上课也没怎么花时间在项目,然后导师让我们确定一个方向来,便于以后发论文什么. ...

  4. 浅读《视觉SLAM十四讲:从理论到实践》--操作1--初识SLAM

    下载<视觉SLAM十四讲:从理论到实践>源码:https://github.com/gaoxiang12/slambook 第二讲:初识SLAM 2.4.2 Hello SLAM(书本P2 ...

  5. 高翔《视觉SLAM十四讲》从理论到实践

    目录 第1讲 前言:本书讲什么:如何使用本书: 第2讲 初始SLAM:引子-小萝卜的例子:经典视觉SLAM框架:SLAM问题的数学表述:实践-编程基础: 第3讲 三维空间刚体运动 旋转矩阵:实践-Ei ...

  6. 高博-《视觉SLAM十四讲》

    0 讲座 (1)SLAM定义 对比雷达传感器和视觉传感器的优缺点(主要介绍视觉SLAM) 单目:不知道尺度信息 双目:知道尺度信息,但测量范围根据预定的基线相关 RGBD:知道深度信息,但是深度信息对 ...

  7. 《视觉SLAM十四讲》第2讲

    目录 一 视觉SLAM中的传感器 二 经典视觉SLAM框架 三 SLAM问题的数学表述 注:原创不易,转载请务必注明原作者和出处,感谢支持! 本讲主要内容: (1) 视觉SLAM中的传感器 (2) 经 ...

  8. 《视觉SLAM十四讲》第1讲

    目录 一 视觉SLAM 注:原创不易,转载请务必注明原作者和出处,感谢支持! 一 视觉SLAM 什么是视觉SLAM? SLAM是Simultaneous Localization and Mappin ...

  9. 视觉SLAM十四讲:从理论到实践 两版 PDF和源码

    视觉SLAM十四讲:从理论到实践 第一版电子版PDF 链接:https://pan.baidu.com/s/1SuuSpavo_fj7xqTYtgHBfw提取码:lr4t 源码github链接:htt ...

  10. 视觉slam十四讲个人理解(ch7视觉里程计1)

    参考博文::https://blog.csdn.net/david_han008/article/details/53560736 https://blog.csdn.net/n66040927/ar ...

随机推荐

  1. 有关二级指针char **p 的问题

    int main() { char *str[] = {"welcome", "to", "Fortemdia", "Nanjin ...

  2. Codeforces Round #835 (Div. 4) A-G

    比赛链接 A 题意 给出三个不同的数,求中位数. 题解 知识点:模拟. 显然. 时间复杂度 \(O(1)\) 空间复杂度 \(O(1)\) 代码 #include <bits/stdc++.h& ...

  3. Sql Server日期转汉字字符串

    以下脚本转至互联网,增加了自己需要的功能并改成了函数的方式 SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ================== ...

  4. JS逆向实战9——cookies DES加密混淆

    cookie加密 DES 混淆 目标网站:aHR0cHM6Ly90bGNoZW1zaG9wLnlvdXpoaWNhaS5jb20vbWFpbi90ZW5kP05vdGljZUNhdGVJZD0xJk5 ...

  5. js文字无限循环向上滚动

    html代码 <div id="scroll"> <div id="con1"> <p style="color: re ...

  6. 【每日一题】【找到位置返回&升序数组中第K大就是n-K小】2022年1月17日-NC88 寻找第K大

    描述有一个整数数组,请你根据快速排序的思路,找出数组中第 k 大的数. 给定一个整数数组 a ,同时给定它的大小n和要找的 k ,请返回第 k 大的数(包括重复的元素,不用去重),保证答案存在. 方法 ...

  7. cesium加载倾斜摄影,添加billboard并注册点击事件

    完整示例代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

  8. Java单例模式的最佳实践?

    "读过书,--我便考你一考.茴香豆的茴字,怎样写的?"--鲁迅<孔乙己> 0x00 大纲 目录 0x00 大纲 0x01 前言 0x02 单例的正确性 new关键字 c ...

  9. MySQL主从配置(Django实现主从配置读写分离)

    目录 一 MySQL主从配置原理(主从分离,主从同步) 二 操作步骤 2.1我们准备两台装好mysql的服务器(我在此用docker模拟了两台机器) 2.2 远程连接入主库和从库 远程连接主库 远程连 ...

  10. 【Vue】启动vue项目报错: errno: -4058, code: ‘ENOENT‘, syscall: ‘spawn cmd‘

    运行vue项目(npm run dev)报错 报错如下 问题原因 缺少cmd运行程序的环境变量 解决方法在环境变量Path中加上C:\windows\system32