fastlivo2-ekf-voxelmap函数总结

fastlivo中:obtained by fusing the last LiDAR or image measurement
(see Section IV-E). We denote the state and covariance
propagated until t k as x̂ k and P̂ k , respectively. Notice that
we do not assume LiDAR scans and images are received at
the same time. The arrival of either a LiDAR scan or image
will cause an update of the state as detailed in Section IV-E.

🧠 核心要点提炼:

概念说明
xˉk−1,Pˉk−1\bar{x}_{k-1}, \bar{P}_{k-1}xˉk−1​,Pˉk−1​上一帧(LiDAR 或图像)观测融合后得到的状态和协方差
x^k,P^k\hat{x}_k, \hat{P}_kx^k​,P^k​从 tk−1t_{k-1}tk−1​ 到 tkt_ktk​ 期间用 IMU 连续预测得到的先验
IMU 测量 uiu_iui​在 tk−1t_{k-1}tk−1​ 和 tkt_ktk​ 之间接收的惯性数据
LiDAR or 图像 到达无论哪一个先到,都会触发一次 EKF 更新
更新位置详见论文 Section IV-E(即 EKF 优化主循环的部分)

状态预测(公式2)和协方差(公式3)是从时间 tk−1t_{k-1}tk−1​ 开始进行传播的,此时最近一次接收到 LiDAR 或图像测量,
传播一直持续到时间 tkt_ktk​,此时当前帧的 LiDAR 或图像测量到达,
在这两个时间点之间,会不断接收 IMU 测量 uiu_iui​,并用它们进行预测传播。

在公式(2)和(3)中的初始状态 xˉk−1\bar{x}_{k-1}xˉk−1​ 和协方差 Pˉk−1\bar{P}_{k-1}Pˉk−1​,是通过上一帧 LiDAR 或图像观测融合得到的(详见论文第 IV-E 节)。

我们将传播到 tkt_ktk​ 时刻的状态和协方差分别记作 x^k\hat{x}_kx^k​ 和 P^k\hat{P}_kP^k​。

注意,我们并不要求 LiDAR 扫描和图像帧同时到达
无论是 LiDAR 扫描或图像帧的到达,都会触发一次状态更新,详细过程见第 IV-E 节。

总结:

特性Fast-LIVOFast-LIVO2
状态更新方式谁先来谁更新(交替)顺序更新(sequential fusion)
每轮优化可融合的残差一种(LiDAR 或 图像)多种(LiDAR + 图像)
多模态信息融合程度松耦合紧耦合 ✅
精度和一致性较好更好 ✅
实现复杂度中等高 ✅

🧭 总结一句话:

Fast-LIVO2 使用的是顺序更新机制,在每一帧更新中,将视觉残差和雷达残差在同一次 EKF 优化循环中依次融合,形成真正的紧耦合联合优化框架。

这也是 Fast-LIVO → Fast-LIVO2 最核心的系统性进化之一。

void VoxelMapManager::StateEstimation(StatesGroup &state_propagat)
{
  cross_mat_list_.clear();
  cross_mat_list_.reserve(feats_down_size_);
  body_cov_list_.clear();
  body_cov_list_.reserve(feats_down_size_);

  // build_residual_time = 0.0;
  // ekf_time = 0.0;
  // double t0 = omp_get_wtime();

  for (size_t i = 0; i < feats_down_body_->size(); i++)
  {
    //遍历降采样后的点云数据 feats_down_body_;
//取出每个点的坐标并转换为 Eigen 向量 V3D 类型(Vector3d);
V3D point_this(feats_down_body_->points[i].x, feats_down_body_->points[i].y, feats_down_body_->points[i].z);
    if (point_this[2] == 0) { point_this[2] = 0.001; }
    M3D var;
    calcBodyCov(point_this, config_setting_.dept_err_, config_setting_.beam_err_, var);
    body_cov_list_.push_back(var);
    point_this = extR_ * point_this + extT_;
    M3D point_crossmat;
    point_crossmat << SKEW_SYM_MATRX(point_this);
    cross_mat_list_.push_back(point_crossmat);
  }

✅ 整体作用:

遍历降采样后的激光点云数据 feats_down_body_,为每个点计算其在传感器坐标系下的协方差 body_cov_list_ 和其世界坐标的反对称矩阵(交叉乘矩阵) cross_mat_list_,作为后续联合更新的一部分准备数据。

vector<pointWithVar>().swap(pv_list_);
  pv_list_.resize(feats_down_size_);

  int rematch_num = 0;
  MD(DIM_STATE, DIM_STATE) G, H_T_H, I_STATE;
  G.setZero();
  H_T_H.setZero();
  I_STATE.setIdentity();

  bool flg_EKF_inited, flg_EKF_converged, EKF_stop_flg = 0;

✅ 小结这段的作用:

这部分是 EKF 联合更新流程的准备阶段,它主要完成了:

  1. 清理并初始化点结构列表 pv_list_

  2. 初始化矩阵:

    • 状态协方差更新项 G

    • 雅可比项累加器 H_T_H

    • 单位矩阵 I_STATE

  3. 设置若干 EKF 状态控制变量,用于后续的联合迭代更新;

for (int iterCount = 0; iterCount < config_setting_.max_iterations_; iterCount++)
  {
    double total_residual = 0.0;
    pcl::PointCloud<pcl::PointXYZI>::Ptr world_lidar(new pcl::PointCloud<pcl::PointXYZI>);
    TransformLidar(state_.rot_end, state_.pos_end, feats_down_body_, world_lidar);
    M3D rot_var = state_.cov.block<3, 3>(0, 0);
    M3D t_var = state_.cov.block<3, 3>(3, 3);
    for (size_t i = 0; i < feats_down_body_->size(); i++)
    {
      pointWithVar &pv = pv_list_[i];
      pv.point_b << feats_down_body_->points[i].x, feats_down_body_->points[i].y, feats_down_body_->points[i].z;
      pv.point_w << world_lidar->points[i].x, world_lidar->points[i].y, world_lidar->points[i].z;

      M3D cov = body_cov_list_[i];
      M3D point_crossmat = cross_mat_list_[i];
      cov = state_.rot_end * cov * state_.rot_end.transpose() + (-point_crossmat) * rot_var * (-point_crossmat.transpose()) + t_var;
      pv.var = cov;
      pv.body_var = body_cov_list_[i];
    }
    ptpl_list_.clear();

    // double t1 = omp_get_wtime();

    BuildResidualListOMP(pv_list_, ptpl_list_);

    // build_residual_time += omp_get_wtime() - t1;

🧠 总结本轮迭代主要任务:

  1. 把 body 坐标下的点转换为 world 坐标;

  2. 计算每个点在 world 坐标下的测量协方差(考虑传感器误差 + 姿态/平移误差);

  3. 构建匹配残差列表,为后续 EKF 的雅可比和卡尔曼增益计算做准备;


    for (int i = 0; i < ptpl_list_.size(); i++)
    {
      total_residual += fabs(ptpl_list_[i].dis_to_plane_);
    }
    effct_feat_num_ = ptpl_list_.size();
    cout << "[ LIO ] Raw feature num: " << feats_undistort_->size() << ", downsampled feature num:" << feats_down_size_ 
         << " effective feature num: " << effct_feat_num_ << " average residual: " << total_residual / effct_feat_num_ << endl;

✅ 总结当前这段代码的作用:

  1. 统计所有参与 EKF 更新的点对的残差;

  2. 计算有效点数量;

  3. 输出优化质量(是否收敛,误差是否变小);

  4. 为判断是否继续迭代,或提前停止提供依据;

   /*** Computation of Measuremnt Jacobian matrix H and measurents covarience
     * ***/
    MatrixXd Hsub(effct_feat_num_, 6);
    MatrixXd Hsub_T_R_inv(6, effct_feat_num_);
    VectorXd R_inv(effct_feat_num_);
    VectorXd meas_vec(effct_feat_num_);
    meas_vec.setZero();
    for (int i = 0; i < effct_feat_num_; i++)
    {
      auto &ptpl = ptpl_list_[i];
      V3D point_this(ptpl.point_b_);
      point_this = extR_ * point_this + extT_;
      V3D point_body(ptpl.point_b_);
      M3D point_crossmat;
      point_crossmat << SKEW_SYM_MATRX(point_this);

      /*** get the normal vector of closest surface/corner ***/

      V3D point_world = state_propagat.rot_end * point_this + state_propagat.pos_end;
      Eigen::Matrix<double, 1, 6> J_nq;
      J_nq.block<1, 3>(0, 0) = point_world - ptpl_list_[i].center_;
      J_nq.block<1, 3>(0, 3) = -ptpl_list_[i].normal_;

      M3D var;
      

      double sigma_l = J_nq * ptpl_list_[i].plane_var_ * J_nq.transpose();

      R_inv(i) = 1.0 / (0.001 + sigma_l + ptpl_list_[i].normal_.transpose() * var * ptpl_list_[i].normal_);
     
      /*** calculate the Measuremnt Jacobian matrix H ***/
      V3D A(point_crossmat * state_.rot_end.transpose() * ptpl_list_[i].normal_);
      Hsub.row(i) << VEC_FROM_ARRAY(A), ptpl_list_[i].normal_[0], ptpl_list_[i].normal_[1], ptpl_list_[i].normal_[2];
      Hsub_T_R_inv.col(i) << A[0] * R_inv(i), A[1] * R_inv(i), A[2] * R_inv(i), ptpl_list_[i].normal_[0] * R_inv(i),
          ptpl_list_[i].normal_[1] * R_inv(i), ptpl_list_[i].normal_[2] * R_inv(i);
      meas_vec(i) = -ptpl_list_[i].dis_to_plane_;
    }

变量名含义
Hsub测量雅可比矩阵 HHH,每行对状态变量偏导(6维:3旋转+3平移)
Hsub_T_R_invHTR−1H^T R^{-1}HTR−1,用于高效构造信息矩阵
R_inv每个残差的测量协方差逆,用于残差加权
meas_vec残差向量 zzz,表示每个点到局部平面的距离误差

✅ 总结

这一段做的事就是:
🔧 为联合 EKF 更新构造观测矩阵 H、残差向量 z、以及测量协方差逆 R−1R^{-1}R−1,用来后续计算 Kalman 增益与状态修正。

如果我们用数学形式概括这一段代码,它就是构建了如下三项:

  • H∈Rn×6\mathbf{H} \in \mathbb{R}^{n \times 6}H∈Rn×6:观测雅可比矩阵

  • z∈Rn\mathbf{z} \in \mathbb{R}^{n}z∈Rn:残差向量

  • Ri−1R_i^{-1}Ri−1​:每个点观测误差的权重

逐个点处理,开始构造每一行的 HiH_iHi​、ziz_izi​、RiR_iRi​。 

    EKF_stop_flg = false;
    flg_EKF_converged = false;
    /*** Iterative Kalman Filter Update ***/
    MatrixXd K(DIM_STATE, effct_feat_num_);
    
    auto &&HTz = Hsub_T_R_inv * meas_vec;
    // fout_dbg<<"HTz: "<<HTz<<endl;
    H_T_H.block<6, 6>(0, 0) = Hsub_T_R_inv * Hsub;
   
    MD(DIM_STATE, DIM_STATE) &&K_1 = (H_T_H.block<DIM_STATE, DIM_STATE>(0, 0) + state_.cov.block<DIM_STATE, DIM_STATE>(0, 0).inverse()).inverse();
    G.block<DIM_STATE, 6>(0, 0) = K_1.block<DIM_STATE, 6>(0, 0) * H_T_H.block<6, 6>(0, 0);
    auto vec = state_propagat - state_;
    VD(DIM_STATE)
    solution = K_1.block<DIM_STATE, 6>(0, 0) * HTz + vec.block<DIM_STATE, 1>(0, 0) - G.block<DIM_STATE, 6>(0, 0) * vec.block<6, 1>(0, 0);
    int minRow, minCol;
    state_ += solution;
    auto rot_add = solution.block<3, 1>(0, 0);
    auto t_add = solution.block<3, 1>(3, 0);

🧠 总结这段做了什么?

步骤说明
1️⃣构建测量项残差 HTR−1zH^T R^{-1} zHTR−1z 和信息矩阵 HTR−1HH^T R^{-1} HHTR−1H
2️⃣结合先验协方差 PPP,构造融合形式 K1K_1K1​
3️⃣计算最终联合状态更新量(融合观测与预测)
4️⃣更新状态变量 state_
5️⃣提取旋转/平移修正用于判断收敛或后续输出

    if ((rot_add.norm() * 57.3 < 0.01) && (t_add.norm() * 100 < 0.015)) { flg_EKF_converged = true; }
    V3D euler_cur = state_.rot_end.eulerAngles(2, 1, 0);

    /*** Rematch Judgement ***/

    if (flg_EKF_converged || ((rematch_num == 0) && (iterCount == (config_setting_.max_iterations_ - 2)))) { rematch_num++; }

    /*** Convergence Judgements and Covariance Update ***/
    if (!EKF_stop_flg && (rematch_num >= 2 || (iterCount == config_setting_.max_iterations_ - 1)))
    {
      /*** Covariance Update ***/
      // _state.cov = (I_STATE - G) * _state.cov;
      state_.cov.block<DIM_STATE, DIM_STATE>(0, 0) =
          (I_STATE.block<DIM_STATE, DIM_STATE>(0, 0) - G.block<DIM_STATE, DIM_STATE>(0, 0)) * state_.cov.block<DIM_STATE, DIM_STATE>(0, 0);
      // total_distance += (_state.pos_end - position_last).norm();
      position_last_ = state_.pos_end;
      geoQuat_ = tf::createQuaternionMsgFromRollPitchYaw(euler_cur(0), euler_cur(1), euler_cur(2));

      // VD(DIM_STATE) K_sum  = K.rowwise().sum();
      // VD(DIM_STATE) P_diag = _state.cov.diagonal();
      EKF_stop_flg = true;
    }
    if (EKF_stop_flg) break;
  }

  // double t2 = omp_get_wtime();
  // scan_count++;
  // ekf_time = t2 - t0 - build_residual_time;

  // ave_build_residual_time = ave_build_residual_time * (scan_count - 1) / scan_count + build_residual_time / scan_count;
  // ave_ekf_time = ave_ekf_time * (scan_count - 1) / scan_count + ekf_time / scan_count;

  // cout << "[ Mapping ] ekf_time: " << ekf_time << "s, build_residual_time: " << build_residual_time << "s" << endl;
  // cout << "[ Mapping ] ave_ekf_time: " << ave_ekf_time << "s, ave_build_residual_time: " << ave_build_residual_time << "s" << endl;
}

🧠 最终总结这一部分做了什么?

步骤作用
✅ 判断收敛根据姿态和平移变化是否小于阈值
🔄 Rematch 控制如果还没收敛,则可能触发 rematch
📉 协方差更新根据信息增益公式更新状态不确定性
💾 保存状态保存位姿、更新 last position、构造 TF 输出用的四元数
🛑 终止条件达到收敛 / 次数限制 / rematch 次数限制时结束迭代

for iterCount in [0, max_iterations):

    // 1. 点云转换:将 body 点云转换到世界坐标系
    world_lidar = TransformLidar(state_.rot_end, state_.pos_end, feats_down_body_)

    // 2. 为每个点计算测量协方差,并转换为世界坐标系下的结构
    for each point i in feats_down_body_:
        point_b = feats_down_body_[i]
        point_w = TransformPoint(state_, extR_, extT_, point_b)
        cov = propagateCov(point_b, state_.cov, body_cov_list_[i])
        pv_list_[i] = { point_b, point_w, cov }

    // 3. 构建匹配的点-平面对
    ptpl_list_ = BuildResidualListOMP(pv_list_)

    // 4. 统计总残差与有效特征点数
    total_residual = sum(|dis_to_plane| for ptpl in ptpl_list_)
    effct_feat_num = ptpl_list_.size()

    // 5. 构建残差向量 z,观测雅可比矩阵 H,以及测量协方差逆 R⁻¹
    for i in [0, effct_feat_num):
        ptpl = ptpl_list_[i]
        point_this = TransformToIMU(ptpl.point_b, extR_, extT_)
        point_crossmat = skew(point_this)
        point_world = TransformToWorld(state_propagat, point_this)
        J_nq = ComputeJnp(point_world, ptpl.center, ptpl.normal)
        sigma_l = J_nq * ptpl.plane_var * J_nqᵀ
        R_inv[i] = 1 / (sigma_l + normalᵀ * cov * normal)
        H[i] = [crossmat * Rᵀ * normal, normal]
        H_T_R_inv[:, i] = H[i] * R_inv[i]
        z[i] = -ptpl.dis_to_plane

    // 6. 联合状态更新(FastLIO-style EKF)
    HTz = H_T_R_inv * z
    HTH = H_T_R_inv * H
    K1 = (HTH + P⁻¹).inverse()
    G = K1 * HTH
    dx = K1 * HTz + (x_p - x) - G * (x_p - x)
    state_ += dx

    // 7. 判断收敛:增量是否足够小
    if (rotation_delta(dx) < 0.01° and translation_delta(dx) < 1.5cm):
        flg_EKF_converged = true

    // 8. 控制 Rematch(如果刚收敛或接近尾声)
    if (flg_EKF_converged or (rematch_num == 0 and iterCount == max_iterations - 2)):
        rematch_num += 1

    // 9. 达到终止条件,更新协方差并退出迭代
    if (!EKF_stop_flg and (rematch_num >= 2 or iterCount == max_iterations - 1)):
        state_.cov = (I - G) * state_.cov
        position_last_ = state_.pos_end
        geoQuat_ = ConvertToQuaternion(state_.rot_end)
        EKF_stop_flg = true

    if (EKF_stop_flg):
        break

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值