本节开始讲解预积分知识,先从一个SE3推导开始吧,这里只展示结论跟代码,预积分相关理论实际上已经比较成熟,此处就不作冗余的推导了,借用下图用以开始

相信看过我前面讲解的博客的应该都能理解预积是干什么的的,这里简单图示下

上图所说的,实际上预计分因子就是关联两个关键帧的约束,其也是一种观测,接下来我们来看预积分参与优化的流程
完整版代码
https://gitee.com/zl_vslam/slam_optimizer/tree/master/multi_sensor_slam
一. 预积分测量模型
预积分代码及公式展示
void IMUPreintegration::Integrate(const IMU &imu, double dt) {
// 去掉零偏的测量
Vec3d gyr = imu.gyro_ - bg_; // 陀螺
Vec3d acc = imu.acce_ - ba_; // 加计
// 更新dv, dp, 见(4.13), (4.16)
dp_ = dp_ + dv_ * dt + 0.5f * dR_.matrix() * acc * dt * dt;
dv_ = dv_ + dR_ * acc * dt;
// dR先不更新,因为A, B阵还需要现在的dR
// 运动方程雅可比矩阵系数,A,B阵,见(4.29)
// 另外两项在后面
Eigen::Matrix<double, 9, 9> A;
A.setIdentity();
Eigen::Matrix<double, 9, 6> B;
B.setZero();
Mat3d acc_hat = SO3::hat(acc);
double dt2 = dt * dt;
// NOTE A, B左上角块与公式稍有不同
A.block<3, 3>(3, 0) = -dR_.matrix() * dt * acc_hat;
A.block<3, 3>(6, 0) = -0.5f * dR_.matrix() * acc_hat * dt2;
A.block<3, 3>(6, 3) = dt * Mat3d::Identity();
B.block<3, 3>(3, 3) = dR_.matrix() * dt;
B.block<3, 3>(6, 3) = 0.5f * dR_.matrix() * dt2;
// 更新各雅可比,见式(4.39)
dP_dba_ = dP_dba_ + dV_dba_ * dt - 0.5f * dR_.matrix() * dt2; // (4.39d)
dP_dbg_ = dP_dbg_ + dV_dbg_ * dt - 0.5f * dR_.matrix() * dt2 * acc_hat * dR_dbg_; // (4.39e)
dV_dba_ = dV_dba_ - dR_.matrix() * dt; // (4.39b)
dV_dbg_ = dV_dbg_ - dR_.matrix() * dt * acc_hat * dR_dbg_; // (4.39c)
// 旋转部分
Vec3d omega = gyr * dt; // 转动量
Mat3d rightJ = SO3::jr(omega); // 右雅可比
SO3 deltaR = SO3::exp(omega); // exp后
dR_ = dR_ * deltaR; // (4.9)
A.block<3, 3>(0, 0) = deltaR.matrix().transpose();
B.block<3, 3>(0, 0) = rightJ * dt;
// 更新噪声项
cov_ = A * cov_ * A.transpose() + B * noise_gyro_acce_ * B.transpose();
// 更新dR_dbg
dR_dbg_ = deltaR.matrix().transpose() * dR_dbg_ - rightJ * dt; // (4.39a)
// 增量积分时间
dt_ += dt;
}
各个公式展示

预积分速度观测量为:

预积分速度观测量为:

预积分噪声模型:


零偏的更新

二. IMU预积分
代码展示
NavStated IMUPreintegration::Predict(const sad::NavStated &start, const Vec3d &grav) const {
SO3 Rj = start.R_ * dR_;
Vec3d vj = start.R_ * dv_ + start.v_ + grav * dt_;
Vec3d pj = start.R_ * dp_ + start.p_ + start.v_ * dt_ + 0.5f * grav * dt_ * dt_;
auto state = NavStated(start.timestamp_ + dt_, Rj, pj, vj);
state.bg_ = bg_;
state.ba_ = ba_;
return state;
}
相应公式如下

三. 预积分残差
代码展示
void EdgeInertial::computeError() {
auto* p1 = dynamic_cast<const VertexPose*>(_vertices[0]);
auto* v1 = dynamic_cast<const VertexVelocity*>(_vertices[1]);
auto* bg1 = dynamic_cast<const VertexGyroBias*>(_vertices[2]);
auto* ba1 = dynamic_cast<const VertexAccBias*>(_vertices[3]);
auto* p2 = dynamic_cast<const VertexPose*>(_vertices[4]);
auto* v2 = dynamic_cast<const VertexVelocity*>(_vertices[5]);
Vec3d bg = bg1->estimate();
Vec3d ba = ba1->estimate();
const SO3 dR = preint_->GetDeltaRotation(bg);
const Vec3d dv = preint_->GetDeltaVelocity(bg, ba);
const Vec3d dp = preint_->GetDeltaPosition(bg, ba);
/// 预积分误差项(4.41)
const Vec3d er = (dR.inverse() * p1->estimate().so3().inverse() * p2->estimate().so3()).log();
Mat3d RiT = p1->estimate().so3().inverse().matrix();
const Vec3d ev = RiT * (v2->estimate() - v1->estimate() - grav_ * dt_) - dv;
const Vec3d ep = RiT * (p2->estimate().translation() - p1->estimate().translation() - v1->estimate() * dt_ -
grav_ * dt_ * dt_ / 2) -
dp;
_error << er, ev, ep;
}
相应公式

四. 预积分残差
代码展示
void EdgeInertial::linearizeOplus() {
auto* p1 = dynamic_cast<const VertexPose*>(_vertices[0]);
auto* v1 = dynamic_cast<const VertexVelocity*>(_vertices[1]);
auto* bg1 = dynamic_cast<const VertexGyroBias*>(_vertices[2]);
auto* ba1 = dynamic_cast<const VertexAccBias*>(_vertices[3]);
auto* p2 = dynamic_cast<const VertexPose*>(_vertices[4]);
auto* v2 = dynamic_cast<const VertexVelocity*>(_vertices[5]);
Vec3d bg = bg1->estimate();
Vec3d ba = ba1->estimate();
Vec3d dbg = bg - preint_->bg_;
// 一些中间符号
const SO3 R1 = p1->estimate().so3();
const SO3 R1T = R1.inverse();
const SO3 R2 = p2->estimate().so3();
auto dR_dbg = preint_->dR_dbg_;
auto dv_dbg = preint_->dV_dbg_;
auto dp_dbg = preint_->dP_dbg_;
auto dv_dba = preint_->dV_dba_;
auto dp_dba = preint_->dP_dba_;
// 估计值
Vec3d vi = v1->estimate();
Vec3d vj = v2->estimate();
Vec3d pi = p1->estimate().translation();
Vec3d pj = p2->estimate().translation();
const SO3 dR = preint_->GetDeltaRotation(bg);
const SO3 eR = SO3(dR).inverse() * R1T * R2;
const Vec3d er = eR.log();
const Mat3d invJr = SO3::jr_inv(eR);
/// 雅可比矩阵
/// 注意有3个index, 顶点的,自己误差的,顶点内部变量的
/// 变量顺序:pose1(R1,p1), v1, bg1, ba1, pose2(R2,p2), v2
/// 残差顺序:eR, ev, ep,残差顺序为行,变量顺序为列
// | R1 | p1 | v1 | bg1 | ba1 | R2 | p2 | v2 |
// vert | 0 | 1 | 2 | 3 | 4 | 5 |
// col | 0 3 | 0 | 0 | 0 | 0 3 | 0 |
// row
// eR 0 |
// ev 3 |
// ep 6 |
/// 残差对R1, 9x3
_jacobianOplus[0].setZero();
// dR/dR1, 4.42
_jacobianOplus[0].block<3, 3>(0, 0) = -invJr * (R2.inverse() * R1).matrix();
// dv/dR1, 4.47
_jacobianOplus[0].block<3, 3>(3, 0) = SO3::hat(R1T * (vj - vi - grav_ * dt_));
// dp/dR1, 4.48d
_jacobianOplus[0].block<3, 3>(6, 0) = SO3::hat(R1T * (pj - pi - v1->estimate() * dt_ - 0.5 * grav_ * dt_ * dt_));
/// 残差对p1, 9x3
// dp/dp1, 4.48a
_jacobianOplus[0].block<3, 3>(6, 3) = -R1T.matrix();
/// 残差对v1, 9x3
_jacobianOplus[1].setZero();
// dv/dv1, 4.46a
_jacobianOplus[1].block<3, 3>(3, 0) = -R1T.matrix();
// dp/dv1, 4.48c
_jacobianOplus[1].block<3, 3>(6, 0) = -R1T.matrix() * dt_;
/// 残差对bg1
_jacobianOplus[2].setZero();
// dR/dbg1, 4.45
_jacobianOplus[2].block<3, 3>(0, 0) = -invJr * eR.inverse().matrix() * SO3::jr((dR_dbg * dbg).eval()) * dR_dbg;
// dv/dbg1
_jacobianOplus[2].block<3, 3>(3, 0) = -dv_dbg;
// dp/dbg1
_jacobianOplus[2].block<3, 3>(6, 0) = -dp_dbg;
/// 残差对ba1
_jacobianOplus[3].setZero();
// dv/dba1
_jacobianOplus[3].block<3, 3>(3, 0) = -dv_dba;
// dp/dba1
_jacobianOplus[3].block<3, 3>(6, 0) = -dp_dba;
/// 残差对pose2
_jacobianOplus[4].setZero();
// dr/dr2, 4.43
_jacobianOplus[4].block<3, 3>(0, 0) = invJr;
// dp/dp2, 4.48b
_jacobianOplus[4].block<3, 3>(6, 3) = R1T.matrix();
/// 残差对v2
_jacobianOplus[5].setZero();
// dv/dv2, 4,46b
_jacobianOplus[5].block<3, 3>(3, 0) = R1T.matrix(); // OK
}
对i时刻的旋转

对j时刻的旋转



最终得到



五. 效果展示

上述效果图为imu融合gps以及轮速观测的效果
总结
本节展示了下预积分残差跟雅可比,此处并没有详细推导,知识把关键地方贴出来看看,下一讲将会以另外一种形式来推导预积分相关知识,因为再高博书里以及预积分论文中均已经有详细推导了,这里就不重复了,参考文献可看下面两篇,推导的都差不多
参考文献:
【1】 《On-Manifold Preintegration for Real-Time Visual--Inertial Odometry》
【2】《自动驾驶与机器人中的 SLAM 技术》