武汉大学KF-GINS开源代码学习1---insPropagation

本着“干中学”的原则,学习了武汉大学的KF-GINS代码,一边学习代码编程的思路,一边学习C++的知识点和一些常见库文件中的函数,编程的思路应该是从后往前,编写代码是从前往后。首先从insPropagation开始写起吧,前面的内容后续再补充了。

目录

insPropagation

1.IMU数据误差补偿

2.IMU状态更新(惯导更新算法)

3.EKFPredict(Phi, Qd)进行状态预测

计算Phi

计算Qd

4. 用到的Eigen库中成员函数

resizeLike

setIdentity

setZero

block


insPropagation

进行INS状态更新(IMU机械编排算法), 并计算IMU状态转移矩阵和噪声阵

    void insPropagation(IMU &imupre, IMU &imucur);

其中输入值是前一时刻(k-1)IMU的值和当前时刻(k)IMU的值,IMU是一个结构体,所以在types.h中定义,但是不知为什么把里程计的速度也放入IMU类中?

    IMU imupre_;
    IMU imucur_;
typedef struct IMU {
    double time;
    double dt;

    Vector3d dtheta; //角速度增量
    Vector3d dvel; //线速度增量

    double odovel;  //里程计(odometer)测量的速度,标量
} IMU;

1.IMU数据误差补偿

imuCompensate(imucur);

imuCompensate代码如下所示:

    imu.dtheta -= imuerror_.gyrbias * imu.dt;
    imu.dvel -= imuerror_.accbias * imu.dt;
    Eigen::Vector3d gyrscale, accscale;
    gyrscale   = Eigen::Vector3d::Ones() + imuerror_.gyrscale;
    accscale   = Eigen::Vector3d::Ones() + imuerror_.accscale;
    imu.dtheta = imu.dtheta.cwiseProduct(gyrscale.cwiseInverse());
    imu.dvel   = imu.dvel.cwiseProduct(accscale.cwiseInverse());

主要是用陀螺零偏和标度因数去校准IMU的输出值,IMU的输出值imu已定义,误差值imuerror_也在gi_engine.h中定义了,如下所示:

ImuError imuerror_;//是gi_engine.h的私有成员变量

对于零偏而言,直接用陀螺和加速度计测量值减去对应零偏即可;

对于标度因数而言,标度误差是指IMU传感器的测量值与真实值之间的比例偏差。例如,如果陀螺仪的标度误差为[0.01, 0.02, 0.03],则实际测量值会比真实值分别放大1.01、1.02和1.03倍。为了校正这种误差,需要将测量值除以标度因子(即取倒数后相乘)。

同时要注意到此处的imuerror_是会变化的,随着量测更新,会得到新估计的误差值,对误差值imuerror_进行校准(再有下一时刻IMU数据来临时,进行惯导更新算法前,即可采用最新估计的误差值对IMU进行误差补偿),如下所示:

    vectemp = dx_.block(BG_ID, 0, 3, 1);
    imuerror_.gyrbias += vectemp;
    vectemp = dx_.block(BA_ID, 0, 3, 1);
    imuerror_.accbias += vectemp;
    vectemp = dx_.block(SG_ID, 0, 3, 1);
    imuerror_.gyrscale += vectemp;
    vectemp = dx_.block(SA_ID, 0, 3, 1);
    imuerror_.accscale += vectemp;

注意:此处是标度因数的误差量δK,而不是标度因数K。

2.IMU状态更新(惯导更新算法)

    INSMech::insMech(pvapre_, pvacur_, imupre, imucur);

函数内容如下所示:

    // 依次进行速度更新、位置更新、姿态更新, 不可调换顺序
    velUpdate(pvapre, pvacur, imupre, imucur);
    posUpdate(pvapre, pvacur, imupre, imucur);
    attUpdate(pvapre, pvacur, imupre, imucur);

 下一篇博客详细介绍。

3.EKFPredict(Phi, Qd)进行状态预测

从EKF的预测公式编写对应的代码,代码如下所示:

void GIEngine::EKFPredict(Eigen::MatrixXd &Phi, Eigen::MatrixXd &Qd) {

    assert(Phi.rows() == Cov_.rows());
    assert(Qd.rows() == Cov_.rows());

    // 传播系统协方差和误差状态
    // propagate system covariance and error state
    Cov_ = Phi * Cov_ * Phi.transpose() + Qd;
    dx_  = Phi * dx_;
}

其中 dx_和Cov_是组合导航中需要用到的变量,因此将其定义到GIEngine类中:

    Eigen::MatrixXd Cov_;

    Eigen::MatrixXd Qc_;

    Eigen::MatrixXd dx_;

 从代码可以看出需要计算得到Phi和Qd,才能进行EKF预测更新,下面就介绍计算的方法:

计算Phi

(1)Phi是离散化之后的值,离散化的公式如下所示:

    Phi.setIdentity();
    Phi = Phi + F * imucur.dt; //离散化

(2)确定F的思路如下:

①首先要确定状态变量是什么,在GIEngine.h之中定义了枚举类型,看以看出状态变量的类型:

enum StateID { P_ID = 0, V_ID = 3, PHI_ID = 6, BG_ID = 9, BA_ID = 12, SG_ID = 15, SA_ID = 18 };

具体的公式可见《惯性导航原理与组合导航课程讲义》:

(1)位置误差的导数关于位置误差和速度误差的关系:

 对应的代码如下所示:

    // position error
    temp.setZero();
    temp(0, 0)                = -pvapre_.vel[2] / rmh;
    temp(0, 2)                = pvapre_.vel[0] / rmh;
    temp(1, 0)                = pvapre_.vel[1] * tan(pvapre_.pos[0]) / rnh;
    temp(1, 1)                = -(pvapre_.vel[2] + pvapre_.vel[0] * tan(pvapre_.pos[0])) / rnh;
    temp(1, 2)                = pvapre_.vel[1] / rnh;
    F.block(P_ID, P_ID, 3, 3) = temp;
    F.block(P_ID, V_ID, 3, 3) = Eigen::Matrix3d::Identity();

(2)速度误差的导数关于速度误差、失准角误差、陀螺零偏和加表零偏之间的关系:

 对应的代码如下所示: 

    temp.setZero();
    temp(0, 0) = -2 * pvapre_.vel[1] * WGS84_WIE * cos(pvapre_.pos[0]) / rmh -
                 pow(pvapre_.vel[1], 2) / rmh / rnh / pow(cos(pvapre_.pos[0]), 2);
    temp(0, 2) = pvapre_.vel[0] * pvapre_.vel[2] / rmh / rmh - pow(pvapre_.vel[1], 2) * tan(pvapre_.pos[0]) / rnh / rnh;
    temp(1, 0) = 2 * WGS84_WIE * (pvapre_.vel[0] * cos(pvapre_.pos[0]) - pvapre_.vel[2] * sin(pvapre_.pos[0])) / rmh +
                 pvapre_.vel[0] * pvapre_.vel[1] / rmh / rnh / pow(cos(pvapre_.pos[0]), 2);
    temp(1, 2) = (pvapre_.vel[1] * pvapre_.vel[2] + pvapre_.vel[0] * pvapre_.vel[1] * tan(pvapre_.pos[0])) / rnh / rnh;
    temp(2, 0) = 2 * WGS84_WIE * pvapre_.vel[1] * sin(pvapre_.pos[0]) / rmh;
    temp(2, 2) = -pow(pvapre_.vel[1], 2) / rnh / rnh - pow(pvapre_.vel[0], 2) / rmh / rmh +
                 2 * gravity / (sqrt(rmrn[0] * rmrn[1]) + pvapre_.pos[2]);
    F.block(V_ID, P_ID, 3, 3) = temp;
    temp.setZero();
    temp(0, 0)                  = pvapre_.vel[2] / rmh;
    temp(0, 1)                  = -2 * (WGS84_WIE * sin(pvapre_.pos[0]) + pvapre_.vel[1] * tan(pvapre_.pos[0]) / rnh);
    temp(0, 2)                  = pvapre_.vel[0] / rmh;
    temp(1, 0)                  = 2 * WGS84_WIE * sin(pvapre_.pos[0]) + pvapre_.vel[1] * tan(pvapre_.pos[0]) / rnh;
    temp(1, 1)                  = (pvapre_.vel[2] + pvapre_.vel[0] * tan(pvapre_.pos[0])) / rnh;
    temp(1, 2)                  = 2 * WGS84_WIE * cos(pvapre_.pos[0]) + pvapre_.vel[1] / rnh;
    temp(2, 0)                  = -2 * pvapre_.vel[0] / rmh;
    temp(2, 1)                  = -2 * (WGS84_WIE * cos(pvapre_.pos(0)) + pvapre_.vel[1] / rnh);
    F.block(V_ID, V_ID, 3, 3)   = temp;
    F.block(V_ID, PHI_ID, 3, 3) = Rotation::skewSymmetric(pvapre_.att.cbn * accel);
    F.block(V_ID, BA_ID, 3, 3)  = pvapre_.att.cbn;
    F.block(V_ID, SA_ID, 3, 3)  = pvapre_.att.cbn * (accel.asDiagonal());

    G.block(V_ID, VRW_ID, 3, 3)    = pvapre_.att.cbn;

失准角误差的导数关于速度误差、失准角误差、陀螺零偏和陀螺标度因数之间的关系:

 对应的代码如下所示: 

    temp.setZero();
    temp(0, 0) = -WGS84_WIE * sin(pvapre_.pos[0]) / rmh;
    temp(0, 2) = pvapre_.vel[1] / rnh / rnh;
    temp(1, 2) = -pvapre_.vel[0] / rmh / rmh;
    temp(2, 0) = -WGS84_WIE * cos(pvapre_.pos[0]) / rmh - pvapre_.vel[1] / rmh / rnh / pow(cos(pvapre_.pos[0]), 2);
    temp(2, 2) = -pvapre_.vel[1] * tan(pvapre_.pos[0]) / rnh / rnh;
    F.block(PHI_ID, P_ID, 3, 3) = temp;
    temp.setZero();
    temp(0, 1)                    = 1 / rnh;
    temp(1, 0)                    = -1 / rmh;
    temp(2, 1)                    = -tan(pvapre_.pos[0]) / rnh;
    F.block(PHI_ID, V_ID, 3, 3)   = temp;
    F.block(PHI_ID, PHI_ID, 3, 3) = -Rotation::skewSymmetric(wie_n + wen_n);
    F.block(PHI_ID, BG_ID, 3, 3)  = -pvapre_.att.cbn;
    F.block(PHI_ID, SG_ID, 3, 3)  = -pvapre_.att.cbn * (omega.asDiagonal());

    G.block(PHI_ID, ARW_ID, 3, 3)  = pvapre_.att.cbn;
//此处应该是负号才对??

 误差的导数与误差之间的关系:

  对应的代码如下所示:

    F.block(BG_ID, BG_ID, 3, 3) = -1 / options_.imunoise.corr_time * Eigen::Matrix3d::Identity();
    F.block(BA_ID, BA_ID, 3, 3) = -1 / options_.imunoise.corr_time * Eigen::Matrix3d::Identity();
    F.block(SG_ID, SG_ID, 3, 3) = -1 / options_.imunoise.corr_time * Eigen::Matrix3d::Identity();
    F.block(SA_ID, SA_ID, 3, 3) = -1 / options_.imunoise.corr_time * Eigen::Matrix3d::Identity();

噪声矩阵的设置:

    G.block(BG_ID, BGSTD_ID, 3, 3) = Eigen::Matrix3d::Identity();
    G.block(BA_ID, BASTD_ID, 3, 3) = Eigen::Matrix3d::Identity();
    G.block(SG_ID, SGSTD_ID, 3, 3) = Eigen::Matrix3d::Identity();
    G.block(SA_ID, SASTD_ID, 3, 3) = Eigen::Matrix3d::Identity();

注意:

此处的位置是e系下的,不是大地坐标(纬度 φ、经度 λ 和椭球高 h)

坐标系的定义不同,导致地球自转在导航系n的投影值不同,即wien不同,导致F矩阵的不同,记得区分。

计算Qd

从《惯性导航原理与组合导航课程讲义》编写公式:

    // 计算系统传播噪声
    // compute system propagation noise
    Qd = G * Qc_ * G.transpose() * imucur.dt;
    Qd = (Phi * Qd * Phi.transpose() + Qd) / 2;

严恭敏老师《捷联惯导算法与组合导航原理》的书上是这样写的:

4. 用到的Eigen库中成员函数

resizeLike

Phi.resizeLike(Cov_);
F.resizeLike(Cov_);
Qd.resizeLike(Cov_);

在 Eigen 库中,resizeLike 是一个用于调整矩阵或向量大小的成员函数,它会将目标矩阵或向量的大小调整为与另一个矩阵或向量的大小一致

setIdentity

Eigen 库中,setIdentity 是一个非常有用的成员函数,用于将矩阵或向量设置为单位矩阵(Identity Matrix)或单位向量。单位矩阵是一个方阵,其对角线上的元素为1,其余元素为0。

setIdentity 可以将一个矩阵或向量设置为单位矩阵或单位向量。它通常用于初始化矩阵或向量。

(1)对于矩阵,setIdentity 会将矩阵设置为单位矩阵。例如:

Eigen::MatrixXd A(3, 3);  // 定义一个 3x3 的矩阵
A.setIdentity();          // 将矩阵 A 设置为单位矩阵

运行后,矩阵 A 的内容为:

1 0 0
0 1 0
0 0 1

(2)对于向量,setIdentity 会将向量的第1个元素设置为1,其余元素设置为0。例如:

Eigen::VectorXd v(4);  // 定义一个 4 维向量
v.setIdentity();       // 将向量 v 设置为单位向量

运行后,向量 v 的内容为:

1
0
0
0

(3)setIdentity 还可以接受两个参数,分别指定行数和列数,用于创建特定大小的单位矩阵。例如:

Eigen::MatrixXd B;
B.setIdentity(4, 4);  // 创建一个 4x4 的单位矩阵

这等价于:

Eigen::MatrixXd B = Eigen::MatrixXd::Identity(4, 4);

(4) 示例代码

以下是一个完整的示例,展示如何使用 setIdentity

#include <iostream>
#include <Eigen/Dense>

using namespace Eigen;
using namespace std;

int main() {
    // 矩阵示例
    MatrixXd A(3, 3);
    A.setIdentity();
    cout << "3x3 单位矩阵 A:\n" << A << endl;

    // 向量示例
    VectorXd v(4);
    v.setIdentity();
    cout << "4 维单位向量 v:\n" << v << endl;

    // 参数化用法
    MatrixXd B;
    B.setIdentity(4, 4);
    cout << "4x4 单位矩阵 B:\n" << B << endl;

    return 0;
}

运行结果:

3x3 单位矩阵 A:
1 0 0
0 1 0
0 0 1
4 维单位向量 v:
1
0
0
0
4x4 单位矩阵 B:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

注意事项

  • setIdentity 是一个 成员函数,它会直接修改调用它的矩阵或向量的内容。

  • 如果需要创建一个新的单位矩阵或向量,可以使用 Eigen::MatrixXd::Identity(rows, cols)Eigen::VectorXd::Unit(size)

  • setIdentity 仅适用于 方阵(矩阵的行数和列数相等)或 向量(一维数组)。

setZero

  • setZero:用于将已存在的矩阵或向量的所有元素设置为零。

  • Zero:用于创建一个新的矩阵或向量,并初始化为零。

 Eigen::Matrix3d mat;
    mat << 1, 2, 3,
           4, 5, 6,
           7, 8, 9;

    std::cout << "Original Matrix:\n" << mat << "\n\n";

    // 使用 setZero 将矩阵所有元素设置为零
    mat.setZero();
    std::cout << "Matrix after setZero:\n" << mat << "\n\n";
Eigen::VectorXd vec1 = Eigen::VectorXd::Zero(5); // 创建一个 5 维零向量
Eigen::MatrixXd mat1 = Eigen::MatrixXd::Zero(3, 4); // 创建一个 3x4 的零矩阵

block

Eigen 库中,block 是一个非常强大的功能,用于操作矩阵或向量的子块(sub-block)。通过 block,你可以提取、修改或赋值矩阵的特定区域,而无需手动遍历元素。这使得代码更加简洁、高效且易于理解。

(1) block 的基本用法

block 的语法如下:

MatrixType::block<int>(int startRow, int startCol, int rows, int cols);
  • startRowstartCol:子块的起始行和列索引(从0开始)。

  • rowscols:子块的行数和列数。

  • 返回值:返回一个子块的引用,可以用于读取或修改。

提取子块

Eigen::MatrixXd A = Eigen::MatrixXd::Random(4, 4);  // 创建一个 4x4 的随机矩阵
Eigen::MatrixXd B = A.block<2, 2>(1, 1);            // 提取从 (1,1) 开始的 2x2 子块

修改子块

A.block<2, 2>(1, 1) = Eigen::MatrixXd::Identity(2, 2);  // 将子块设置为单位矩阵

(2)动态大小的子块

如果子块的大小是动态的(运行时确定),可以省略模板参数:

int rows = 2;
int cols = 2;
A.block(1, 1, rows, cols) = Eigen::MatrixXd::Zero(rows, cols);  // 将子块设置为零矩阵

(3)示例代码

以下是一个完整的示例,展示如何使用 block 来提取和修改矩阵的子块:

#include <iostream>
#include <Eigen/Dense>

using namespace Eigen;
using namespace std;

int main() {
    // 创建一个 4x4 的随机矩阵
    MatrixXd A = MatrixXd::Random(4, 4);
    cout << "原始矩阵 A:\n" << A << endl;

    // 提取从 (1,1) 开始的 2x2 子块
    MatrixXd B = A.block<2, 2>(1, 1);
    cout << "提取的 2x2 子块 B:\n" << B << endl;

    // 修改子块
    A.block<2, 2>(1, 1) = MatrixXd::Identity(2, 2);  // 将子块设置为单位矩阵
    cout << "修改后的矩阵 A:\n" << A << endl;

    // 提取第 2 行
    VectorXd row = A.row(1);
    cout << "第 2 行:\n" << row.transpose() << endl;

    // 提取第 3 列
    VectorXd col = A.col(2);
    cout << "第 3 列:\n" << col << endl;

    return 0;
}

(4)输出结果

假设矩阵 A 是:

原始矩阵 A:
 0.12  0.34  0.56  0.78
 0.90 -0.12  0.34 -0.56
-0.78  0.90 -0.12  0.34
 0.56 -0.78  0.90 -0.12

运行结果:

提取的 2x2 子块 B:
-0.12  0.34
 0.90 -0.12

修改后的矩阵 A:
 0.12  0.34  0.56  0.78
 0.90  1.00  0.00 -0.56
-0.78  0.00  1.00  0.34
 0.56 -0.78  0.90 -0.12

第 2 行:
 0.90  1.00  0.00 -0.56

第 3 列:
 0.56
 0.00
 1.00
 0.90

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值