版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011178262/article/details/88769414
概述
Monocular visual-inertial odometry with relocalization achieved via nonlinear graph optimization-based, tightly-coupled, sliding window, visual-inertial bundle adjustment.
1. 测量预处理
1.1 前端视觉处理
1.2 IMU 预积分
IMU 测量方程
忽略地球旋转,IMU 测量方程为
a^tω^=at+bat+Rwtgw+na=ωt+bωt+nω
预积分方程
(1)IMU integration in world frame
由上面的IMU测量方程积分就可以计算出下一时刻的p、v和q:
(2)IMU integration in the body frame of first pose of interests
为避免重新传播IMU观测值,选用IMU预积分模型,从世界坐标系转为本体坐标系
则 预积分IMU测量模型(估计值)为
⎣⎢⎢⎢⎢⎢⎡α^bk+1bkγ^bk+1bkβ^bk+1bk00⎦⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎡Rwbk(pbk+1w−pbkw+21gwΔt2−vbkwΔt)qbkw−1⊗qbk+1wRwbk(vbk+1w+gwΔt−vbkw)babk+1−babkbwbk+1−bwbk⎦⎥⎥⎥⎥⎤
离散状态下采用 中值法积分 的预积分方程(预积分 测量值)为
δqi+1δαi+1δβi+1bai+1bgi+1=δqi⊗[121wi′δt]=δαi+δβit+0.5ai′δt2=δβi+ai′δt=bai=bgi
其中
wi′ai′=2wi+1+wi−bi=2δqi(ai+na0−bai)+δqi+1(ai+1++na1−bai)
midPointIntegration
中的相关代码:
Vector3d un_gyr = 0.5 * (_gyr_0 + _gyr_1) - linearized_bg;
result_delta_q = delta_q * Quaterniond(1, un_gyr(0) * _dt / 2, un_gyr(1) * _dt / 2, un_gyr(2) * _dt / 2);
Vector3d un_acc_0 = delta_q * (_acc_0 - linearized_ba);
Vector3d un_acc_1 = result_delta_q * (_acc_1 - linearized_ba);
Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
result_delta_p = delta_p + delta_v * _dt + 0.5 * un_acc * _dt * _dt;
result_delta_v = delta_v + un_acc * _dt;
// 预积分的过程中Bias没有发生改变
result_linearized_ba = linearized_ba;
result_linearized_bg = linearized_bg;
误差状态方程
IMU误差状态向量
δX=[δPδvδθδbaδbg]T∈R15×1
根据ESKF中 5.3.3 The error-state kinematics 小节公式
对于 中值积分 下的 误差状态方程 为
δXk˙=⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧δθk˙=δβk˙=δαk˙=δbak˙=δbgk˙=−[2wk+1+wk−bgk]×δθk−δbgk+2nw0+nw1−21qk[ak−bak]×δθ−21qk+1[ak+1−bak]×((I−[2wk+1+wk−bgk]×δt)δθk−δbgkδt+2nw0+nw1δt)−21qkδbak−21qk+1δbak−21qkna0−21qkna1−41qk[ak−bak]×δθδt−41qk+1[ak+1−bak]×((I−[2wk+1+wk−bgk]×δt)δθk−δbgkδt+2nw0+nw1δt)δt−41qkδbakδt−41qk+1δbakδt−41qkna0δt−41qkna1δtnbanbg
简写为
δXk˙=FδXk+Gn
所以
δXk+1=δXk+δXk˙δt=δXk+(FδXk+Gn)δt=(I+Fδt)δXk+(Gδt)n
展开得
⎣⎢⎢⎢⎢⎡δαk+1δθk+1δβk+1δbak+1δbgk+1⎦⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎡I0000f01I−[2wk+1+wk−bwk]×δtf2100δt0I00−41(qk+qk+1)δt20−21(qk+qk+1)δtI0f04−δtf240I⎦⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎡δαkδθkδβkδbakδbgk⎦⎥⎥⎥⎥⎤+⎣⎢⎢⎢⎢⎡41qkδt2021qkδt00v0121δtv210041qk+1δt2021qk+1δt00v0321δtv2300000δt00000δt⎦⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎡na0nw0na1nw1nbanbg⎦⎥⎥⎥⎥⎥⎥⎤
其中
f01f21f04f24v01v03v21v23=−41qk[ak−bak]×δt2−41qk+1[ak+1−bak]×(I−[2wk+1+wk−bgk]×δt)δt2=−21qk[ak−bak]×δt−21qk+1[ak+1−bak]×(I−[2wk+1+wk−bgk]×δt)δt=41(−qk+1[ak+1−bak]×δt2)(−δt)=21(−qk+1[ak+1−bak]×δt)(−δt)=41(−qk+1[ak+1−bak]×δt2)21δt=41(−qk+1[ak+1−bak]×δt2)21δt=21(−qk+1[ak+1−bak]×δt2)21δt=21(−qk+1[ak+1−bak]×δt2)21δt
令
F′V=I+Fδt=Gδt∈R15×15∈R15×18
则简写为
δXk+1=F′δXk+Vn
此处
F′ 即代码中
F,相关代码见 midPointIntegration
。
最后得到 IMU预积分测量关于IMU Bias 的 雅克比矩阵
Jk+1 、IMU预积分测量的 协方差矩阵
Pk+1 和 噪声的 协方差矩阵
Q,初始状态下的雅克比矩阵和协方差矩阵为 单位阵 和 零矩阵
JbkPbkbkJt+δtPt+δtbkQ=I=0=F′Jt=(I+Ftδt)Jt,t∈[k,k+1]=F′PtbkF′T+VQVT=(I+Ftδt)Ptbk(I+Ftδt)+(Gtδt)Q(Gtδt)=diag(σa02σω02σa12σω12σba2σbg2)∈R18×18
当bias估计轻微改变时,我们可以使用如下的一阶近似 对中值积分得到的预积分测量值进行矫正,而不重传播,从而得到 更加精确的预积分测量值(bias修正的线性模型)
αbk+1bkβbk+1bkγbk+1bk≈α^bk+1bk+Jbaαδbak+Jbωαδbωk≈β^bk+1bk+Jbaβδbak+Jbωβδbωk≈γ^bk+1bk⊗[121Jbωγδbωk]
2. 初始化(松耦合)
在提取的图像的Features和做完IMU的预积分之后,进入了系统的初始化环节,主要的目的有以下两个:
- 系统使用单目相机,如果没有一个良好的尺度估计,就无法对两个传感器做进一步的融合,这个时候需要恢复出尺度;
- 要对IMU进行初始化,IMU会受到bias的影响,所以要得到IMU的bias。
所以我们要从初始化中恢复出尺度、重力、速度以及IMU的bias,因为视觉(SFM)在初始化的过程中有着较好的表现,所以在初始化的过程中主要以SFM为主,然后将IMU的预积分结果与其对齐,即可得到较好的初始化结果。
2.1 相机与IMU之间的相对旋转
相机与IMU之间的旋转标定非常重要,偏差1-2°系统的精度就会变的极低。
设相机利用对极关系得到的旋转矩阵为
Rck+1ck ,IMU经过预积分得到的旋转矩阵为
Rbk+1bk,相机与IMU之间的相对旋转为
Rcb,则对于任一帧满足,
Rbk+1bkRcb=RcbRck+1ck
将旋转矩阵写为四元数,则上式可以写为
qbk+1bk⊗qcb=qcb⊗qck+1ck
将其写为左乘和右乘的形式
([qbk+1bk]L−[qck+1ck]R)qcb=Qk+1kqcb=0
[q]L 与
[q]R 分别表示 四元数左乘矩阵 和 四元数右乘矩阵,其定义为(四元数实部在后)
[q]L[q]R=[qwI3+[qxyz]×−qxyzqxyzqw]=[qwI3−[qxyz]×−qxyzqxyzqw]
那么对于
n对测量值,则有
⎣⎢⎢⎢⎡w10Q10w21Q21⋮wNN−1QNN−1⎦⎥⎥⎥⎤qcb=QNqcb=0
其中
wNN−1 为外点剔除权重,其与相对旋转求得的角度残差有关,
N为计算相对旋转需要的测量对数,其由最终的终止条件决定。角度残差可以写为,
θk+1k=arccos(2tr(R^cb−1Rbk+1bk−1R^cbRck+1ck)−1)
从而权重为
wk+1k={1,θk+1kthreshold,θk+1k<threshold(一般5°)otherwise
至此,就可以通过求解方程
QNqcb=0 得到相对旋转,解为
QN 的左奇异向量中最小奇异值对应的特征向量。
但是,在这里还要注意 求解的终止条件(校准完成的终止条件) 。在足够多的旋转运动中,我们可以很好的估计出相对旋转
Rcb,这时
QN 对应一个准确解,且其零空间的秩为1。但是在校准的过程中,某些轴向上可能存在退化运动(如匀速运动),这时
QN 的零空间的秩会大于1。判断条件就是
QN 的第二小的奇异值是否大于某个阈值,若大于则其零空间的秩为1,反之秩大于1,相对旋转
Rcb 的精度不够,校准不成功。
对应代码在 InitialEXRotation::CalibrationExRotation
中。
// 相机与IMU之间的相对旋转
if(ESTIMATE_EXTRINSIC == 2)
{
ROS_INFO("calibrating extrinsic param, rotation movement is needed");
if (frame_count != 0)
{
// 选取两帧之间共有的Features
vector<pair<Vector3d, Vector3d>> corres = f_manager.getCorresponding(frame_count - 1, frame_count);
// 校准相机与IMU之间的旋转
Matrix3d calib_ric;
if (initial_ex_rotation.CalibrationExRotation(corres, pre_integrations[frame_count]->delta_q, calib_ric))
{
ROS_WARN("initial extrinsic rotation calib success");
ROS_WARN_STREAM("initial extrinsic rotation: " << endl << calib_ric);
ric[0] = calib_ric;
RIC[0] = calib_ric;
ESTIMATE_EXTRINSIC = 1;
}
}
}
2.2 检测IMU可观性
// 计算均值
map<double, ImageFrame>::iterator frame_it;
Vector3d sum_g;
for (frame_it = all_image_frame.begin(), frame_it++; frame_it != all_image_frame.end(); frame_it++)
{
double sum_dt = frame_it->second.pre_integration->sum_dt;
Vector3d tmp_g = frame_it->second.pre_integration->delta_v / sum_dt;
sum_g += tmp_g;
}
Vector3d aver_g = sum_g * 1.0 / ((int)all_image_frame.size() - 1);
// 计算方差
double var = 0;
for (frame_it = all_image_frame.begin(), frame_it++; frame_it != all_image_frame.end(); frame_it++)
{
double sum_dt = frame_it->second.pre_integration->sum_dt;
Vector3d tmp_g = frame_it->second.pre_integration->delta_v / sum_dt;
var += (tmp_g - aver_g).transpose() * (tmp_g - aver_g);
}
// 计算标准差
var = sqrt(var / ((int)all_image_frame.size() - 1));
//ROS_WARN("IMU variation %f!", var);
if(var < 0.25) //! 以标准差判断可观性
{
ROS_INFO("IMU excitation not enouth!");
//return false;
}
2.3 相机初始化(Vision-Only SFM)
- 求取本质矩阵求解位姿(
relativePose
)
- 三角化特征点(
sfm.construct
)
- PnP求解位姿(
cv::solvePnP
)
- 转换到IMU坐标系下
-
c0 坐标系作为参考系
- 不断重复直到恢复出滑窗内的Features和相机位姿
2.4 视觉与IMU对齐
- Gyroscope Bias Calibration
- Velocity, Gravity Vector and Metric Scale Initialization
- Gravity Refinement
- Completing Initialization
对应代码:VisualIMUAlignment
陀螺仪Bias标定
标定陀螺仪Bias使用如下代价函数
δbwmink∈B∑∥∥∥qbk+1c0−1⊗qbkc0⊗γbk+1bk∥∥∥2
因为四元数最小值为单位四元数
[1,0v]T,所以令
qbk+1c0−1⊗qbkc0⊗γbk+1bk=[10]
其中
γbk+1bk≈γ^bk+1bk⊗[121Jbwγδbw]
所以
γ^bk+1bk⊗[121Jbwγδbw]=qbkc0−1⊗qbk+1c0
[121Jbwγδbw]=γ^bk+1bk−1⊗qbkc0−1⊗qbk+1c0
只取上式虚部,再进行最小二乘求解
JbwγTJbwγδbw=2⋅JbwγT(γ^bk+1bk−1⊗qbkc0−1⊗qbk+1c0)vec
求解上式的最小二乘解,即可得到
δbw,注意这个地方得到的只是Bias的变化量,需要在滑窗内累加得到Bias的准确值。
对应代码:solveGyroscopeBias
void solveGyroscopeBias(map<double, ImageFrame> &all_image_frame, Vector3d* Bgs) {
Matrix3d A;
Vector3d b;
A.setZero();
b.setZero();
map<double, ImageFrame>::iterator frame_i;
map<double, ImageFrame>::iterator frame_j;
for (frame_i = all_image_frame.begin(); next(frame_i) != all_image_frame.end(); frame_i++) {
frame_j = next(frame_i);
MatrixXd tmp_A(3, 3);
VectorXd tmp_b(3);
tmp_A.setZero();
tmp_b.setZero();
Eigen::Quaterniond q_ij(frame_i->second.R.transpose() * frame_j->second.R);
tmp_A = frame_j->second.pre_integration->jacobian.template block<3, 3>(O_R, O_BG);
tmp_b = 2 * (frame_j->second.pre_integration->delta_q.inverse() * q_ij).vec();
A += tmp_A.transpose() * tmp_A;
b += tmp_A.transpose() * tmp_b;
}
Vector3d delta_bg = A.ldlt().solve(b);
ROS_WARN_STREAM("gyroscope bias initial calibration " << delta_bg.transpose());
// 因为求解出的Bias是变化量,所以要累加
for (int i = 0; i <= WINDOW_SIZE; i++)
Bgs[i] += delta_bg;
// 利用新的Bias重新repropagate
for (frame_i = all_image_frame.begin(); next(frame_i) != all_image_frame.end(); frame_i++) {
frame_j = next(frame_i);
frame_j->second.pre_integration->repropagate(Vector3d::Zero(), Bgs[0]);
}
}
初始化速度、重力向量和尺度因子
要估计的状态量
XI=[vb0b0,vb1b0,⋯,vbnbn,gc0,s]∈R3(n+1)+3+1
其中,
gc0 为在第 0 帧 Camera 相机坐标系下的重力向量。
根据IMU测量模型可知
αbk+1bkβbk+1bk=Rc0bk(s(pˉbk+1c0−pˉbkc0)+21gc0Δtk2−Rbkc0vbkbkΔtk)=Rc0bk(Rbk+1c0vbk+1bk+1+gc0Δtk−Rbkc0vbkbk)
我们已经得到了IMU相对于相机的旋转
qbc,假设IMU到相机的平移量
pbc,那么可以很容易地将相机坐标系下的位姿转换到IMU坐标系下
qbkc0spˉbkc0=qckc0⊗(qcb)−1=spˉckc0−Rbkc0pcb
所以,定义相邻两帧之间的IMU预积分出的增量(
α^bk+1bk,
β^bk+1bk)与预测值之间的残差,即
r(z^bk+1bk,XI)=[δαbk+1bkδβbk+1bk]=[α^bk+1bk−β^bk+1bk−Rc0bk(s(pˉbk+1c0−pˉbkc0)+21gc0Δtk2−Rbkc0vbkbkΔtk)Rc0bk(Rbk+1c0vbk+1bk+1+gc0Δtk−Rbkc0vbkbk)]
令
r(z^bk+1bk,XI)=0,转换成
Hx=b 的形式
[−IΔtk−I0Rc0bkRbk+1c021Rc0bkΔtk2Rc0bkΔtkRc0bk(pˉck+1c0−pˉckc0)0]⎣⎢⎢⎡vbkbkvbk+1bk+1gc0s⎦⎥⎥⎤=[αbk+1bk−pcb+Rc0bkRbk+1c0pcbβbk+1bk]
通过Cholosky分解求解
XI
HTHXI=HTb
对应代码:LinearAlignment
优化重力
重力矢量的模长固定(9.8),其为2个自由度,在切空间上对其参数化
g^=∥g∥⋅g^ˉ+ω1b1
+ω2b2
=∥g∥⋅g^ˉ+Bω
,B∈R3×2,ω
∈R2×1
令
g^=gc0,将其代入上一小节公式得
[−IΔtk−I0Rc0bkRbk+1c021Rc0bkΔtk2BRc0bkΔtkBRc0bk(pˉck+1c0−pˉckc0)0]⎣⎢⎢⎡vbkbkvbk+1bk+1ω
s⎦⎥⎥⎤=[αbk+1bk−pcb+Rc0bkRbk+1c0pcb−21Rc0bkΔtk2∥g∥⋅g^ˉβbk+1bk−Rc0bkΔtk∥g∥⋅g^ˉ]
同样,通过Cholosky分解求得
gc0,即相机
C0 系下的重力向量。
最后,通过将
gc0 旋转至惯性坐标系(世界系)中的 z 轴方向[0,0,1],可以计算第一帧相机系到惯性系的旋转矩阵
qc0w,这样就可以将所有变量调整至惯性世界系中。
对应代码:RefineGravity
3. 后端优化(紧耦合)
VIO 紧耦合方案的主要思路就是通过将基于视觉构造的残差项和基于IMU构造的残差项放在一起构造成一个联合优化的问题,整个优化问题的最优解即可认为是比较准确的状态估计。
为了限制优化变量的数目,VINS-Mono 采用了滑动窗口的形式,滑动窗口 中的 全状态量:
Xxkxcb=[x0,x1,⋯,xn,xcb,λ0,λ1,⋯,λm]=[pbkw,vbkw,qbkw,ba,bg],k∈[0,n]=[pcb,qcb]
- 滑动窗口内 n+1 个所有相机的状态(包括位置、朝向、速度、加速度计 bias 和陀螺仪 bias)
- Camera 到 IMU 的外参
- m+1 个 3D 点的逆深度
优化过程中的 误差状态量
δXδxkδxcb=[δx0,δx1,⋯,δxn,δxcb,λ0,δλ1,⋯,δλm]=[δpbkw,δvbkw,δθbkw,δba,δbg],k∈[0,n]=[δpcb,δqcb]
进而得到系统优化的代价函数(Minimize residuals from all sensors)
Xmin{∥rp−HpX∥2+∑k∈B∥∥∥rB(z^bk+1bk,X)∥∥∥Pbk+1bk2+∑(i,j)∈C∥∥rC(z^lcj,X)∥∥Plcj2}
其中三个残差项依次是
三种残差都是用 马氏距离(与量纲无关) 来表示的。
Motion-only visual-inertial bundle adjustment: Optimize position, velocity, rotation in a smaller windows, assuming all other quantities are fixed
3.1 IMU 测量残差
(1)IMU 测量残差
上面的IMU预积分(估计值 - 测量值),得到IMU测量残差
rB(z^bk+1bk,X)=⎣⎢⎢⎢⎢⎢⎡δαbk+1bkδθbk+1bkδβbk+1bk00⎦⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎡Rwbk(pbk+1w−pbkw+21gwΔt2−vbkwΔt)−α^bk+1bk2[qbkw−1⊗qbk+1w⊗(γ^bk+1bk)−1]xyzRwbk(vbk+1w+gwΔt−vbkw)−β^bk+1bkbabk+1−babkbgbk+1−bgbk⎦⎥⎥⎥⎥⎥⎤
其中
[α^bk+1bk,γ^bk+1bk,β^bk+1bk] 为 IMU预积分Bias修正值。
/**
* [evaluate 计算IMU测量模型的残差]
* @param Pi,Qi,Vi,Bai,Bgi [前一次预积分结果]
* @param Pj,Qj,Vj,Baj,Bgj [后一次预积分结果]
*/
Eigen::Matrix<double, 15, 1> evaluate(
const Eigen::Vector3d &Pi, const Eigen::Quaterniond &Qi, const Eigen::Vector3d &Vi, const Eigen::Vector3d &Bai, const Eigen::Vector3d &Bgi,
const Eigen::Vector3d &Pj, const Eigen::Quaterniond &Qj, const Eigen::Vector3d &Vj, const Eigen::Vector3d &Baj, const Eigen::Vector3d &Bgj)
{
Eigen::Matrix<double, 15, 1> residuals;
Eigen::Matrix3d dp_dba = jacobian.block<3, 3>(O_P, O_BA);
Eigen::Matrix3d dp_dbg = jacobian.block<3, 3>(O_P, O_BG);
Eigen::Matrix3d dq_dbg = jacobian.block<3, 3>(O_R, O_BG);
Eigen::Matrix3d dv_dba = jacobian.block<3, 3>(O_V, O_BA);
Eigen::Matrix3d dv_dbg = jacobian.block<3, 3>(O_V, O_BG);
Eigen::Vector3d dba = Bai - linearized_ba;
Eigen::Vector3d dbg = Bgi - linearized_bg;
// IMU预积分的结果,消除掉acc bias和gyro bias的影响, 对应IMU model中的\hat{\alpha},\hat{\beta},\hat{\gamma}
Eigen::Quaterniond corrected_delta_q = delta_q * Utility::deltaQ(dq_dbg * dbg);
Eigen::Vector3d corrected_delta_v = delta_v + dv_dba * dba + dv_dbg * dbg;
Eigen::Vector3d corrected_delta_p = delta_p + dp_dba * dba + dp_dbg * dbg;
// IMU项residual计算,输入参数是状态的估计值, 上面correct_delta_*是预积分值, 二者求'diff'得到residual
residuals.block<3, 1>(O_P, 0) = Qi.inverse() * (0.5 * G * sum_dt * sum_dt + Pj - Pi - Vi * sum_dt) - corrected_delta_p;
residuals.block<3, 1>(O_R, 0) = 2 * (corrected_delta_q.inverse() * (Qi.inverse() * Qj)).vec();
residuals.block<3, 1>(O_V, 0) = Qi.inverse() * (G * sum_dt + Vj - Vi) - corrected_delta_v;
residuals.block<3, 1>(O_BA, 0) = Baj - Bai;
residuals.block<3, 1>(O_BG, 0) = Bgj - Bgi;
return residuals;
}
(2)协方差矩阵
此处用到的协方差矩阵为前面IMU预积分计算出的协方差矩阵。
残差的后处理对应代码:
// 在优化迭代的过程中, 预积分值是不变的, 输入的状态值会被不断的更新, 然后不断的调用evaluate()计算更新后的IMU残差
Eigen::Map<Eigen::Matrix<double, 15, 1>> residual(residuals);
residual = pre_integration->evaluate(Pi, Qi, Vi, Bai, Bgi,
Pj, Qj, Vj, Baj, Bgj);
Eigen::Matrix<double, 15, 15> sqrt_info =
Eigen::LLT<Eigen::Matrix<double, 15, 15>>(pre_integration->covariance.inverse()).matrixL().transpose();
//sqrt_info.setIdentity();
residual = sqrt_info * residual; // 为了保证 IMU 和 视觉參差项在尺度上保持一致,一般会采用与量纲无关的马氏距离
这里残差 residual 乘以 sqrt_info,这是因为真正的优化项其实是 Mahalanobis 距离:
d=rTP−1r,其中
P 是协方差。Mahalanobis距离 其实相当于一个残差加权,协方差大的加权小,协方差小的加权大,着重优化那些比较确定的残差。
而 ceres只接受最小二乘优化,也就是
mineTe,所以把
P−1 做 LLT分解,即
LLT=P−1,则
d=rT(LLT)r=(LTr)T(LTr),令
r′=(LTr),作为新的优化误差,所以 sqrt_info 等于
LT。
(3)雅克比矩阵
高斯迭代优化过程中会用到IMU测量残差对状态量的雅克比矩阵,但此处我们是 对误差状态量求偏导,下面对四部分误差状态量求取雅克比矩阵。
对
[δpbkw,δθbkw] 求偏导得
J[0]=⎣⎢⎢⎡−qwbk000Rwbk[(pbk+1w−pbkw+21gwΔt2−vbkwΔt)]×[qbk+1w−1qbkw]L[γ^bk+1bk]RJbwγRwbk[(vbk+1w+gwΔt−vbkw)]×0⎦⎥⎥⎤∈R15×7
对
[δvbkw,δbabk,δbwbk] 求偏导得
J[1]=⎣⎢⎢⎢⎢⎡−qwbkΔt0−qwbk00−Jbaα0−Jbaβ−I0−Jbaα−[qbk+1w−1⊗qbkw⊗γ^bk+1bk]LJbwγ−Jbaβ0−I⎦⎥⎥⎥⎥⎤∈R15×9
对
[δpbk+1w,δθbk+1w] 求偏导得
J[2]=⎣⎢⎢⎢⎢⎡−qwbk00000[γ^bk+1bk−1⊗qwbk⊗qbk+1w]L000⎦⎥⎥⎥⎥⎤∈R15×7
对
[δvbkw,δbabk,δbwbk] 求偏导得
J[3]=⎣⎢⎢⎢⎢⎡−qwbk0qwbk00000I00000I⎦⎥⎥⎥⎥⎤∈R15×9
雅克比矩阵计算的对应代码在 class IMUFactor : public ceres::SizedCostFunction<15, 7, 9, 7, 9>
中的 Evaluate()
函数中。
3.2 视觉(td) 测量残差
视觉测量残差 即 特征点的重投影误差,视觉残差和雅克比矩阵计算的对应代码在 ProjectionFactor::Evaluate
函数中。
(1)切平面重投影误差(Spherical camera model)
rC=(z^lcj,X)=[b1,b2]T⋅(Pˉlcj−∥∥Plcj∥∥Plcj)
其中,
Plcj=qbc(qwbj(qbiw(qcbλlPˉlci+pcb)+pbiw−pbjw)−pcb)
// 将第i frame下的3D点转到第j frame坐标系下
Eigen::Vector3d pts_camera_i = pts_i / inv_dep_i; // pt in ith camera frame, 归一化平面
Eigen::Vector3d pts_imu_i = qic * pts_camera_i + tic; // pt in ith body frame
Eigen::Vector3d pts_w = Qi * pts_imu_i + Pi; // pt in world frame
Eigen::Vector3d pts_imu_j = Qj.inverse() * (pts_w - Pj); // pt in jth body frame
Eigen::Vector3d pts_camera_j = qic.inverse() * (pts_imu_j - tic); // pt in jth camera frame
(2)像素重投影误差(Pinhole camera model)
rC=(z^lcj,X)=(1.5f⋅I2×2)⋅(ZˉPˉlcj−ZjPlcj)2
Eigen::Map<Eigen::Vector2d> residual(residuals);
#ifdef UNIT_SPHERE_ERROR
// 把归一化平面上的重投影误差投影到Unit sphere上的好处就是可以支持所有类型的相机 why
// 求取切平面上的误差
residual = tangent_base * (pts_camera_j.normalized() - pts_j.normalized());
#else
// 求取归一化平面上的误差
double dep_j = pts_camera_j.z();
residual = (pts_camera_j / dep_j).head<2>() - pts_j.head<2>();
#endif
residual = sqrt_info * residual; // 转成 与量纲无关的马氏距离
(3)协方差矩阵
固定的协方差矩阵,归一化平面的标准差为
f1.5,即像素标准差为
1.5
ProjectionFactor::sqrt_info = FOCAL_LENGTH / 1.5 * Matrix2d::Identity();
(4)雅克比矩阵
下面关于误差状态量对相机测量残差求偏导,得到高斯迭代优化过程中的雅克比矩阵。
对
[δpbiw,δθbiw] 求偏导
J[0]=[qbcqwbj−qbcqwbjqbiw[qcbλlPˉlci+pcb]×]∈R3×6
对
[δpbjw,δθbjw] 求偏导
J[1]=[−qbcqwbjqbcqwbj[qbiw(qcbλlPˉlci+pcb)+pbiw−pbjw]×]∈R3×6
对
[δpcb,δθcb] 求偏导
J[2]=[qbc(qwbjqbiw−I3∗3)−qbcqwbjqbiwqcb[λlPˉlci]×+[qbc(qwbj(qbiwpcb+pbiw−pbjw)−pcb)]]∈R3×6
对
δλl 求偏导
J[3]=−qbcqwbjqbiwqcbλl2Pˉlci∈R3×1
(5)Vision measurement residual for temporal calibration
视觉残差和雅克比矩阵计算的对应代码在 ProjectionTdFactor::Evaluate
函数中。
// TR / ROW * row_i 是相机 rolling 到这一行时所用的时间
Eigen::Vector3d pts_i_td, pts_j_td;
pts_i_td = pts_i - (td - td_i + TR / ROW * row_i) * velocity_i;
pts_j_td = pts_j - (td - td_j + TR / ROW * row_j) * velocity_j;
Eigen::Vector3d pts_camera_i = pts_i_td / inv_dep_i;
Eigen::Vector3d pts_imu_i = qic * pts_camera_i + tic;
Eigen::Vector3d pts_w = Qi * pts_imu_i + Pi;
Eigen::Vector3d pts_imu_j = Qj.inverse() * (pts_w - Pj);
Eigen::Vector3d pts_camera_j = qic.inverse() * (pts_imu_j - tic);
Eigen::Map<Eigen::Vector2d> residual(residuals);
#ifdef UNIT_SPHERE_ERROR
residual = tangent_base * (pts_camera_j.normalized() - pts_j_td.normalized());
#else
double dep_j = pts_camera_j.z();
residual = (pts_camera_j / dep_j).head<2>() - pts_j_td.head<2>();
#endif
residual = sqrt_info * residual;
- 添加对 imu-camera 时间戳不完全同步和 Rolling Shutter 相机的支持:通过前端光流计算得到每个角点在归一化的速度,根据 imu-camera 时间戳的时间同步误差和Rolling Shutter相机做一次rolling的时间,对角点的归一化坐标进行调整
3.3 Temporal Calibration
Timestamps
Time Synchronization
Temporal Calibration
- calibrate the fixed latency
td occurred during time stamping
- change the IMU pre-integration interval to the interval between two image timestamps
- linear incorporation of IMU measurements to obtain the IMU reading at image time stamping
- estimates states(position, orientation, etc.) at image time stamping
3.4 边缘化(Marginalization)
SLAM is tracking a noraml distribution through a large state space
滑窗(Sliding Window) 限制了关键帧的数量,防止pose和feature的个数不会随时间不断增加,使得优化问题始终在一个有限的复杂度内,不会随时间不断增长。
Marginalization
然而,将pose移出windows时,有些约束会被丢弃掉,这样势必会导致求解的精度下降,而且当MAV进行一些退化运动(如: 匀速运动)时,没有历史信息做约束的话是无法求解的。所以,在移出位姿或特征的时候,需要将相关联的约束转变成一个约束项作为prior放到优化问题中,这就是marginalization要做的事情。
边缘化的过程就是将滑窗内的某些较旧或者不满足要求的视觉帧剔除的过程,所以边缘化也被描述为 将联合概率分布分解为边缘概率分布和条件概率分布的过程(就是利用shur补减少优化参数的过程)。
直接进行边缘化而不加入先验条件的后果:
-
无故地移除这些pose和feature会丢弃帧间约束,会降低了优化器的精度,所以在移除pose和feature的时候需要将相关联的约束转变为一个先验的约束条件作为prior放到优化问题中
-
在边缘化的过程中,不加先验的边缘化会导致系统尺度的缺失(参考[6]),尤其是系统在进行退化运动时(如无人机的悬停和恒速运动)。一般来说 只有两个轴向的加速度不为0的时候,才能保证尺度可观,而退化运动对于无人机或者机器人来说是不可避免的。所以在系统处于退化运动的时候,要加入先验信息保证尺度的可观性
VINS-Mono中为了处理一些悬停的case,引入了一个two-way marginalization:
-
MARGIN_OLD:如果次新帧是关键帧,则丢弃滑动窗口内最老的图像帧,同时对与该图像帧关联的约束项进行边缘化处理。这里需要注意的是,如果该关键帧是观察到某个地图点的第一帧,则需要把该地图点的深度转移到后面的图像帧中去。
-
MARGIN_NEW:如果次新帧不是关键帧,则丢弃当前帧的前一帧。因为判定当前帧不是关键帧的条件就是当前帧与前一帧视差很小,也就是说当前帧和前一帧很相似,这种情况下直接丢弃前一帧,然后用当前帧代替前一帧。为什么这里可以不对前一帧进行边缘化,而是直接丢弃,原因就是当前帧和前一帧很相似,因此当前帧与地图点之间的约束和前一帧与地图点之间的约束是很接近的,直接丢弃并不会造成整个约束关系丢失信息。这里需要注意的是,要把当前帧和前一帧之间的 IMU 预积分转换为当前帧和前二帧之间的 IMU 预积分。
在悬停等运动较小的情况下,会频繁的MARGIN_NEW,这样也就保留了那些比较旧但是视差比较大的pose。这种情况如果一直MARGIN_OLD的话,视觉约束不够强,状态估计会受IMU积分误差影响,具有较大的累积误差。
Schur Complement
- Marginalization via Schur complement on information matrix
First Estimate Jacobin
4. 重定位
4.1 Loop Detection
Vins-Mono利用 词袋 DBoW2 做Keyframe Database的构建和查询。在建立闭环检测的数据库时,关键帧的Features包括两部分:VIO部分的200个强角点 和 500个Fast角点,然后描述子使用 BRIEF (因为旋转可观,匹配过程中对旋转有一定的适应性,所以不用使用ORB)。
- Describe features by BRIEF
- Features that we use in the VIO (200, not enough for loop detection)
- Extract new FAST features (500, only use for loop detection)
- Query Bag-of-Word (DBoW2)
4.2 Feature Retrieval
在闭环检测成功之后,会得到回环候选帧,所以要在已知位姿的回环候选帧和滑窗内的匹配帧通过 BRIEF描述子匹配,然后把回环帧加入到滑窗的优化当中,这时整个滑窗的状态量的维度是不发生变化的,因为回环帧的位姿是固定的。
- Try to retrieve matches for features (200) that are used in the VIO
- BRIEF descriptor match
- Geometric check
- 2D-2D: fundamental matrix test with RANSAC
- 3D-3D: PnP test with RANSAC
- At least 30 inliers
4.3 Tightly-Coupled Relocalization
5. 全局位姿图优化
因为之前做的非线性优化本质只是在一个滑窗之内求解出了相机的位姿,而且在回环检测部分,利用固定位姿的回环帧只是纠正了滑窗内的相机位姿,并没有修正其他位姿(或者说没有将回环发现的误差分配到整个相机的轨迹上),缺少全局的一致性,所以要做一次全局的Pose Graph。全局的Pose Graph较之滑窗有一定的迟滞性,只有相机的Pose滑出滑窗的时候,Pose才会被加到全局的Pose Graph当中。
(1) Adding Keyframes into the Pose Graph
- Sequential edges from VIO
- Connected with 4 previous keyframes
- Loop closure edges
- Only added when a keyframe is marginalized out from the sliding window VIO
- Multi-constraint relocalization helps eliminating false loop closures
- Huber norm for rejection of wrong loops
(2) 4-DOF Pose Graph Optimization
- Roll and pitch are observable from VIO
(3) Pose Graph Management
(4) Map Reuse
- Save map at any time
- Load map and re-localize with respect to it
- Pose graph merging
6. Remarks on Monocular Visual-Inertial SLAM
- Important factors
- Access to raw camera data (especially for rolling shutter cameras)
- Sensor synchronization and timestamps
- Camera-IMU rotation
- Estimator initialization
- Not-so-important factors
- Camera-IMU translation
- Types of features (we use the simplest corner+KLT)
- Quality of feature tracking (outlier is acceptable)
- Failures – need more engineering treatment
- Long range scenes (aerial vehicles)
- Constant velocity (ground vehicle)
- Pure rotation (augmented reality)
- Be aware of computational power requirement
参考文献
- [1] VINS-Mono: A Robust and Versatile Monocular Visual-Inertial State Estimator
- [2] Shaojie Shen, Monocular Visual-Inertial SLAM slides, 2018
- [3] Quaternion kinematics for the error-state Kalman filter
- [4] Xiaobuyi, VINS-Mono代码分析总结