视觉SLAM:VIO的误差和误差雅可比矩阵
1.两个相机之间的非线性优化
观测相机方程关于相机位姿与特征点的雅可比矩阵:
1.1 位姿:

1.2 3D特征点

- fx,fy,fz为相机内参
- X',Y',Z'为3D点在相机坐标系下的坐标
- 该误差是观测值减去预测值,反过来,预测值减观测值时,去掉或加上负号即可
- 姿态定义为先平移后旋转,如果定义为先旋转后平移,将该矩阵的前3列与后3列对调即可
2.vio滑动窗口的BA优化
1.相机:
相机误差仍然为重投影误差:
优化是在机体坐标系下完成,也就是imu系,所以多了一个相机到机体坐标的外参



根据链式法则,可以分两步走,第一步,误差对\(f_{cj}\)求导,最后再分别相乘即可
2.1 误差对\(f_{cj}\)求导:

2.2 \(f_{cj}\)对逆深度的求导:

2.3 \(f_{cj}\)对各时刻状态量的求导:
对i时刻的位移求导:

对i时刻的角度增量求导:

对j时刻的位移求导;

对j时刻的角度增量求导

2.4 \(f_{cj}\)对imu和相机的外参求导:
- 对位移求导:

- 对角度增量求导:
分为两部分求导: \(f_{cj} = f_{cj}^{1} + f_{cj}^{2}\)
第一部分:

第二部分:

最后相加即可。
注意:最后别忘了分别乘上误差对\(f_{cj}\)的求导
2.5 程序示例:
double inv_dep_i = verticies_[0]->Parameters()[0];
VecX param_i = verticies_[1]->Parameters(); //i时刻位姿
Qd Qi(param_i[6], param_i[3], param_i[4], param_i[5]); //姿态
Vec3 Pi = param_i.head<3>(); //位移
VecX param_j = verticies_[2]->Parameters(); //j时刻位姿
Qd Qj(param_j[6], param_j[3], param_j[4], param_j[5]);
Vec3 Pj = param_j.head<3>();
VecX param_ext = verticies_[3]->Parameters();
Qd qic(param_ext[6], param_ext[3], param_ext[4], param_ext[5]);
Vec3 tic = param_ext.head<3>()
Vec3 pts_camera_i = pts_i_ / inv_dep_i;
Vec3 pts_imu_i = qic * pts_camera_i + tic;
Vec3 pts_w = Qi * pts_imu_i + Pi;
Vec3 pts_imu_j = Qj.inverse() * (pts_w - Pj);
Vec3 pts_camera_j = qic.inverse() * (pts_imu_j - tic);
double dep_j = pts_camera_j.z();
Mat33 Ri = Qi.toRotationMatrix();
Mat33 Rj = Qj.toRotationMatrix();
Mat33 ric = qic.toRotationMatrix();
Mat23 reduce(2, 3); //误差对f_cj求导
reduce << 1. / dep_j, 0, -pts_camera_j(0) / (dep_j * dep_j),
0, 1. / dep_j, -pts_camera_j(1) / (dep_j * dep_j);
// reduce = information_ * reduce;
Eigen::Matrix<double, 2, 6> jacobian_pose_i;
Eigen::Matrix<double, 3, 6> jaco_i;
jaco_i.leftCols<3>() = ric.transpose() * Rj.transpose(); //位移求导
jaco_i.rightCols<3>() = ric.transpose() * Rj.transpose() * Ri * -
Sophus::SO3d::hat(pts_imu_i); //角度增量求导
jacobian_pose_i.leftCols<6>() = reduce * jaco_i;
Eigen::Matrix<double, 2, 6> jacobian_pose_j;
Eigen::Matrix<double, 3, 6> jaco_j;
jaco_j.leftCols<3>() = ric.transpose() * -Rj.transpose();
jaco_j.rightCols<3>() = ric.transpose() * Sophus::SO3d::hat(pts_imu_j);
jacobian_pose_j.leftCols<6>() = reduce * jaco_j;
Eigen::Vector2d jacobian_feature;
//逆深度求导
jacobian_feature = reduce * ric.transpose() * Rj.transpose() * Ri * ric * pts_i_ * -1.0 / (inv_dep_i * inv_dep_i);
//IMU和相机外参求导
Eigen::Matrix<double, 2, 6> jacobian_ex_pose;
Eigen::Matrix<double, 3, 6> jaco_ex;
jaco_ex.leftCols<3>() = ric.transpose() * (Rj.transpose() * Ri - Eigen::Matrix3d::Identity());
Eigen::Matrix3d tmp_r = ric.transpose() * Rj.transpose() * Ri * ric;
jaco_ex.rightCols<3>() = -tmp_r * Utility::skewSymmetric(pts_camera_i) + Utility::skewSymmetric(tmp_r * pts_camera_i) + Utility::skewSymmetric(ric.transpose() * (Rj.transpose() * (Ri * tic + Pi - Pj) - tic));
jacobian_ex_pose.leftCols<6>() = reduce * jaco_ex;
jacobians_[0] = jacobian_feature; //2行1列
jacobians_[1] = jacobian_pose_i;
jacobians_[2] = jacobian_pose_j;
jacobians_[3] = jacobian_ex_pose;
2.IMU:
IMU的真实值为 w,a, 测量值为\(w^{~}, a^{~}\),则有:

其中: b为bias随机游走误差,n为白噪声。
预积分:
预积分仅仅与imu测量值有关,将一段时间的imu数据直接积分起来就得到了与积分量

则j时刻的PVQ积分积分方程为:

其中p为位移,v为速度,q为姿态,b为bias噪声
2.1 IMU的与积分误差:

其中,位移,速度,bias噪声的误差都是直接相减,第二项是关于四元数的旋转误差,后缀xyz代表取四元数的虚部(x, y, z)组成的三维向量。
void EdgeImu::ComputeResidual() {
VecX param_0 = verticies_[0]->Parameters();
Qd qi(param_0[6], param_0[3], param_0[4], param_0[5]);
Vec3 pi = param_0.head<3>();
SO3d ri(qi);
SO3d ri_inv = ri.inverse();
VecX param_1 = verticies_[1]->Parameters();
Vec3 vi = param_1.head<3>();
Vec3 bai = param_1.segment(3, 3);
Vec3 bgi = param_1.tail<3>();
VecX param_2 = verticies_[2]->Parameters();
Qd qj(param_2[6], param_2[3], param_2[4], param_2[5]);
Vec3 pj = param_2.head<3>();
VecX param_3 = verticies_[3]->Parameters();
Vec3 vj = param_3.head<3>();
Vec3 baj = param_3.segment(3, 3);
Vec3 bgj = param_3.tail<3>();
SO3d rj(qj);
double dt = pre_integration_->GetSumDt();
double dt2 = dt * dt;
SO3d dr;
Vec3 dv;
Vec3 dp;
pre_integration_->GetDeltaRVP(dr, dv, dp); //获取预积分值
SO3d res_r = dr.inverse() * ri_inv * rj;
residual_.block<3, 1>(0, 0) = SO3d::log(res_r);
residual_.block<3, 1>(3, 0) = ri_inv * (vj - vi - gravity_ * dt) - dv;
residual_.block<3, 1>(6, 0) = ri_inv * (pj - pi - vi * dt - 0.5 * gravity_ * dt2) - dp;
residual_.block<3, 1>(9, 0) = baj - bai;
residual_.block<3, 1>(12, 0) = bgj - bgi;
}
2.2 IMU的误差雅可比矩阵:
基于泰勒展开的误差传递(EKF):
非线性系统\(x_{k} = f(x_{k-1}, u_{k-1})\) 的状态误差的线性递推关系为:

其中,F是状态量\(x_{k}\)对状态量\(x_{k-1}\)的雅可比矩阵,G是状态量\(x_{k}对输入量\)u_{k-1}$的雅可比矩阵。
IMU的误差传递方程为:


其中的系数为:

- 速度预积分对各状态量的雅可比,为F的第三行,分别是:位移预积分,旋转预积分,速度预积分,陀螺仪bias噪声,加速度bias噪声
f33: 速度预积分量对上一时刻速度预积分量的雅可比,为I
f32: 速度预积分量对角度预积分量的雅可比
f35: 速度预积分量对k时刻角速度bias噪声的雅可比
f22: 前一时刻的旋转误差如何影响当前旋转误差
2.3 IMU相对于优化变量的雅可比矩阵:
在求解非线性方程式,我们需要知道IMU误差对两个关键帧i,j的状态p,q,v,\(b^{a}, b^{g}\)的雅可比
- 对i时刻的位移:

- 对i时刻的旋转:


- 对i时刻的速度:

- 对i时刻的加速度bias:

IMU角度误差相对于优化变量的雅可比 - 角度误差对i时刻的姿态求导:

其中[]L 和[]R 为四元数转为左/右旋转矩阵的算子 - 角度误差对j时刻姿态求导

- 角度误差对i时刻陀螺仪bias噪声求导

void EdgeImu::ComputeJacobians() {
VecX param_0 = verticies_[0]->Parameters();
Qd Qi(param_0[6], param_0[3], param_0[4], param_0[5]);
Vec3 Pi = param_0.head<3>();
VecX param_1 = verticies_[1]->Parameters();
Vec3 Vi = param_1.head<3>();
Vec3 Bai = param_1.segment(3, 3);
Vec3 Bgi = param_1.tail<3>();
VecX param_2 = verticies_[2]->Parameters();
Qd Qj(param_2[6], param_2[3], param_2[4], param_2[5]);
Vec3 Pj = param_2.head<3>();
VecX param_3 = verticies_[3]->Parameters();
Vec3 Vj = param_3.head<3>();
Vec3 Baj = param_3.segment(3, 3);
Vec3 Bgj = param_3.tail<3>();
double sum_dt = pre_integration_->sum_dt;
Eigen::Matrix3d dp_dba = pre_integration_->jacobian.template block<3, 3>(O_P, O_BA);
Eigen::Matrix3d dp_dbg = pre_integration_->jacobian.template block<3, 3>(O_P, O_BG);
Eigen::Matrix3d dq_dbg = pre_integration_->jacobian.template block<3, 3>(O_R, O_BG);
Eigen::Matrix3d dv_dba = pre_integration_->jacobian.template block<3, 3>(O_V, O_BA);
Eigen::Matrix3d dv_dbg = pre_integration_->jacobian.template block<3, 3>(O_V, O_BG);
if (pre_integration_->jacobian.maxCoeff() > 1e8 || pre_integration_->jacobian.minCoeff() < -1e8)
{
// ROS_WARN("numerical unstable in preintegration");
}
// if (jacobians[0])
{
Eigen::Matrix<double, 15, 6, Eigen::RowMajor> jacobian_pose_i;
jacobian_pose_i.setZero();
jacobian_pose_i.block<3, 3>(O_P, O_P) = -Qi.inverse().toRotationMatrix();
jacobian_pose_i.block<3, 3>(O_P, O_R) = Utility::skewSymmetric(Qi.inverse() * (0.5 * G * sum_dt * sum_dt + Pj - Pi - Vi * sum_dt));
Eigen::Quaterniond corrected_delta_q = pre_integration_->delta_q * Utility::deltaQ(dq_dbg * (Bgi - pre_integration_->linearized_bg));
jacobian_pose_i.block<3, 3>(O_R, O_R) = -(Utility::Qleft(Qj.inverse() * Qi) * Utility::Qright(corrected_delta_q)).bottomRightCorner<3, 3>();
jacobian_pose_i.block<3, 3>(O_V, O_R) = Utility::skewSymmetric(Qi.inverse() * (G * sum_dt + Vj - Vi));
if (jacobian_pose_i.maxCoeff() > 1e8 || jacobian_pose_i.minCoeff() < -1e8)
{
// ROS_WARN("numerical unstable in preintegration");
}
jacobians_[0] = jacobian_pose_i;
}
// if (jacobians[1])
{
Eigen::Matrix<double, 15, 9, Eigen::RowMajor> jacobian_speedbias_i;
jacobian_speedbias_i.setZero();
jacobian_speedbias_i.block<3, 3>(O_P, O_V - O_V) = -Qi.inverse().toRotationMatrix() * sum_dt;
jacobian_speedbias_i.block<3, 3>(O_P, O_BA - O_V) = -dp_dba;
jacobian_speedbias_i.block<3, 3>(O_P, O_BG - O_V) = -dp_dbg;
jacobian_speedbias_i.block<3, 3>(O_R, O_BG - O_V) = -Utility::Qleft(Qj.inverse() * Qi * pre_integration_->delta_q).bottomRightCorner<3, 3>() * dq_dbg;
jacobian_speedbias_i.block<3, 3>(O_V, O_V - O_V) = -Qi.inverse().toRotationMatrix();
jacobian_speedbias_i.block<3, 3>(O_V, O_BA - O_V) = -dv_dba;
jacobian_speedbias_i.block<3, 3>(O_V, O_BG - O_V) = -dv_dbg;
jacobian_speedbias_i.block<3, 3>(O_BA, O_BA - O_V) = -Eigen::Matrix3d::Identity();
jacobian_speedbias_i.block<3, 3>(O_BG, O_BG - O_V) = -Eigen::Matrix3d::Identity();
jacobians_[1] = jacobian_speedbias_i;
}
// if (jacobians[2])
{
Eigen::Matrix<double, 15, 6, Eigen::RowMajor> jacobian_pose_j;
jacobian_pose_j.setZero();
jacobian_pose_j.block<3, 3>(O_P, O_P) = Qi.inverse().toRotationMatrix();
Eigen::Quaterniond corrected_delta_q = pre_integration_->delta_q * Utility::deltaQ(dq_dbg * (Bgi - pre_integration_->linearized_bg));
jacobian_pose_j.block<3, 3>(O_R, O_R) = Utility::Qleft(corrected_delta_q.inverse() * Qi.inverse() * Qj).bottomRightCorner<3, 3>();
jacobians_[2] = jacobian_pose_j;
}
// if (jacobians[3])
{
Eigen::Matrix<double, 15, 9, Eigen::RowMajor> jacobian_speedbias_j;
jacobian_speedbias_j.setZero();
jacobian_speedbias_j.block<3, 3>(O_V, O_V - O_V) = Qi.inverse().toRotationMatrix();
jacobian_speedbias_j.block<3, 3>(O_BA, O_BA - O_V) = Eigen::Matrix3d::Identity();
jacobian_speedbias_j.block<3, 3>(O_BG, O_BG - O_V) = Eigen::Matrix3d::Identity();
jacobians_[3] = jacobian_speedbias_j;
}
}
视觉SLAM:VIO的误差和误差雅可比矩阵的更多相关文章
- RGB-D相机视觉SLAM
RGB-D相机视觉SLAM Dense Visual SLAM for RGB-D Cameras 开源代码地址: vision.in.tum.de/data/software/dvo 摘要 本文提 ...
- 转:SLAM算法解析:抓住视觉SLAM难点,了解技术发展大趋势
SLAM(Simultaneous Localization and Mapping)是业界公认视觉领域空间定位技术的前沿方向,中文译名为“同步定位与地图构建”,它主要用于解决机器人在未知环境运动时的 ...
- (转) SLAM系统的研究点介绍 与 Kinect视觉SLAM技术介绍
首页 视界智尚 算法技术 每日技术 来打我呀 注册 SLAM系统的研究点介绍 本文主要谈谈SLAM中的各个研究点,为研究生们(应该是博客的多数读者吧)作一个提纲挈领的摘要.然后,我 ...
- 经典视觉SLAM框架
经典视觉SLAM框架 整个视觉SLAM流程包括以下步骤: 1. 传感器信息读取.在视觉SLAM中主要为相机图像信息的读取和预处理. 2. 视觉里程计(Visual Odometry,VO).视觉里程计 ...
- 高翔《视觉SLAM十四讲》从理论到实践
目录 第1讲 前言:本书讲什么:如何使用本书: 第2讲 初始SLAM:引子-小萝卜的例子:经典视觉SLAM框架:SLAM问题的数学表述:实践-编程基础: 第3讲 三维空间刚体运动 旋转矩阵:实践-Ei ...
- 高博-《视觉SLAM十四讲》
0 讲座 (1)SLAM定义 对比雷达传感器和视觉传感器的优缺点(主要介绍视觉SLAM) 单目:不知道尺度信息 双目:知道尺度信息,但测量范围根据预定的基线相关 RGBD:知道深度信息,但是深度信息对 ...
- 视觉SLAM漫淡
视觉SLAM漫谈 1. 前言 开始做SLAM(机器人同时定位与建图)研究已经近一年了.从一年级开始对这个方向产生兴趣,到现在为止,也算是对这个领域有了大致的了解.然而越了解,越觉得这个方向难度很 ...
- 视觉SLAM中的深度估计问题
一.研究背景 视觉SLAM需要获取世界坐标系中点的深度. 世界坐标系到像素坐标系的转换为(深度即Z): 深度的获取一共分两种方式: a)主动式 RGB-D相机按照原理又分为结构光测距.ToF相机 To ...
- 视觉SLAM漫淡(二):图优化理论与g2o的使用
视觉SLAM漫谈(二):图优化理论与g2o的使用 1 前言以及回顾 各位朋友,自从上一篇<视觉SLAM漫谈>写成以来已经有一段时间了.我收到几位热心读者的邮件.有的希望我介绍一下当前 ...
- 第六篇 视觉slam中的优化问题梳理及雅克比推导
优化问题定义以及求解 通用定义 解决问题的开始一定是定义清楚问题.这里引用g2o的定义. \[ \begin{aligned} \mathbf{F}(\mathbf{x})&=\sum_{k\ ...
随机推荐
- Java-数组工具类Arrays
java.util.Arrays是一个与数组相关的工具类,里面提供了大量静态方法,用来实现数组常见的操作. toSting方法 public static String toString(数组):将参 ...
- YeserCMS
这道题直接让我们查网站根目录的flag,我首先想到的是一句话木马,但是奈何找不到上传的接口啊,只好作罢, 在下载发现有个cmseasy的标识,明显是要提示我们这里是easycms,百度easycms的 ...
- ubuntu 20.04 / 22.04 运行32位程序
sudo dpkg --add-architecture i386 sudo apt install libc6:i386 libstdc++6:i386 sudo apt-get update su ...
- chrome设置socket5代理
利用自带的参数命令打破一个死循环. chrome可执行文件 --show-app-list --proxy-server="SOCKS5://127.0.0.1:1080"
- 【每日一题】【回溯backtrace】N皇后
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案. 每一种解法包含一个不同的 n 皇后问 ...
- org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException: The supplied data appears to be in the OLE2 Format. You are calling the part of POI that deals with OOXML (Office Open XML) Documents
异常:org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException: The supplied data appears to b ...
- 多进程TCP服务端并发- 进程join方法 - IPC机制
目录 同步与异步 阻塞与非阻塞 综合使用 创建进程的多种方式 进程间数据隔离 进程join方法 IPC机制 生产者消费者模型 进程对象的多种方法 僵尸进程与孤儿进程 守护进程 多线程实现TCP服务端并 ...
- CCS选择器 选择器优先级 选择器常见属性
目录 CSS前戏 1.css语法结构 2.css注释语法 3.引入css的多种方式 CSS基本选择器 1.标签选择器 2.类选择器 3.id选择器 4.通用选择器 CSS组合选择器 1.后代选择器(空 ...
- Django中ORM多对多三种创建方式(全自动-纯手动-半自动)
一:多对多三种创建方式 1.全自动: 利用orm自动帮我们创建第三张关系表 class Book(models.Model): name = models.CharField(max_length=3 ...
- Surp Suite入门
BurpSuite代理工具是以拦截代理的方式,拦截所有通过代理的网络流量,如客户端的请求数据.服务器端的返回信息等.Burp Suite主要拦截HTTP和HTTPS 协议的流量,通过拦截,Burp S ...