【SLAM】VINS-MONO解析——IMU預積分

4.IMU預積分

IMU預積分主要乾了2件事,第一個是IMU預積分獲得α、β、γ值,另一個是誤差傳遞函數的獲取。本部分的流程圖如下圖所示。
在這裏插入圖片描述

各個部分的講解如下鏈接:

【SLAM】VINS-MONO解析——綜述

【SLAM】VINS-MONO解析——feature_tracker

【SLAM】VINS-MONO解析——IMU預積分

【SLAM】VINS-MONO解析——vins_estimator

【SLAM】VINS-MONO解析——初始化(理論部分)

【SLAM】VINS-MONO解析——初始化(代碼部分)

【SLAM】VINS-MONO解析——後端優化(理論部分)

【SLAM】VINS-MONO解析——後端優化(代碼部分)

【SLAM】VINS-MONO解析——sliding window

【SLAM】VINS-MONO解析——迴環檢測

【SLAM】VINS-Fusion解析——流程

【SLAM】VINS-MONO解析——對vins-mono的修改使流程邏輯更清晰

【SLAM】VINS-MONO解析——基於vins-mono的slam系統開發

4.1 IMU預積分

這部分內容主要出現在vins_estimator/src/factor/integration_base.h中。

4.1.1 連續形式

第一個問題,爲什麼要IMU積分?
IMU獲取的是加速度和角速度,通過對IMU測量量的積分操作,能夠獲得機器人的位姿信息。

IMU測量值包括加速度計得到的線加速度和陀螺儀得到的角速度。
在這裏插入圖片描述
其中 t下標表示在IMU座標系下,並受到加速度偏置ba、陀螺儀偏置bw和附加噪聲na、nw的影響。線加速度是重力加速度和物體加速度的合矢量,上標^代表是IMU的測量量,沒有上標的值代表的是真實的量。
附加噪聲是滿足高斯噪聲的。
在這裏插入圖片描述
加速度計偏置和陀螺儀偏置被定義爲爲隨機遊走並隨着時間變化的,它的導數滿足高斯分佈。
在這裏插入圖片描述
對於圖像幀k和k+1,體座標系對應爲bk和bk+1,位置、速度和方向狀態值PVQ可以根據[tk,tk+1]時間間隔內的IMU測量值,在世界座標系下進行傳遞的。
在這裏插入圖片描述
在這裏插入圖片描述
等號右邊的三個量是IMU積分需要求的量,分別是bk+1時刻下,IMU座標原點在世界座標系上的座標,速度和旋轉角度,它們都是由bk時刻對應的值加上從bk到bk+1的變化量求得的,而這個變化量是由IMU積分獲得的。

第二個問題,爲什麼要IMU“預”積分?
IMU的採樣頻率是高於圖像幀的發佈頻率的,所以相鄰兩個圖像幀之間的IMU信息需要積分從而與視覺信息對齊。

Rw<-t,也就是qw<-t,是需要被優化的量,而這個優化的量,是在IMU積分符號內部的,在後續的優化過程中,這個值是在不斷的變化,所以需要一遍遍的重新IMU積分才能獲得真正精確的PVQ值。爲了減少IMU積分次數,就希望這個被優化的量不要出現在積分符號裏,所以就採用IMU預積分的方式。把上面三個等式,全部等號兩邊乘以Rbk<-w,這樣積分裏面的內容只跟IMU的測量量有關,而與被優化的狀態量無關,這就是IMU預積分。
在這裏插入圖片描述
上式中,等號左邊是要求的值,等號右邊括號裏的值是不需要積分的,也是能直接算出來的,α、β、γ是需要積分的量,也就是IMU預積分主要操作的值。
在這裏插入圖片描述
此時的積分結果α、β、γ可以理解爲bk+1對bk的相對運動量,分別對應量綱爲位移,速度和四元數。這裏的α、β、γ可以用一階泰勒展開來獲取它們的近似值。
在這裏插入圖片描述
其中,帶有^的量是由IMU測量量直接計算得到的。dba和dbw是bias的變化量,J是它們與α、β、γ對應的Jacobian。所以說要想求α、β、γ,我們需要首先分別求出它們由IMU測量量直接計算得到的標量值,然後再用bias進行修正獲取真實值。

這一部分代碼,見src/vins_estimator/estimator_node.cpp的evaluate()函數。

    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)
    {//get IMU residuals   Qi transform coordinate from i to w; Vi volosity at time i in w coordinate
        Eigen::Matrix<double, 15, 1> residuals;
//    O_P = 0,O_R = 3, O_V = 6, O_BA = 9, O_BG = 12
        Eigen::Matrix3d dp_dba = jacobian.block<3, 3>(O_P, O_BA);//0,9
        Eigen::Matrix3d dp_dbg = jacobian.block<3, 3>(O_P, O_BG);//0,12

        Eigen::Matrix3d dq_dbg = jacobian.block<3, 3>(O_R, O_BG);//3,12

        Eigen::Matrix3d dv_dba = jacobian.block<3, 3>(O_V, O_BA);//6,9
        Eigen::Matrix3d dv_dbg = jacobian.block<3, 3>(O_V, O_BG);//6,12

        Eigen::Vector3d dba = Bai - linearized_ba;
        Eigen::Vector3d dbg = Bgi - linearized_bg;
        //cuihuakun p6-(6)  // 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;
        //ppt ch3-p69 // 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;
    }


4.1.2 離散形式
在這裏插入圖片描述
這一部分的目的是時刻求出從bk到bk+1時刻α尖,β尖、γ尖的值。從而α、β、γ提供一個初始值,等待後續被bias和相應的J進行修正。
如上圖所示,IMU的採樣頻率要比feature_tracker_node的圖像幀發佈頻率要高得多,分別對應着上圖的紅線和綠線。假設相鄰的兩根紅線對應着i和i+1時刻,那麼某一個delta_ti時間範圍內,它的平均加速度,平均角速度分別是:
在這裏插入圖片描述
那麼i+1時刻的PVQ分別是,
在這裏插入圖片描述
這個公式很明顯可以用程序迭代的方式來計算,從第bk到bk+1時刻的值都能用這種方式遞推獲取。這部分代碼在VINS裏出現過多次。

第一次出現,見src/vins_estimator/estimator_node.cpp的pridict()函數。

void predict(const sensor_msgs::ImuConstPtr &imu_msg)
{   //獲取當前時間
    double t = imu_msg->header.stamp.toSec();
    //首幀判斷
    if (init_imu)
    {latest_time = t;init_imu = 0;return;}
    //獲取dt並傳遞時間
    double dt = t - latest_time;
    latest_time = t;
    //獲取當前時刻的IMU採樣數據
    double dx = imu_msg->linear_acceleration.x;
    double dy = imu_msg->linear_acceleration.y;
    double dz = imu_msg->linear_acceleration.z;
    Eigen::Vector3d linear_acceleration{dx, dy, dz};
    double rx = imu_msg->angular_velocity.x;
    double ry = imu_msg->angular_velocity.y;
    double rz = imu_msg->angular_velocity.z;
    Eigen::Vector3d angular_velocity{rx, ry, rz};
    //注意,以下數據都是世界座標系下的
    Eigen::Vector3d un_acc_0 = tmp_Q * (acc_0 - tmp_Ba) - estimator.g;
    Eigen::Vector3d un_gyr = 0.5 * (gyr_0 + angular_velocity) - tmp_Bg;
    tmp_Q = tmp_Q * Utility::deltaQ(un_gyr * dt);
    Eigen::Vector3d un_acc_1 = tmp_Q * (linear_acceleration - tmp_Ba) - estimator.g;
    Eigen::Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
    tmp_P = tmp_P + dt * tmp_V + 0.5 * dt * dt * un_acc;
    tmp_V = tmp_V + dt * un_acc;
    //信息傳遞
    acc_0 = linear_acceleration;
    gyr_0 = angular_velocity;
}

第二次出現,見src/vins_estimator/estimator.cpp的processIMU()函數,分析見5.1.2-2。

第三次出現,見vins_estimator/src/factor/integration_base.h的midPointIntegration()的前半段。

    void midPointIntegration(double _dt, 
const Eigen::Vector3d &_acc_0, const Eigen::Vector3d &_gyr_0,const Eigen::Vector3d &_acc_1,
const Eigen::Vector3d &_gyr_1,const Eigen::Vector3d &delta_p,const Eigen::Quaterniond &delta_q, const Eigen::Vector3d &delta_v,const Eigen::Vector3d &linearized_ba, const Eigen::Vector3d &linearized_bg,
Eigen::Vector3d &result_delta_p, Eigen::Quaterniond &result_delta_q, Eigen::Vector3d &result_delta_v, Eigen::Vector3d &result_linearized_ba, Eigen::Vector3d &result_linearized_bg, bool update_jacobian)
    {
        Vector3d un_acc_0 = delta_q * (_acc_0 - linearized_ba);
        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);//after dt:qi+1
        Vector3d un_acc_1 = result_delta_q * (_acc_1 - linearized_ba);
        Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);//average mid
        result_delta_p = delta_p + delta_v * _dt + 0.5 * un_acc * _dt * _dt;//alpha(i+1)
        result_delta_v = delta_v + un_acc * _dt;//beta(i+1)
        result_linearized_ba = linearized_ba;
        result_linearized_bg = linearized_bg; 
    }

4.2 誤差傳遞矩陣

爲什麼要求誤差傳遞函數?主要有2個目的,第一個是在求α、β、γ的值的時候給不同bias提供相應的Jacobian;第二個目的是給後端優化部分中IMU部分提供信息矩陣。

實際上講,IMU在每一個時刻的測量都是有誤差的,而且這個誤差是滿足的高斯分佈的。在IMU預積分的遞推過程中,由於下一時刻的值是由上一時刻的值再加上當前的測量值獲得的,所以每一時刻的PVQ的計算值所對應的方差也是在不斷累積。因此,求出對應的誤差傳遞函數十分重要。
所以,根據誤差傳遞函數的定義,我們可以建立一個下面這個形式的誤差傳遞線性模型,它描述了下一時刻和當前時刻誤差的關係:
在這裏插入圖片描述
dzk+1和dzk分別是下一時刻的誤差和上一時刻的誤差,上面這個等式對應的向量/矩陣表達式如下式所示:
在這裏插入圖片描述
至於每一項的推導過程,請見參考文獻中提供的鏈接。

Jacobian的迭代公式爲:
在這裏插入圖片描述
這個J有了之後,帶入4.1.1最後那個公式中去,就能得到真正的α、β、γ。注意的是,VINS裏面出現過多次J的計算,一個是在這,另三個是在後端優化裏出現,注意不要混淆。

協方差迭代公式爲:
在這裏插入圖片描述
這個協方差矩陣,可以用於計算後端優化中IMU部分的信息矩陣。

噪聲項的對角協方差矩陣Q爲:
在這裏插入圖片描述

這一部分代碼對應在midPointIntegration()的後半段,如下:

        if(update_jacobian)
        {   //這些量都是給F的填充作輔助
            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;
              //構造F矩陣
            MatrixXd F = MatrixXd::Zero(15, 15);
            F.block<3, 3>(0, 0) = Matrix3d::Identity();
            F.block<3, 3>(0, 3) = -0.25 * delta_q.toRotationMatrix() * R_a_0_x * _dt * _dt + 
                                  -0.25 * result_delta_q.toRotationMatrix() * R_a_1_x * (Matrix3d::Identity() - R_w_x * _dt) * _dt * _dt;
            F.block<3, 3>(0, 6) = MatrixXd::Identity(3,3) * _dt;
            F.block<3, 3>(0, 9) = -0.25 * (delta_q.toRotationMatrix() + result_delta_q.toRotationMatrix()) * _dt * _dt;
            F.block<3, 3>(0, 12) = -0.25 * result_delta_q.toRotationMatrix() * R_a_1_x * _dt * _dt * -_dt;
            F.block<3, 3>(3, 3) = Matrix3d::Identity() - R_w_x * _dt;
            F.block<3, 3>(3, 12) = -1.0 * MatrixXd::Identity(3,3) * _dt;
            F.block<3, 3>(6, 3) = -0.5 * delta_q.toRotationMatrix() * R_a_0_x * _dt + 
                                  -0.5 * result_delta_q.toRotationMatrix() * R_a_1_x * (Matrix3d::Identity() - R_w_x * _dt) * _dt;
            F.block<3, 3>(6, 6) = Matrix3d::Identity();
            F.block<3, 3>(6, 9) = -0.5 * (delta_q.toRotationMatrix() + result_delta_q.toRotationMatrix()) * _dt;
            F.block<3, 3>(6, 12) = -0.5 * result_delta_q.toRotationMatrix() * R_a_1_x * _dt * -_dt;
            F.block<3, 3>(9, 9) = Matrix3d::Identity();
            F.block<3, 3>(12, 12) = Matrix3d::Identity();

            MatrixXd V = MatrixXd::Zero(15,18);
            V.block<3, 3>(0, 0) =  0.25 * delta_q.toRotationMatrix() * _dt * _dt;
            V.block<3, 3>(0, 3) =  0.25 * -result_delta_q.toRotationMatrix() * R_a_1_x  * _dt * _dt * 0.5 * _dt;
            V.block<3, 3>(0, 6) =  0.25 * result_delta_q.toRotationMatrix() * _dt * _dt;
            V.block<3, 3>(0, 9) =  V.block<3, 3>(0, 3);
            V.block<3, 3>(3, 3) =  0.5 * MatrixXd::Identity(3,3) * _dt;
            V.block<3, 3>(3, 9) =  0.5 * MatrixXd::Identity(3,3) * _dt;
            V.block<3, 3>(6, 0) =  0.5 * delta_q.toRotationMatrix() * _dt;
            V.block<3, 3>(6, 3) =  0.5 * -result_delta_q.toRotationMatrix() * R_a_1_x  * _dt * 0.5 * _dt;
            V.block<3, 3>(6, 6) =  0.5 * result_delta_q.toRotationMatrix() * _dt;
            V.block<3, 3>(6, 9) =  V.block<3, 3>(6, 3);
            V.block<3, 3>(9, 12) = MatrixXd::Identity(3,3) * _dt;
            V.block<3, 3>(12, 15) = MatrixXd::Identity(3,3) * _dt;

            jacobian = F * jacobian;//used for cuihuakun p6-(6)
            covariance = F * covariance * F.transpose() + V * noise * V.transpose();

4.3 IntegrationBase類的解讀

這部分內容主要出現在vins_estimator/src/factor/integration_base.h中。

4.3.1 構造函數的定義

IntegrationBase(const Eigen::Vector3d &_acc_0, const Eigen::Vector3d &_gyr_0,                                      const Eigen::Vector3d &_linearized_ba,const Eigen::Vector3d &_linearized_bg)
    : acc_0{_acc_0}, gyr_0{_gyr_0}, linearized_acc{_acc_0}, linearized_gyr{_gyr_0},
      linearized_ba{_linearized_ba}, linearized_bg{_linearized_bg},
        jacobian{Eigen::Matrix<double, 15, 15>::Identity()}, covariance{Eigen::Matrix<double, 15, 15>::Zero()},
      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();//cuihuakun p9
    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();
}

這個構造函數讀取了4個i時刻的初始值(共12個變量),
用_acc_0初始化acc_0和linearized_acc(分別是i時刻和bk時刻),
用_gyr_0初始化gyr_0和linearized_gyr(分別是i時刻和bk時刻),
用1515的單位矩陣初始化J,
用15
15的0矩陣初始化協方差傳遞矩陣。
用yaml文件裏讀取的IMU噪聲參數初始化18*18的噪聲矩陣。

4.3.2 傳播與重新傳播
對應的函數分別是propagate()和repropagate()。
對於propagate()而言,它的使用次數更多,針對的是bk和bk+1內部的i時刻到i+1時刻的PVQ傳播和誤差傳遞。

void propagate(double _dt, const Eigen::Vector3d &_acc_1, const Eigen::Vector3d &_gyr_1)
{
    dt = _dt;
    acc_1 = _acc_1; //a at time t=t+dt
    gyr_1 = _gyr_1; //w at time t=t+dt
    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; //alpha_i+1
    delta_q = result_delta_q; //gama_i+1 from i+1 coordinate to bk
    delta_v = result_delta_v; //beta_i+1
    linearized_ba = result_linearized_ba;
    linearized_bg = result_linearized_bg;
    delta_q.normalize();
    sum_dt += dt; //time for bk to bk+1
    acc_0 = acc_1;//a_i+1 at bi+1 coordinate
    gyr_0 = gyr_1;//w_i+1  
}

對於repropagate()而言,它的使用次數較少,針對的是從bk到bk+1的PVQ傳播矯正和誤差傳遞矯正。

void repropagate(const Eigen::Vector3d &_linearized_ba, const Eigen::Vector3d &_linearized_bg)
{
    sum_dt = 0.0;//the gap between IMU plot
    acc_0 = linearized_acc;//a at bk in bk coordinate
    gyr_0 = linearized_gyr;//w at bk in bk coordinate
    delta_p.setZero();//alpha
    delta_q.setIdentity();//gama trans bi to bk
    delta_v.setZero();//beta
    linearized_ba = _linearized_ba;
    linearized_bg = _linearized_bg;
    jacobian.setIdentity();
    covariance.setZero();
    for (int i = 0; i < static_cast<int>(dt_buf.size()); i++)
        propagate(dt_buf[i], acc_buf[i], gyr_buf[i]);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章