从零入门激光SLAM(二十二)——Fast-lio2代码详解(一) IMU预测与点云去除畸变

大家好呀,我是一个SLAM方向的在读博士,深知SLAM学习过程一路走来的坎坷,也十分感谢各位大佬的优质文章和源码。随着知识的越来越多,越来越细,我准备整理一个自己的激光SLAM学习笔记专栏,从0带大家快速上手激光SLAM,也方便想入门SLAM的同学和小白学习参考,相信看完会有一定的收获。如有不对的地方欢迎指出,欢迎各位大佬交流讨论,一起进步。博主创建了一个科研互助群Q:951026257,欢迎大家加入讨论。

Fast-lio2原理解析见链接
从零入门激光SLAM(二十一)——看不懂FAST-LIO?进来_fastlio 雷达 更改频率-CSDN博客
注释版代码完整版见
https://github.com/huashu996/Fast-lio2-Supernote
本代码解析以算法流程的逻辑解析代码,一些简单的函数忽略讲解。

一、初始化卡尔曼滤波

初始化了卡尔曼滤波器的动态共享数据结构。通过设置过程模型、状态转移矩阵雅可比、过程噪声雅可比、测量模型、最大迭代次数和收敛阈值,准备了卡尔曼滤波器所需的所有参数和模型,以便进行状态预测和更新

/*初始化卡尔曼滤波器 get_f:过程模型(Process Model) df_dx: 表示状态转移矩阵的雅可比矩阵 df_dw: 表示过程噪声矩阵的雅可比矩阵 h_share_model: 观测模型 NUM_MAX_ITERATIONS: 表示最大迭代次数 epsi:表示收敛阈值(Convergence Threshold) */
kf.init_dyn_share(get_f, df_dx, df_dw, h_share_model, NUM_MAX_ITERATIONS, epsi);
    /*** ROS subscribe initialization ***/
    //sub
    ros::Subscriber sub_pcl = p_pre->lidar_type == AVIA ? \
        nh.subscribe(lid_topic, 200000, livox_pcl_cbk) : \
        nh.subscribe(lid_topic, 200000, standard_pcl_cbk);
    ros::Subscriber sub_imu = nh.subscribe(imu_topic, 200000, imu_cbk);
    //pub
    ros::Publisher pubLaserCloudFull = nh.advertise<sensor_msgs::PointCloud2>
            ("/cloud_registered", 100000);
    ros::Publisher pubLaserCloudFull_body = nh.advertise<sensor_msgs::PointCloud2>
            ("/cloud_registered_body", 100000);
    ros::Publisher pubLaserCloudEffect = nh.advertise<sensor_msgs::PointCloud2>
            ("/cloud_effected", 100000);
    ros::Publisher pubLaserCloudMap = nh.advertise<sensor_msgs::PointCloud2>
            ("/Laser_map", 100000);
    ros::Publisher pubOdomAftMapped = nh.advertise<nav_msgs::Odometry> 
            ("/Odometry", 100000);
    ros::Publisher pubPath          = nh.advertise<nav_msgs::Path> 
            ("/path", 100000);
  • 初始化卡尔曼滤波参数

      //接收系统特定的模型及其差异
      //作为特征矩阵的测量,其维数是变化的。
      //通过一个函数(h_dyn_share_in)完成了测量(z),估计测量(h),偏微分矩阵(h_x, h_v)和噪声协方差(R)的同时计算。
      void init_dyn_share(processModel f_in, processMatrix1 f_x_in, processMatrix2 f_w_in, measurementModel_dyn_share h_dyn_share_in, int maximum_iteration, scalar_type limit_vector[n])
      {
          //预测模型
          f = f_in;
          //状态转移矩阵
          f_x = f_x_in;
          //过程噪声矩阵
          f_w = f_w_in;
          //动态共享观测模型
          h_dyn_share = h_dyn_share_in;
          //最大迭代次数
          maximum_iter = maximum_iteration;
          for(int i=0; i<n; i++)
          {
              limit[i] = limit_vector[i];
          }
    
          x_.build_S2_state();
          x_.build_SO3_state();
          x_.build_vect_state();
      }
    

    二、IMU预测与前向传播

    2.1 整体框架

    当IMU数据与LIDAR数据同步后,初始化系统参数,进入p_imu->Process函数进行对IMU数据处理,获得当前的预测位姿state_point

    signal(SIGINT, SigHandle);
      ros::Rate rate(5000);
      bool status = ros::ok();
      while (status)
      {
    
          if (flg_exit) break;
          ros::spinOnce();
          if(sync_packages(Measures)) //信息同步
          {
              //如果是第一帧
              if (flg_first_scan)
              {
                  first_lidar_time = Measures.lidar_beg_time;
                  p_imu->first_lidar_time = first_lidar_time;
                  flg_first_scan = false;
                  continue;
              }
              auto start = std::chrono::high_resolution_clock::now();
              //初始化参数
              double t0,t1,t2,t3,t4,t5,match_start, solve_start, svd_time;
              match_time = 0;
              kdtree_search_time = 0.0;
              solve_time = 0;
              solve_const_H_time = 0;
              svd_time   = 0;
              t0 = omp_get_wtime();
              // 1. 对IMU数据进行预处理,其中包含了点云畸变处理 前向传播 反向传播
              p_imu->Process(Measures, kf, feats_undistort);
              // 2.获取kf预测的全局状态(imu)
              state_point = kf.get_x(); //imu先验位姿
    

    2.2 IMU处理流程

    meas:包含IMU和LiDAR数据的测量组。
    kf_state:滤波器的状态,类型为 esekfom::esekf<state_ikfom, 12,="" input_ikfom="">。
    cur_pcl_un_:当前未去畸变的点云数据,类型为 PointCloudXYZI::Ptr。</state_ikfom,>

  1. 初始化时间:记录开始处理的时间。
  2. 检查IMU数据和LiDAR数据:确保IMU数据不为空,并且LiDAR数据指针有效。
  3. IMU初始化:如果需要初始化IMU,调用 IMU_init 进行初始化,并检查初始化迭代次数,超过最大计数后,调整加速度协方差并标记初始化完成。
  4. 点云去畸变:调用 UndistortPcl 函数对点云数据进行去畸变处理。
  5. 记录时间:记录去畸变处理后的时间,用于后续分析和调试。

    void ImuProcess::Process(const MeasureGroup &meas,  esekfom::esekf<state_ikfom, 12, input_ikfom> &kf_state, PointCloudXYZI::Ptr cur_pcl_un_)
    {
    //初始化时间测量:
    double t1,t2,t3;
    t1 = omp_get_wtime();
    //检查IMU数据是否为空:
    if(meas.imu.empty()) {return;};
    //检查LiDAR数据指针是否为NULL:
    ROS_ASSERT(meas.lidar != nullptr);
    //IMU初始化:
    if (imu_need_init_)
    {
    /// The very first lidar frame
    //调用 IMU_init 函数进行初始化
    IMU_init(meas, kf_state, init_iter_num);
    imu_need_init_ = true;
    //保存最新的IMU数据
    last_imu_   = meas.imu.back();
    //获取滤波器状态 
    state_ikfom imu_state = kf_state.get_x();
    //如果初始化迭代次数超过最大计数 
    if (init_iter_num > MAX_INI_COUNT)
    {
    //调整加速度协方差 
    cov_acc *= pow(G_m_s2 / mean_acc.norm(), 2);
    imu_need_init_ = false;
    //重置加速度和陀螺仪的协方差比例
    cov_acc = cov_acc_scale;
    cov_gyr = cov_gyr_scale;
    //打印IMU初始化完成信息。
    ROS_INFO("IMU Initial Done");
    // ROS_INFO("IMU Initial Done: Gravity: %.4f %.4f %.4f %.4f; state.bias_g: %.4f %.4f %.4f; acc covarience: %.8f %.8f %.8f; gry covarience: %.8f %.8f %.8f",\ // imu_state.grav[0], imu_state.grav[1], imu_state.grav[2], mean_acc.norm(), cov_bias_gyr[0], cov_bias_gyr[1], cov_bias_gyr[2], cov_acc[0], cov_acc[1], cov_acc[2], cov_gyr[0], cov_gyr[1], cov_gyr[2]);
    fout_imu.open(DEBUG_FILE_DIR("imu.txt"),ios::out);
    }
    
    return;
    }
    //前向传播,点云去畸变
    UndistortPcl(meas, kf_state, *cur_pcl_un_);
    //记录时间
    t2 = omp_get_wtime();
    t3 = omp_get_wtime();
    // cout<<"[ IMU Process ]: Time: "<<t3 - t1<<endl;
    }
    
  • IMU初始化
    用于初始化IMU,包括计算重力、陀螺仪偏差、加速度和陀螺仪的协方差,并将加速度测量归一化为单位重力
    meas:包含IMU和LiDAR数据的测量组。
    kf_state:滤波器的状态,类型为 esekfom::esekf<state_ikfom, 12,="" input_ikfom="">。
    N:用于记录初始化过程中IMU数据的计数。</state_ikfom,>
  1. 初始化重力、陀螺仪偏差、加速度和陀螺仪的协方差:初始化当前加速度和陀螺仪数据。
  2. 处理第一帧IMU数据:如果是第一帧数据,重置参数并初始化平均加速度和陀螺仪。
  3. 计算均值和协方差:通过遍历IMU数据,计算加速度和陀螺仪的均值和协方差。
  4. 初始化滤波器状态:根据计算的均值和协方差,初始化滤波器状态,包括重力、陀螺仪偏差和平移旋转偏差。
  5. 初始化滤波器协方差矩阵:设置滤波器协方差矩阵的初始值。
  6. 记录最新IMU数据:保存最后一个IMU数据以备后续使用。
  1. void ImuProcess::IMU_init(const MeasureGroup &meas, esekfom::esekf<state_ikfom, 12, input_ikfom> &kf_state, int &N)
    {
    /** 1. initializing the gravity, gyro bias, acc and gyro covariance ** 2. normalize the acceleration measurenments to unit gravity **/
    //初始化协方差
    V3D cur_acc, cur_gyr;
    //处理第一帧IMU数据:
    if (b_first_frame_)
    {
    Reset(); // 重置初始化参数
    N = 1; // 设置初始计数
    b_first_frame_ = false; // 标记第一帧已经处理
    const auto &imu_acc = meas.imu.front()->linear_acceleration; //提取线加速度
    const auto &gyr_acc = meas.imu.front()->angular_velocity; //提取角速度
    mean_acc << imu_acc.x, imu_acc.y, imu_acc.z; // 初始化平均加速度
    mean_gyr << gyr_acc.x, gyr_acc.y, gyr_acc.z; // 初始化平均陀螺仪
    first_lidar_time = meas.lidar_beg_time; // 记录第一帧LiDAR时间
    }
    //计算加速度和陀螺仪的均值和协方差:
    for (const auto &imu : meas.imu)
    {
    const auto &imu_acc = imu->linear_acceleration;
    const auto &gyr_acc = imu->angular_velocity;
    cur_acc << imu_acc.x, imu_acc.y, imu_acc.z;
    cur_gyr << gyr_acc.x, gyr_acc.y, gyr_acc.z;
    mean_acc      += (cur_acc - mean_acc) / N; // 更新平均加速度
    mean_gyr      += (cur_gyr - mean_gyr) / N; // 更新平均陀螺仪
    // 更新加速度协方差
    cov_acc = cov_acc * (N - 1.0) / N + (cur_acc - mean_acc).cwiseProduct(cur_acc - mean_acc) * (N - 1.0) / (N * N);
    // 更新陀螺仪协方差
    cov_gyr = cov_gyr * (N - 1.0) / N + (cur_gyr - mean_gyr).cwiseProduct(cur_gyr - mean_gyr) * (N - 1.0) / (N * N);
    // cout<<"acc norm: "<<cur_acc.norm()<<" "<<mean_acc.norm()<<endl;
    N ++;
    }
    //初始化滤波器状态
    state_ikfom init_state = kf_state.get_x();
    init_state.grav = S2(- mean_acc / mean_acc.norm() * G_m_s2); //设置重力方向
    //state_inout.rot = Eye3d; // Exp(mean_acc.cross(V3D(0, 0, -1 / scale_gravity)));
    init_state.bg  = mean_gyr; // 设置陀螺仪偏差
    init_state.offset_T_L_I = Lidar_T_wrt_IMU; // 设置LiDAR相对于IMU的平移偏差
    init_state.offset_R_L_I = Lidar_R_wrt_IMU; // 设置LiDAR相对于IMU的旋转偏差
    kf_state.change_x(init_state); // 更新滤波器状态
    //初始化滤波器协方差矩阵
    esekfom::esekf<state_ikfom, 12, input_ikfom>::cov init_P = kf_state.get_P();
    init_P.setIdentity();  // 初始化协方差为单位矩阵
    init_P(6,6) = init_P(7,7) = init_P(8,8) = 0.00001;  // 设置位置协方差
    init_P(9,9) = init_P(10,10) = init_P(11,11) = 0.00001; // 设置速度协方差
    init_P(15,15) = init_P(16,16) = init_P(17,17) = 0.0001; // 设置陀螺仪偏差协方差
    init_P(18,18) = init_P(19,19) = init_P(20,20) = 0.001; // 设置加速度偏差协方差
    init_P(21,21) = init_P(22,22) = 0.00001;  // 设置其他状态协方差
    kf_state.change_P(init_P); // 更新滤波器协方差矩阵
    //记录最新的IMU数据:
    last_imu_ = meas.imu.back();
    }
    
  • 点云畸变
    用于对点云数据进行去畸变处理
    meas:包含IMU和LiDAR数据的测量组。
    kf_state:滤波器的状态,类型为 esekfom::esekf<state_ikfom, 12,="" input_ikfom="">。
    pcl_out:输出去畸变后的点云数据,类型为 PointCloudXYZI。</state_ikfom,>
  1. 添加上一帧IMU数据:确保IMU数据连续性。
  2. 排序点云数据:按时间偏移排序点云。
  3. 初始化IMU位姿:获取滤波器状态并初始化IMU位姿列表。
  4. 前向传播每个IMU数据点:遍历IMU数据点,更新滤波器状态并保存IMU位姿。
  5. 计算帧末的位姿预测:根据点云结束时间预测帧末位姿。
  6. 对点云进行去畸变:遍历点云数据,使用IMU数据对点云进行去畸变处理。

详情请见...

从零入门激光SLAM(二十二)——Fast-lio2代码详解(一) IMU预测与点云去除畸变 - 古月居 (guyuehome.com)

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桦树无泪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值