学习资料是深蓝学院的《从零开始手写VIO》课程,对课程做一些记录,方便自己以后查询,如有错误还请斧正。由于习惯性心算公式,所以为了加深理解,文章公式采用手写的形式。
关于前端的内容可以参考高翔博士的《视觉SLAM十四讲》。
通常的 SLAM 框架由前后端共同构成
实际上, 前端
非常能 体现
一个 SLAM 的追踪效果。
在实现中,尽管 后端
存在明显的理论 差异
,但 很难
直观体验在最终精度上。
问:假设给定相同的 Pose 和 Landmark,给定噪声范围,各种方法是否有明显差异?还是仅仅有理论性质上的差异?
尽管存在上述问题但是还可以问:
在很多实际场合,很难回答某种后端算法是否明确优于另一种。
例如:ORB-SLAM2 使用 Covisibility-Graph,DSO 使用带边缘化的滑动窗口,Tango 使用 MSCKF,等等。
实验效果:ORB-SLAM2 具有较好的全局精度,但无回环时 DSO具有漂移较少。Tango 计算量明显少于其他算法。
算法的结果和数据集关系很大
。例如:Kitti 属于比较简单的(视野开阔,动态物体少,标定准确),EUROC 一般(人工设定场景,纹理丰富,但曝光有变化),TUM-Mono 比较难(场景多样,主要为真实场景)
相比来说,前端的差异就比较明显
:
前端更多是范式(Paradigm)之间
的比较,而非范式之内的比较。
我们后文以传统光流为主来展开前端的介绍。
一个传统的双目光流前端流程(正常追踪流程):
在光流中,我们通常选择角点来追踪。
角点的梯度在两个方向都有分布:
FAST:仅含像素亮度、不含计算的快速角点提取方式;
GFTT:在 Harris 基础改进:Shi-tomasi 分数,增加固定选点数,等等
光流可以追踪一个时刻的角点在下个时刻的图像位置
灰度不变假设:
一阶 Taylor 展开:
得到:
其中 W 为 Warp Function,通常取仿射变换
其中
p
1
−
p
6
p _1 − p _6
p1−p6 为 W 的参数,需要在线估计。
Coarse-to-Fine:从顶层最模糊的图像开始计算光流,一直往下迭代到最高分辨率
对于非关键帧,只执行前端算法,不参与后端优化
因此,对于非关键帧,它的误差会逐渐累积。只有该帧被作为关键帧插入后端,BA 才会保证窗口内的一致性
总结关键帧选取的策略:在计算量允许范围内,且不引起退化时,应尽可能多地插入关键帧。
ORB-SLAM2 使用非常宽松的关键帧策略(大多数时候只要后端线程Idle 就会插入关键帧),然后在后端剔除冗余的关键帧。
DSO 利用光度误差插入关键帧(插入比较频繁)。然后在后端计算每个关键帧的 Active Landmarks,Marg 对窗口贡献最低的。因此,DSO 的关键帧窗口通常有一个很远的和两三个很近的,其他几个分布在中间。
在单目 SLAM 中,通常在插入关键帧时计算新路标点的三角化。
以上,我们推导了对任意次观测的三角化算法,可作为通用的依据。
//
// Created by hyj on 18-11-11.
//
#include <iostream>
#include <vector>
#include <random>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <Eigen/Eigenvalues>
struct Pose
{
Pose(Eigen::Matrix3d R, Eigen::Vector3d t):Rwc(R),qwc(R),twc(t) {};
Eigen::Matrix3d Rwc;
Eigen::Quaterniond qwc;
Eigen::Vector3d twc;
// 这帧图像观测到的特征坐标
Eigen::Vector2d uv;
};
int main()
{
int poseNums = 10;
double radius = 8;
double fx = 1.;
double fy = 1.;
std::vector<Pose> camera_pose;
for(int n = 0; n < poseNums; ++n ) {
double theta = n * 2 * M_PI / ( poseNums * 4); // 1/4 圆弧
// 绕 z轴 旋转
Eigen::Matrix3d R;
R = Eigen::AngleAxisd(theta, Eigen::Vector3d::UnitZ());
Eigen::Vector3d t = Eigen::Vector3d(radius * cos(theta) - radius, radius * sin(theta), 1 * sin(2 * theta));
camera_pose.push_back(Pose(R,t));
}
// 随机数生成 1 个 三维特征点
std::default_random_engine generator;
std::uniform_real_distribution<double> xy_rand(-4, 4.0);
std::uniform_real_distribution<double> z_rand(8., 10.);
double tx = xy_rand(generator);
double ty = xy_rand(generator);
double tz = z_rand(generator);
Eigen::Vector3d Pw(tx, ty, tz);
// 这个特征从第三帧相机开始被观测,i=3
int start_frame_id = 3;
int end_frame_id = poseNums;
for (int i = start_frame_id; i < end_frame_id; ++i) {
Eigen::Matrix3d Rcw = camera_pose[i].Rwc.transpose();
Eigen::Vector3d Pc = Rcw * (Pw - camera_pose[i].twc);
double x = Pc.x();
double y = Pc.y();
double z = Pc.z();
camera_pose[i].uv = Eigen::Vector2d(x/z,y/z);
}
/// TODO::homework; 请完成三角化估计深度的代码
// 遍历所有的观测数据,并三角化
Eigen::Vector3d P_est; // 结果保存到这个变量
P_est.setZero();
/* your code begin */
//step1:constuct D
Eigen::Matrix<double, 14, 4> D;
for (int i = start_frame_id; i < end_frame_id; ++i) {
Eigen::Matrix<double,3,4> Tcw;
Eigen::Matrix3d Rcw = camera_pose[i].Rwc.transpose();
Eigen::Vector3d tcw = -Rcw*camera_pose[i].twc;//注意是-:Pc=Pc0-tcw
Tcw<< Rcw, tcw;
//rows = mat.rows(2); // 获取第3行
Eigen::Vector2d uv = camera_pose[i].uv ;
D.block(2*(i - start_frame_id), 0, 1,4) = uv[0]*Tcw.row(2) - Tcw.row(0);
D.block(2*(i - start_frame_id)+1, 0, 1,4) = uv[1]*Tcw.row(2) - Tcw.row(1);
}
std::cout<<"D Matrix is :"<<D<<std::endl;
//step2:recale D,找到D中最大元素
Eigen::MatrixXd::Index maxRow, maxCol;
double max = D.maxCoeff(&maxRow,&maxCol);
//注意是D的绝对值中最大元素的D.cwiseAbs().maxCoeff()
std::cout << "Max = \n" << max <<"行:"<<maxRow<<"列"<<maxCol<< std::endl;
Eigen::MatrixXd DtD((D/max).transpose()*(D/max));
clock_t time_stt = clock();
Eigen::JacobiSVD<Eigen::MatrixXd> svd_holder(DtD, Eigen::ComputeThinU | Eigen::ComputeThinV );
clock_t time_stt1 = clock();
std::cout<<"SVD分解,耗时:\n"<<(clock()-time_stt1)/(double) CLOCKS_PER_SEC<<"ms"<<std::endl;
// 构建SVD分解结果 Eigen::MatrixXd U = svd_holder.matrixU(); Eigen::MatrixXd V = svd_holder.matrixV();
Eigen::MatrixXd S(svd_holder.singularValues());
std::cout<<"singularValues :\n"<<S<<std::endl;
//step3:判断分解是否有效,然后求解y
if(std::abs(svd_holder.singularValues()[3]/svd_holder.singularValues()[2]) < 1e-2){
Eigen::Vector4d U4 = (max*svd_holder.matrixU().rightCols(1));//最后一列
P_est = (U4/U4[3]).head(3);
}
else{
std::cout<<"这次求解无效"<<std::endl;
}
/* your code end */
std::cout <<"ground truth: \n"<< Pw.transpose() <<std::endl;
std::cout <<"your result: \n"<< P_est.transpose() <<std::endl;
// TODO:: 请如课程讲解中提到的判断三角化结果好坏的方式,绘制奇异值比值变化曲线
return 0;
}
参考博客:
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- ovod.cn 版权所有 湘ICP备2023023988号-4
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务