• VINS学习(二)IMU预积分原理与实现


    一、连续时间下的IMU积分

    IMU测量值是在IMU坐标系中测量的,它受到加速度偏置 a t a_t at、陀螺仪偏置 b t b_t bt和噪声 n a n_a na的影响。假设加速度计和陀螺仪测量值中的噪声为高斯噪声:
    在这里插入图片描述
    IMU坐标系对应 b k , b k + 1 b_k, b_{k+1} bk,bk+1,对于图像帧 k k k k + 1 k+1 k+1,在 [ 𝑡 𝑘 , 𝑡 𝑘 + 1 ] [𝑡_𝑘,𝑡_{𝑘+1}] [tk,tk+1]时间间隔内对所有IMU测量值进行积分,可得第 k + 1 k+1 k+1 帧的位置、速度和旋转 ( P V Q PVQ PVQ)在世界坐标系下进行传递:
    在这里插入图片描述
    其中:
    在这里插入图片描述
    关于四元数 q = [ q w , q x , q y , q z ] q=[q_w,q_x,q_y,q_z] q=[qw,qx,qy,qz]求导的补充
    q ˙ t w = lim ⁡ Δ t → 0 q t + Δ t w − q t w Δ t = lim ⁡ Δ t → 0 q t w ⊗ q t + Δ t t − q t w ⊗ I ( q ) Δ t = lim ⁡ Δ t → 0 q t w ⊗ [ c o s θ 2 n ⃗ s i n θ 2 ] − q t w ⊗ [ 1 0 ⃗ ] Δ t ≈ lim ⁡ Δ t → 0 q t w ⊗ [ 1 n ⃗ θ 2 ] − q t w ⊗ [ 1 0 ⃗ ] Δ t = lim ⁡ Δ t → 0 [ q t + Δ t t ] R q t w − [ I ( q ) ] R q t w = lim ⁡ Δ t → 0 Ω ( n ⃗ θ 2 ) q t w Δ t = 1 2 Ω ( w ^ ) q t w ˙qwt=limΔt0qwt+ΔtqwtΔt=limΔt0qwtqtt+ΔtqwtI(q)Δt=limΔt0qwt[cosθ2nsinθ2]qwt[10]ΔtlimΔt0qwt[1nθ2]qwt[10]Δt=limΔt0[qtt+Δt]Rqwt[I(q)]Rqwt=limΔt0Ω(nθ2)qwtΔt=12Ω(ˆw)qwt

    q˙wt=limΔt0qwt+ΔtqwtΔt=limΔt0qwtqtt+ΔtqwtI(q)Δt=limΔt0qwt[cosθ2n⃗ sinθ2]qwt[10⃗ ]ΔtlimΔt0qwt[1n⃗ θ2]qwt[10⃗ ]Δt=limΔt0[qtt+Δt]Rqwt[I(q)]Rqwt=limΔt0Ω(n⃗ θ2)qwtΔt=12Ω(w^)qwt
    q˙tw=Δt0limΔtqt+Δtwqtw=Δt0limΔtqtwqt+ΔttqtwI(q)=Δt0limΔtqtw[cos2θn 2sinθ]qtw[10 ]Δt0limΔtqtw[12n θ]qtw[10 ]=Δt0lim[qt+Δtt]Rqtw[I(q)]Rqtw=Δt0limΔtΩ(2n θ)qtw=21Ω(w^)qtw
    关于四元数乘法的补充
    在这里插入图片描述
    在这里插入图片描述

    二、连续时间下的IMU预积分

    在卡尔曼滤波中,假设了一阶马尔可夫性,当前时刻状态值只与上一时刻的状态值有关,所以历史时刻的状态不会发生改变,使用普通的方法对IMU进行积分,单向传播即可。但是,在当我们后端进行非线性优化时,历史时刻的状态变量PVQ以及IMU的bais会随着每次迭代而改变,由于IMU积分与上一时刻的状态量相关,所以每次调整完之后,都需要重新计算IMU积分, 造成重复转播,为了解决这个问题引入了IMU预积分方法:
    将参考坐标系从世界坐标系( w w w)转变为当前帧坐标系( b k b_k bk系):
    在这里插入图片描述
    其中:
    在这里插入图片描述
    这样我们就得到了连续时刻的 IMU 预积分公式,可以发现,上式得到的 IMU 预积分的值只与不同时刻的 a ^ t \hat a_t a^t w ^ t \hat w_t w^t相关(实际上预积分值与我们优化变量IMU的bias 也是相关的,但是我们放在后面讨论,连续情况下的预积分先到此为止)。显然,当上一时刻的状态量变化时,预积分值不发生改变,只需要重新简单的计算加减法即可。

    三、离散时间下的IMU预积分

    1. 欧拉法

    下面给出离散时刻的 IMU 预积分公式,首先按照论文中采用的欧拉法,给出第 i i i 个 IMU时刻与第 i + 1 i+1 i+1 个 IMU 时刻的变量关系为:
    在这里插入图片描述

    2. 中值法

    下面给出代码中采用的基于中值法的 IMU 预积分公式,这里积分出来的是前后两帧之间的 IMU 增量信息,而不是当前帧时刻的物理量信息:
    α ^ i + 1 b k = α ^ i b k + β ^ i b k δ t + 1 2 α ^ ˉ i δ t 2 β ^ i + 1 b k = β ^ i b k + α ^ ˉ i δ t γ ^ i + 1 b k = γ ^ i b k ⊗ γ ^ i + 1 i = γ ^ i b k ⊗ [ 1 1 2 ω ^ i δ t ˉ ] ˆαbki+1=ˆαbki+ˆβbkiδt+12ˉˆαiδt2ˆβbki+1=ˆβbki+ˉˆαiδtˆγbki+1=ˆγbkiˆγii+1=ˆγbki[112¯ˆωiδt]

    α^bki+1β^bki+1γ^bki+1=α^bki+β^bkiδt+12α^¯iδt2=β^bki+α^¯iδt=γ^bkiγ^ii+1=γ^bki[112ω^iδt¯]
    α^i+1bkβ^i+1bkγ^i+1bk=α^ibk+β^ibkδt+21α^ˉiδt2=β^ibk+α^ˉiδt=γ^ibkγ^i+1i=γ^ibk[121ω^iδtˉ]
    其中:
    α ^ i ˉ = 1 2 [ q i b k ( α ^ i − b a i ) + q i + 1 b k ( α ^ i + 1 b k − b α i ) ] ω ^ i ˉ = 1 2 ( ω ^ i + ω ^ i + 1 ) − b ω i ¯ˆαi=12[qbki(ˆαibai)+qbki+1(ˆαbki+1bαi)]¯ˆωi=12(ˆωi+ˆωi+1)bωi
    α^iˉω^iˉ=21[qibk(α^ibai)+qi+1bk(α^i+1bkbαi)]=21(ω^i+ω^i+1)bωi

    四、连续时间下的IMU状态误差传递

    IMU 在每一个时刻积分出来的值是有误差的,下面我们对误差进行分析。首先我们直接给出在 t 时刻误差项的导数为(更具体的推导可以参考我之前的一篇博客IMU预积分模型分析):
    在这里插入图片描述
    可以简写为:
    在这里插入图片描述
    根据导数定义可知,下一时刻的均值预测为:
    在这里插入图片描述
    协方差预测公式如下:
    在这里插入图片描述
    在协方差的迭代公式中初始值 P b k b k = 0 P^{b_k}_{b_k}=0 Pbkbk=0 Q Q Q表示噪声的协方差矩阵:
    在这里插入图片描述
    误差项的 Jacobian 初始值为 J b k = I J_{b_k}=I Jbk=I,迭代公式如下:
    在这里插入图片描述

    五、离散时间下的IMU状态误差传递

    考虑离散时间下的IMU状态误差传递:
    δ z k + 1 = δ z k + J ( x ) Δ t δzk+1=δzk+J(x)Δt

    δzk+1=δzk+J(x)Δt
    利用连续时间下的推导,最终可以得到增量误差在离散形式下的矩阵形式:

    在这里插入图片描述
    最终离散时间下矩阵形式可以表达为:
    在这里插入图片描述
    Q Q Q 为表示噪声项的对角协方差矩阵:
    在这里插入图片描述

    离散时间下预积分协方差矩阵的传递可以表示为( P k P_k Pk初值为0):
    在这里插入图片描述

    六、预积分量关于零偏的雅克比

    在第二节中提到预积分值与我们优化变量IMU的bias 也是相关的,而bias 也是我们需要优化的变量,如果每次优化后因为bias改变需要重新计算预积分的话,那么预积分的引入将毫无意义。所以我们这里假设预积分的变化量与bias 是线性关系,则可以表示为:

    在这里插入图片描述
    当优化后bais发生改变时,我们使用上式近似校正预积分结果,而不重新计算预积分。
    显然这样计算会带来一个新的问题: J b a α 、 J b w α 、 J b a β 、 J b w β 、 J b w γ J^{\alpha}_{b_{a}}、J^{\alpha}_{b_w}、J^{\beta}_{b_a}、J^{\beta}_{b_w}、J^{\gamma}_{b_w} JbaαJbwαJbaβJbwβJbwγ怎么计算。想要计算出这几个雅可比矩阵的闭式解是困难的,我们考虑估计值对本身的求导: J z k + 1 = ∂ z k + 1 ∂ z k J_{z_{k+1}}=\frac{\partial z_{k+1}}{\partial z_{k}} Jzk+1=zkzk+1,显然有:
    J b a α = J z k + 1 ( 0 , 1 ) J b w α = J z k + 1 ( 0 , 4 ) J b a β = J z k + 1 ( 1 , 0 ) J b w β = J z k + 1 ( 1 , 4 ) J b w γ = J z k + 1 ( 2 , 4 ) Jαba=Jzk+1(0,1)Jαbw=Jzk+1(0,4)Jβba=Jzk+1(1,0)Jβbw=Jzk+1(1,4)Jγbw=Jzk+1(2,4)

    JbaαJbwαJbaβJbwβJbwγ=Jzk+1(0,1)=Jzk+1(0,4)=Jzk+1(1,0)=Jzk+1(1,4)=Jzk+1(2,4)
    根据链式求导法, J z k + 1 J_{z_{k+1}} Jzk+1可由 J z k J_{z_{k}} Jzk(初值为单位阵 I I I)递推得到:
    J z k + 1 = . . . . . . . . . F i = 1 i = 2 F i = 0 i = 1 J z k J_{z_{k+1}}=......... F^{i=2}_{i=1}F^{i=1}_{i=0}J_{z_{k}} Jzk+1=.........Fi=1i=2Fi=0i=1Jzk

    七、VINS代码实践

    VINS中关于IMU预积分的代码都集中在integration_base.h中,实现了一个IntegrationBase类。

    1.预积分类的数据成员与构造函数

        double dt; //IMU帧的时间间隔
        Eigen::Vector3d acc_0, gyr_0; //当前帧传入的IMU加速度和角速度
        Eigen::Vector3d acc_1, gyr_1;//上一帧IMU加速度和角速度
    
        const Eigen::Vector3d linearized_acc, linearized_gyr;//当前帧传入的IMU加速度和角速度
        Eigen::Vector3d linearized_ba, linearized_bg;//上一帧IMU加速度和角速度的bais
    
        Eigen::Matrix<double, 15, 15> jacobian, covariance;//雅可比矩阵和协方差矩阵
        Eigen::Matrix<double, 15, 15> step_jacobian;
        Eigen::Matrix<double, 15, 18> step_V;
        Eigen::Matrix<double, 18, 18> noise;//噪声矩阵 包括 n_ak n_wk n_ak+1 n_wk+1 n_ba n_bw
    
        double sum_dt;  //关键帧的时间间隔
        Eigen::Vector3d delta_p; //位移增量
        Eigen::Quaterniond delta_q;//旋转增量
        Eigen::Vector3d delta_v;//速度增量
    
        std::vector<double> dt_buf;//关键帧之间的IMU帧时间间隔
        std::vector<Eigen::Vector3d> acc_buf;//关键帧之间的IMU帧加速度
        std::vector<Eigen::Vector3d> gyr_buf;//关键帧之间的IMU帧角速度
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    IntegrationBase(const Eigen::Vector3d &_acc_0, const Eigen::Vector3d &_gyr_0, 
                    //预积分开始时(初始时刻)的IMU加速度和角速度
                    
                   const Eigen::Vector3d &_linearized_ba, const Eigen::Vector3d &_linearized_bg) 
                   //一次预积分的IMU加速度和角速度的bais(不会改变)
                   
       : acc_0{_acc_0}, gyr_0{_gyr_0}, linearized_acc{_acc_0}, linearized_gyr{_gyr_0},
       //利用传入的IMU加速度和角速度给初始时刻和上一时刻的数据赋值
       
         linearized_ba{_linearized_ba}, linearized_bg{_linearized_bg}, 
         //预积分的IMU加速度和角速度的bais给数据成员赋值
         
           jacobian{Eigen::Matrix<double, 15, 15>::Identity()}, covariance{Eigen::Matrix<double, 15, 15>::Zero()},
           //预积分雅可比矩阵初值为单位阵,协方差矩阵为0
           
         sum_dt{0.0}, delta_p{Eigen::Vector3d::Zero()}, delta_q{Eigen::Quaterniond::Identity()}, delta_v{Eigen::Vector3d::Zero()}
         //位移、速度、旋转的增量初始化
    
    {
       noise = Eigen::Matrix<double, 18, 18>::Zero();
       //初始化噪声矩阵
       noise.block<3, 3>(0, 0) =  (ACC_N * ACC_N) * Eigen::Matrix3d::Identity();
       noise.block<3, 3>(3, 3) =  (GYR_N * GYR_N) * Eigen::Matrix3d::Identity();
       noise.block<3, 3>(6, 6) =  (ACC_N * ACC_N) * Eigen::Matrix3d::Identity();
       noise.block<3, 3>(9, 9) =  (GYR_N * GYR_N) * Eigen::Matrix3d::Identity();
       noise.block<3, 3>(12, 12) =  (ACC_W * ACC_W) * Eigen::Matrix3d::Identity();
       noise.block<3, 3>(15, 15) =  (GYR_W * GYR_W) * Eigen::Matrix3d::Identity();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    2.添加IMU数据

    在类的构造函数初始化预积分数据之后,我们基于当前图像关键帧进行IMU预积分操作,所以需要多次添加IMU帧数据,添加函数为push_back。得到每一帧IMU数据之后进行保存并进行传播。

    void push_back(double dt, const Eigen::Vector3d &acc, const Eigen::Vector3d &gyr)
    {
        // 相关时间差和传感器数据保留,方便后续repropagate
        dt_buf.push_back(dt); //IMU帧之间的时间间隔
        acc_buf.push_back(acc);//当前IMU帧的加速度
        gyr_buf.push_back(gyr);//当前IMU帧的角速度
        propagate(dt, acc, gyr);//传播函数 计算预积分并更新协方差矩阵
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.根据IMU数据进行预积分

    dt = _dt;//IMU帧间隔
    acc_1 = _acc_1;//当前帧IMU加速度
    gyr_1 = _gyr_1;//当前帧IMU角速度
    
    
    Vector3d result_delta_p;
    Quaterniond result_delta_q;
    Vector3d result_delta_v;
    Vector3d result_linearized_ba;
    Vector3d result_linearized_bg;
    //定义变量存储预积分结果
    
    
    midPointIntegration(_dt, acc_0, gyr_0, _acc_1, _gyr_1, delta_p, delta_q, delta_v,
                        linearized_ba, linearized_bg,
                        result_delta_p, result_delta_q, result_delta_v,
                        result_linearized_ba, result_linearized_bg, 1);
    
    //checkJacobian(_dt, acc_0, gyr_0, acc_1, gyr_1, delta_p, delta_q, delta_v,
    //                    linearized_ba, linearized_bg);
    //将中值预积分得到的结果进行赋值
    delta_p = result_delta_p;
    delta_q = result_delta_q;
    delta_v = result_delta_v;
    linearized_ba = result_linearized_ba;
    linearized_bg = result_linearized_bg;
    //四元数结果需要归一化
    delta_q.normalize();
    sum_dt += dt;
    //将当前帧IMU数据保存为上一帧
    acc_0 = acc_1;
    gyr_0 = gyr_1;  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    预积分中最主要的函数是midPointIntegration,实现了一次IMU中值预积分
    参数说明:

    void midPointIntegration(
    double _dt, 
    //两帧IMU的时间间隔
    
          const Eigen::Vector3d &_acc_0, const Eigen::Vector3d &_gyr_0,
          //上一帧的IMU数据
          
          const Eigen::Vector3d &_acc_1, const Eigen::Vector3d &_gyr_1,
          //当前帧的IMU数据
          
          const Eigen::Vector3d &delta_p, const Eigen::Quaterniond &delta_q, const Eigen::Vector3d &delta_v,
          //上一IMU帧的位移 速度 旋转的增量  (已知)
          
          const Eigen::Vector3d &linearized_ba, const Eigen::Vector3d &linearized_bg,
          //预积分过程中的IMU bais  (已知,一次预积分过程中不变)
          
          Eigen::Vector3d &result_delta_p, Eigen::Quaterniond &result_delta_q, Eigen::Vector3d &result_delta_v,
          ///当前IMU帧的位移 速度 旋转的增量  (待求)
          
          Eigen::Vector3d &result_linearized_ba, Eigen::Vector3d &result_linearized_bg, 
          预积分过程中的IMU bais  (待求 ,直接由linearized_ba、linearized_bg赋值)
          
          bool update_jacobian)
          //是否更新雅可比矩阵
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    代码细节:

      Vector3d un_acc_0 = delta_q * (_acc_0 - linearized_ba);
      //根据上一IMU帧的四元数 将上一帧IMU加速度去bais 变换到b_k坐标系下
    
      Vector3d un_gyr = 0.5 * (_gyr_0 + _gyr_1) - linearized_bg;
      //根据上一帧和当前帧IMU角速度计算中值角速度
    
      result_delta_q = delta_q * Quaterniond(1, un_gyr(0) * _dt / 2, un_gyr(1) * _dt / 2, un_gyr(2) * _dt / 2);
      //计算当前帧四元数 旋转角度比较小 \gamma_{k+1} 近似为 [1,\theta /2]
    
      Vector3d un_acc_1 = result_delta_q * (_acc_1 - linearized_ba);
      //根据当前IMU帧的四元数 将当前IMU帧加速度去bais 变换到b_k坐标系下
    
      Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
      //根据上一帧和当前帧IMU加速度(b_k系下)计算中值加速度
    
      result_delta_p = delta_p + delta_v * _dt + 0.5 * un_acc * _dt * _dt;
      //计算位移增量
    
      result_delta_v = delta_v + un_acc * _dt;
      //计算速度增量
    
      result_linearized_ba = linearized_ba;
      result_linearized_bg = linearized_bg;      
      //两图像关键帧之间的预积分认为bais不变 所以 直接赋值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    接下来的代码主要是根据第五节中的离散化公式计算 F F F矩阵以及 V V V矩阵。为了简化计算过程,作者提前先计算了三个反对称矩阵: ( a ^ k − b a k ) ∧ (\hat{a}_k-b_{a_k})^{\wedge} (a^kbak) ( a ^ k + 1 − b a k ) ∧ (\hat{a}_{k+1}-b_{a_{k}})^{\wedge} (a^k+1bak) ( w ^ k + w ^ k + 1 2 − b w k ) ∧ (\frac{\hat{w}_k+\hat{w}_{k+1}}{2}-b_{w_k})^{\wedge} (2w^k+w^k+1bwk)

      Vector3d w_x = 0.5 * (_gyr_0 + _gyr_1) - linearized_bg;
      Vector3d a_0_x = _acc_0 - linearized_ba;
      Vector3d a_1_x = _acc_1 - linearized_ba;
      Matrix3d R_w_x, R_a_0_x, R_a_1_x;
    
      R_w_x<<0, -w_x(2), w_x(1),
          w_x(2), 0, -w_x(0),
          -w_x(1), w_x(0), 0;
      R_a_0_x<<0, -a_0_x(2), a_0_x(1),
          a_0_x(2), 0, -a_0_x(0),
          -a_0_x(1), a_0_x(0), 0;
      R_a_1_x<<0, -a_1_x(2), a_1_x(1),
          a_1_x(2), 0, -a_1_x(0),
          -a_1_x(1), a_1_x(0), 0;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接下来套用公式计算(具体实现见源代码及公式):

     MatrixXd F = MatrixXd::Zero(15, 15);
     //略具体赋值 
     
     MatrixXd V = MatrixXd::Zero(15,18);
     //略具体赋值 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    值得注意的是,在在计算 V V V矩阵时,关于 n a k 、 n a k + 1 、 n w k + 1 、 n w k n_{a_k} 、 n_{a_{k+1}} 、n_{w_{k+1}} 、n_{w_{k}} naknak+1nwk+1nwk的系数与前面的计算相差一个符号,但是由于是均值为0的高斯白噪声,所以其效果是一样的。

    最后每次预积分更新雅可比 J J J和协方差 P P P

    jacobian = F * jacobian;
    covariance = F * covariance * F.transpose() + V * noise * V.transpose();
    
    • 1
    • 2
  • 相关阅读:
    基于PHP+MySQL青年志愿者服务管理系统的设计与实现
    Java版CRM客户管理系统源码 CRM小程序源码
    什么是自动化测试框架?常用的自动化测试框架有哪些?
    Kubernetes IPVS和IPTABLES
    无需编写一行代码,实现任何方法的流量防护能力
    【C++笔试强训】第二十五天
    LVS+Keepalived群集
    重新整理汇编—————汇编的基础理论前置篇
    重装系统后新建文本文档打不开怎么办
    Elasticsearch 8.10 中引入查询规则 - query rules
  • 原文地址:https://blog.csdn.net/weixin_44156680/article/details/127764589