首页 > 其他分享 >fast-lio代码解析(一)

fast-lio代码解析(一)

时间:2025-01-16 13:33:01浏览次数:3  
标签:acc mathbf text lio fast init imu pcl 解析

文章目录


一.lasermap_fov_segment

void lasermap_fov_segment()
{
    cub_needrm.clear(); // 清空需要移除的立方体集合
    kdtree_delete_counter = 0; // 初始化删除点计数器
    kdtree_delete_time = 0.0;  // 初始化删除时间

    // 计算机体坐标到世界坐标的转换
    pointBodyToWorld(XAxisPoint_body, XAxisPoint_world);
    V3D pos_LiD = pos_lid; // LiDAR当前位姿

    // 初始化局部地图范围
    if (!Localmap_Initialized) {
        for (int i = 0; i < 3; i++) {
            LocalMap_Points.vertex_min[i] = pos_LiD(i) - cube_len / 2.0; // 最小边界
            LocalMap_Points.vertex_max[i] = pos_LiD(i) + cube_len / 2.0; // 最大边界
        }
        Localmap_Initialized = true; // 设置初始化标志
        return;
    }

    // 检查是否需要移动局部地图
    float dist_to_map_edge[3][2]; // 当前位置到边界的距离
    bool need_move = false;       // 是否需要移动标志
    for (int i = 0; i < 3; i++) {
        dist_to_map_edge[i][0] = fabs(pos_LiD(i) - LocalMap_Points.vertex_min[i]); // 到最小边界距离
        dist_to_map_edge[i][1] = fabs(pos_LiD(i) - LocalMap_Points.vertex_max[i]); // 到最大边界距离
        if (dist_to_map_edge[i][0] <= MOV_THRESHOLD * DET_RANGE || 
            dist_to_map_edge[i][1] <= MOV_THRESHOLD * DET_RANGE) {
            need_move = true; // 如果接近边界,设置移动标志
        }
    }

    // 如果无需移动局部地图,直接返回
    if (!need_move) return;

    // 计算新的局部地图范围
    BoxPointType New_LocalMap_Points, tmp_boxpoints;
    New_LocalMap_Points = LocalMap_Points;
    float mov_dist = max((cube_len - 2.0 * MOV_THRESHOLD * DET_RANGE) * 0.5 * 0.9, 
                         double(DET_RANGE * (MOV_THRESHOLD - 1)));

    for (int i = 0; i < 3; i++) {
        tmp_boxpoints = LocalMap_Points;
        if (dist_to_map_edge[i][0] <= MOV_THRESHOLD * DET_RANGE) {
            New_LocalMap_Points.vertex_max[i] -= mov_dist; // 更新最大边界
            New_LocalMap_Points.vertex_min[i] -= mov_dist; // 更新最小边界
            tmp_boxpoints.vertex_min[i] = LocalMap_Points.vertex_max[i] - mov_dist;
            cub_needrm.push_back(tmp_boxpoints); // 添加需要移除的立方体
        } else if (dist_to_map_edge[i][1] <= MOV_THRESHOLD * DET_RANGE) {
            New_LocalMap_Points.vertex_max[i] += mov_dist; // 更新最大边界
            New_LocalMap_Points.vertex_min[i] += mov_dist; // 更新最小边界
            tmp_boxpoints.vertex_max[i] = LocalMap_Points.vertex_min[i] + mov_dist;
            cub_needrm.push_back(tmp_boxpoints); // 添加需要移除的立方体
        }
    }

    // 更新局部地图范围
    LocalMap_Points = New_LocalMap_Points;

    // 清理冗余点
    points_cache_collect(); // 收集点缓存
    double delete_begin = omp_get_wtime(); // 开始计时
    if (cub_needrm.size() > 0) 
        kdtree_delete_counter = ikdtree.Delete_Point_Boxes(cub_needrm); // 调用KD树清理点云
    kdtree_delete_time = omp_get_wtime() - delete_begin; // 记录清理耗时
}

该函数的核心功能是通过动态更新局部地图的范围,保持激光雷达(LiDAR)位于地图中心附近,并清理超出范围的点云数据。以下是关键过程公式化描述:

1. 初始化局部地图

局部地图的最小边界和最大边界初始化为:
v min , i = x i − L 2 \mathbf{v}_{\text{min},i} = x_i - \frac{L}{2} vmin,i​=xi​−2L​
v max , i = x i + L 2 \mathbf{v}_{\text{max},i} = x_i + \frac{L}{2} vmax,i​=xi​+2L​
其中:

  • v min , i \mathbf{v}_{\text{min},i} vmin,i​ 和 v max , i \mathbf{v}_{\text{max},i} vmax,i​ 表示局部地图在第 i i i 维的边界。
  • x i x_i xi​ 表示激光雷达在第 i i i 维的当前位置。
  • L L L 是局部地图的边长。

2. 计算距离并判断是否需要移动

计算当前激光雷达位置到局部地图边界的距离:
d min , i = ∣ x i − v min , i ∣ d_{\text{min},i} = |x_i - \mathbf{v}_{\text{min},i}| dmin,i​=∣xi​−vmin,i​∣
d max , i = ∣ x i − v max , i ∣ d_{\text{max},i} = |x_i - \mathbf{v}_{\text{max},i}| dmax,i​=∣xi​−vmax,i​∣

如果任意维度满足以下条件之一,则需要移动局部地图:
d min , i ≤ γ ⋅ R d_{\text{min},i} \leq \gamma \cdot R dmin,i​≤γ⋅R
d max , i ≤ γ ⋅ R d_{\text{max},i} \leq \gamma \cdot R dmax,i​≤γ⋅R
其中:

  • γ \gamma γ 是边界移动的阈值比例(MOV_THRESHOLD)。
  • R R R 是检测范围(DET_RANGE)。
    // 检查是否需要移动局部地图
    float dist_to_map_edge[3][2]; // 当前位置到边界的距离
    bool need_move = false;       // 是否需要移动标志
    for (int i = 0; i < 3; i++) {
        dist_to_map_edge[i][0] = fabs(pos_LiD(i) - LocalMap_Points.vertex_min[i]); // 到最小边界距离
        dist_to_map_edge[i][1] = fabs(pos_LiD(i) - LocalMap_Points.vertex_max[i]); // 到最大边界距离
        if (dist_to_map_edge[i][0] <= MOV_THRESHOLD * DET_RANGE || 
            dist_to_map_edge[i][1] <= MOV_THRESHOLD * DET_RANGE) {
            need_move = true; // 如果接近边界,设置移动标志
        }
    }

3. 计算地图边界的移动距离

地图边界的移动距离计算为:
Δ d = max ⁡ ( L − 2 γ R 2 ⋅ 0.9 , R ⋅ ( γ − 1 ) ) \Delta d = \max\left(\frac{L - 2 \gamma R}{2} \cdot 0.9, R \cdot (\gamma - 1)\right) Δd=max(2L−2γR​⋅0.9,R⋅(γ−1))

    // 计算新的局部地图范围
    BoxPointType New_LocalMap_Points, tmp_boxpoints;
    New_LocalMap_Points = LocalMap_Points;
    float mov_dist = max((cube_len - 2.0 * MOV_THRESHOLD * DET_RANGE) * 0.5 * 0.9, 
                         double(DET_RANGE * (MOV_THRESHOLD - 1)));

4. 更新局部地图边界

当激光雷达位置接近最小边界时:
v min , i ← v min , i − Δ d \mathbf{v}_{\text{min},i} \gets \mathbf{v}_{\text{min},i} - \Delta d vmin,i​←vmin,i​−Δd
v max , i ← v max , i − Δ d \mathbf{v}_{\text{max},i} \gets \mathbf{v}_{\text{max},i} - \Delta d vmax,i​←vmax,i​−Δd

当激光雷达位置接近最大边界时:
v min , i ← v min , i + Δ d \mathbf{v}_{\text{min},i} \gets \mathbf{v}_{\text{min},i} + \Delta d vmin,i​←vmin,i​+Δd
v max , i ← v max , i + Δ d \mathbf{v}_{\text{max},i} \gets \mathbf{v}_{\text{max},i} + \Delta d vmax,i​←vmax,i​+Δd

   for (int i = 0; i < 3; i++) {
        tmp_boxpoints = LocalMap_Points;
        if (dist_to_map_edge[i][0] <= MOV_THRESHOLD * DET_RANGE) {
            New_LocalMap_Points.vertex_max[i] -= mov_dist; // 更新最大边界
            New_LocalMap_Points.vertex_min[i] -= mov_dist; // 更新最小边界
            tmp_boxpoints.vertex_min[i] = LocalMap_Points.vertex_max[i] - mov_dist;
            cub_needrm.push_back(tmp_boxpoints); // 添加需要移除的立方体
        } else if (dist_to_map_edge[i][1] <= MOV_THRESHOLD * DET_RANGE) {
            New_LocalMap_Points.vertex_max[i] += mov_dist; // 更新最大边界
            New_LocalMap_Points.vertex_min[i] += mov_dist; // 更新最小边界
            tmp_boxpoints.vertex_max[i] = LocalMap_Points.vertex_min[i] + mov_dist;
            cub_needrm.push_back(tmp_boxpoints); // 添加需要移除的立方体
        }
    }

5. 清理超出范围的点云数据

通过 KD 树方法移除超出范围的点云:
N deleted = KDTree.Delete_Boxes ( C remove ) N_{\text{deleted}} = \text{KDTree.Delete\_Boxes}(\mathbf{C}_{\text{remove}}) Ndeleted​=KDTree.Delete_Boxes(Cremove​)

  // 清理冗余点
    points_cache_collect(); // 收集点缓存
    double delete_begin = omp_get_wtime(); // 开始计时
    if (cub_needrm.size() > 0) 
        kdtree_delete_counter = ikdtree.Delete_Point_Boxes(cub_needrm); // 调用KD树清理点云
    kdtree_delete_time = omp_get_wtime() - delete_begin; // 记录清理耗时

二. IMU_init

void ImuProcess::IMU_init(const MeasureGroup &meas, esekfom::esekf<state_ikfom, 12, input_ikfom> &kf_state, int &N)
{
  // 1. 初始化重力、陀螺仪偏差、加速度计和陀螺仪的协方差
  // 2. 将加速度测量值归一化为单位重力
  
  V3D cur_acc, cur_gyr;  // 当前加速度和陀螺仪测量值
  
  // 如果是第一帧数据
  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;  // 记录第一帧激光雷达开始时间
  }

  // 遍历所有IMU数据
  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);
  
  // 初始化陀螺仪偏差
  init_state.bg = mean_gyr;
  // 初始化激光雷达相对于IMU的位移和旋转
  init_state.offset_T_L_I = Lidar_T_wrt_IMU;
  init_state.offset_R_L_I = Lidar_R_wrt_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();
}

IMU_init 函数初始化惯性测量单元(IMU)的状态,包括计算加速度和角速度的均值与协方差、初始化滤波器状态,并将初始IMU状态用于后续滤波计算。以下是关键过程公式化描述:

1. 加速度与角速度均值计算

设 N N N 为当前已处理的IMU样本数,每次迭代更新加速度均值 μ a \mathbf{\mu}_a μa​ 和角速度均值 μ g \mathbf{\mu}_g μg​:
μ a = μ a + a cur − μ a N \mathbf{\mu}_a = \mathbf{\mu}_a + \frac{\mathbf{a}_{\text{cur}} - \mathbf{\mu}_a}{N} μa​=μa​+Nacur​−μa​​
μ g = μ g + g cur − μ g N \mathbf{\mu}_g = \mathbf{\mu}_g + \frac{\mathbf{g}_{\text{cur}} - \mathbf{\mu}_g}{N} μg​=μg​+Ngcur​−μg​​
其中:

  • μ a \mathbf{\mu}_a μa​:当前加速度均值。
  • μ g \mathbf{\mu}_g μg​:当前角速度均值。
  • a cur \mathbf{a}_{\text{cur}} acur​ 和 g cur \mathbf{g}_{\text{cur}} gcur​:当前IMU加速度和角速度。
 // 遍历所有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;

2. 加速度与角速度协方差更新

每次迭代更新加速度协方差 Σ a \mathbf{\Sigma}_a Σa​ 和角速度协方差 Σ g \mathbf{\Sigma}_g Σg​:
Σ a = N − 1 N Σ a + N − 1 N 2 ( a cur − μ a ) ∘ ( a cur − μ a ) \mathbf{\Sigma}_a = \frac{N-1}{N} \mathbf{\Sigma}_a + \frac{N-1}{N^2} (\mathbf{a}_{\text{cur}} - \mathbf{\mu}_a) \circ (\mathbf{a}_{\text{cur}} - \mathbf{\mu}_a) Σa​=NN−1​Σa​+N2N−1​(acur​−μa​)∘(acur​−μa​)
Σ g = N − 1 N Σ g + N − 1 N 2 ( g cur − μ g ) ∘ ( g cur − μ g ) \mathbf{\Sigma}_g = \frac{N-1}{N} \mathbf{\Sigma}_g + \frac{N-1}{N^2} (\mathbf{g}_{\text{cur}} - \mathbf{\mu}_g) \circ (\mathbf{g}_{\text{cur}} - \mathbf{\mu}_g) Σg​=NN−1​Σg​+N2N−1​(gcur​−μg​)∘(gcur​−μg​)
其中:

  • Σ a \mathbf{\Sigma}_a Σa​ 和 Σ g \mathbf{\Sigma}_g Σg​:分别为加速度和角速度的协方差矩阵。
  • ∘ \circ ∘ 表示逐元素相乘。
    // 更新加速度计和陀螺仪的协方差
    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);

3. 初始化重力方向

根据归一化后的加速度均值 μ a \mathbf{\mu}_a μa​ 初始化重力方向 g \mathbf{g} g:
g = − μ a ∥ μ a ∥ ⋅ g \mathbf{g} = -\frac{\mathbf{\mu}_a}{\|\mathbf{\mu}_a\|} \cdot g g=−∥μa​∥μa​​⋅g
其中:

  • g g g 为标准重力加速度,通常取 9.81   m/s 2 9.81 \, \text{m/s}^2 9.81m/s2。
  // 获取滤波器的初始状态
  state_ikfom init_state = kf_state.get_x();
  // 初始化重力向量,方向为平均加速度的反方向,大小为标准重力加速度
  init_state.grav = S2(- mean_acc / mean_acc.norm() * G_m_s2);

4. 滤波器状态初始化

状态向量 x \mathbf{x} x 的初始值设置为:
x init = [ g μ g T L → I R L → I ] \mathbf{x}_{\text{init}} = \begin{bmatrix} \mathbf{g} \\ \mathbf{\mu}_g \\ \mathbf{T}_{L \to I} \\ \mathbf{R}_{L \to I} \end{bmatrix} xinit​= ​gμg​TL→I​RL→I​​
其中:

  • T L → I \mathbf{T}_{L \to I} TL→I​ 和 R L → I \mathbf{R}_{L \to I} RL→I​ 为LiDAR相对于IMU的位移和旋转。

协方差矩阵 P \mathbf{P} P 的初始值:
P = diag ( σ g 2 , σ b g 2 , σ T L I 2 , σ R L I 2 ) \mathbf{P} = \text{diag}( \sigma_g^2, \sigma_{bg}^2, \sigma_{TLI}^2, \sigma_{RLI}^2 ) P=diag(σg2​,σbg2​,σTLI2​,σRLI2​)
其中各分量的值为:
σ g 2 = 0.00001 σ b g 2 = 0.0001 σ T L I 2 = 0.001 σ R L I 2 = 0.00001 \begin{aligned} \sigma_g^2 &= 0.00001 \\ \sigma_{bg}^2 &= 0.0001 \\ \sigma_{TLI}^2 &= 0.001 \\ \sigma_{RLI}^2 &= 0.00001 \end{aligned} σg2​σbg2​σTLI2​σRLI2​​=0.00001=0.0001=0.001=0.00001​

  // 获取滤波器的初始协方差矩阵
  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; 

三. UndistortPcl

UndistortPcl 函数的核心功能是利用IMU数据对LiDAR点云进行去畸变处理(Undistortion),以矫正点云因激光扫描期间的运动引起的失真。主要过程包括IMU前向传播后向点云补偿以及预测帧尾姿态

void ImuProcess::UndistortPcl(const MeasureGroup &meas, esekfom::esekf<state_ikfom, 12, input_ikfom> &kf_state, PointCloudXYZI &pcl_out)
{
  // 将上一帧尾部的IMU数据添加到当前帧头部
  auto v_imu = meas.imu;
  v_imu.push_front(last_imu_);
  const double &imu_beg_time = v_imu.front()->header.stamp.toSec();  // IMU数据的开始时间
  const double &imu_end_time = v_imu.back()->header.stamp.toSec();  // IMU数据的结束时间

  double pcl_beg_time = meas.lidar_beg_time;  // 激光雷达数据的开始时间
  double pcl_end_time = meas.lidar_end_time;  // 激光雷达数据的结束时间

  // 如果是MARSIM类型,调整激光雷达时间范围
  if (lidar_type == MARSIM) {
      pcl_beg_time = last_lidar_end_time_;
      pcl_end_time = meas.lidar_beg_time;
  }

  // 按照时间戳对点云进行排序
  pcl_out = *(meas.lidar);
  sort(pcl_out.points.begin(), pcl_out.points.end(), time_list);
  // 输出调试信息
  // cout << "[ IMU Process ]: Process lidar from " << pcl_beg_time << " to " << pcl_end_time << ", " \
  //          << meas.imu.size() << " imu msgs from " << imu_beg_time << " to " << imu_end_time << endl;

  // 初始化IMU姿态
  state_ikfom imu_state = kf_state.get_x();
  IMUpose.clear();
  IMUpose.push_back(set_pose6d(0.0, acc_s_last, angvel_last, imu_state.vel, imu_state.pos, imu_state.rot.toRotationMatrix()));

  // 前向传播每个IMU点
  V3D angvel_avr, acc_avr, acc_imu, vel_imu, pos_imu;
  M3D R_imu;

  double dt = 0;

  input_ikfom in;
  for (auto it_imu = v_imu.begin(); it_imu < (v_imu.end() - 1); it_imu++)
  {
    auto &&head = *(it_imu);
    auto &&tail = *(it_imu + 1);
    
    if (tail->header.stamp.toSec() < last_lidar_end_time_)    continue;
    
    // 计算角速度和加速度的平均值
    angvel_avr << 0.5 * (head->angular_velocity.x + tail->angular_velocity.x),
                  0.5 * (head->angular_velocity.y + tail->angular_velocity.y),
                  0.5 * (head->angular_velocity.z + tail->angular_velocity.z);
    acc_avr   << 0.5 * (head->linear_acceleration.x + tail->linear_acceleration.x),
                  0.5 * (head->linear_acceleration.y + tail->angular_velocity.y),
                  0.5 * (head->linear_acceleration.z + tail->angular_velocity.z);

    // 归一化加速度
    acc_avr = acc_avr * G_m_s2 / mean_acc.norm(); // - state_inout.ba;

    // 计算时间间隔
    if (head->header.stamp.toSec() < last_lidar_end_time_)
    {
      dt = tail->header.stamp.toSec() - last_lidar_end_time_;
    }
    else
    {
      dt = tail->header.stamp.toSec() - head->header.stamp.toSec();
    }
    
    in.acc = acc_avr;
    in.gyro = angvel_avr;
    Q.block<3, 3>(0, 0).diagonal() = cov_gyr;
    Q.block<3, 3>(3, 3).diagonal() = cov_acc;
    Q.block<3, 3>(6, 6).diagonal() = cov_bias_gyr;
    Q.block<3, 3>(9, 9).diagonal() = cov_bias_acc;
    kf_state.predict(dt, Q, in);

    // 保存每个IMU测量的姿态
    imu_state = kf_state.get_x();
    angvel_last = angvel_avr - imu_state.bg;
    acc_s_last  = imu_state.rot * (acc_avr - imu_state.ba);
    for (int i = 0; i < 3; i++)
    {
      acc_s_last[i] += imu_state.grav[i];
    }
    double &&offs_t = tail->header.stamp.toSec() - pcl_beg_time;
    IMUpose.push_back(set_pose6d(offs_t, acc_s_last, angvel_last, imu_state.vel, imu_state.pos, imu_state.rot.toRotationMatrix()));
  }

  // 计算帧末的位置和姿态预测
  double note = pcl_end_time > imu_end_time ? 1.0 : -1.0;
  dt = note * (pcl_end_time - imu_end_time);
  kf_state.predict(dt, Q, in);
  
  imu_state = kf_state.get_x();
  last_imu_ = meas.imu.back();
  last_lidar_end_time_ = pcl_end_time;

  // 对每个激光雷达点进行畸变校正(反向传播)
  if (pcl_out.points.begin() == pcl_out.points.end()) return;

  if (lidar_type != MARSIM) {
      auto it_pcl = pcl_out.points.end() - 1;
      for (auto it_kp = IMUpose.end() - 1; it_kp != IMUpose.begin(); it_kp--)
      {
          auto head = it_kp - 1;
          auto tail = it_kp;
          R_imu << MAT_FROM_ARRAY(head->rot);
          vel_imu << VEC_FROM_ARRAY(head->vel);
          pos_imu << VEC_FROM_ARRAY(head->pos);
          acc_imu << VEC_FROM_ARRAY(tail->acc);
          angvel_avr << VEC_FROM_ARRAY(tail->gyr);

          for (; it_pcl->curvature / double(1000) > head->offset_time; it_pcl--)
          {
              dt = it_pcl->curvature / double(1000) - head->offset_time;

              // 只使用旋转将点转换到“末”帧
              // 注意:补偿方向与帧移动方向相反
              // 因此,如果要将时间戳为i的点补偿到帧e
              // P_compensate = R_imu_e ^ T * (R_i * P_i + T_ei),其中T_ei表示在全局坐标系中
              M3D R_i(R_imu * Exp(angvel_avr, dt));

              V3D P_i(it_pcl->x, it_pcl->y, it_pcl->z);
              V3D T_ei(pos_imu + vel_imu * dt + 0.5 * acc_imu * dt * dt - imu_state.pos);
              V3D P_compensate = imu_state.offset_R_L_I.conjugate() * (imu_state.rot.conjugate() * (R_i * (imu_state.offset_R_L_I * P_i + imu_state.offset_T_L_I) + T_ei) - imu_state.offset_T_L_I); // 不够准确!

              // 保存校正后的点和它们的旋转
              it_pcl->x = P_compensate(0);
              it_pcl->y = P_compensate(1);
              it_pcl->z = P_compensate(2);

              if (it_pcl == pcl_out.points.begin()) break;
          }
      }
  }
}

1. IMU前向传播

IMU前向传播用于根据IMU测量值预测每个IMU时间点的姿态与速度。

1.1 平均角速度与加速度

每对连续的IMU测量值,计算平均角速度 ω avg \boldsymbol{\omega}_{\text{avg}} ωavg​ 和加速度 a avg \mathbf{a}_{\text{avg}} aavg​:
ω avg = ω head + ω tail 2 \boldsymbol{\omega}_{\text{avg}} = \frac{\boldsymbol{\omega}_{\text{head}} + \boldsymbol{\omega}_{\text{tail}}}{2} ωavg​=2ωhead​+ωtail​​
a avg = a head + a tail 2 \mathbf{a}_{\text{avg}} = \frac{\mathbf{a}_{\text{head}} + \mathbf{a}_{\text{tail}}}{2} aavg​=2ahead​+atail​​
其中:

  • ω head \boldsymbol{\omega}_{\text{head}} ωhead​ 和 ω tail \boldsymbol{\omega}_{\text{tail}} ωtail​:前后IMU角速度。
  • a head \mathbf{a}_{\text{head}} ahead​ 和 a tail \mathbf{a}_{\text{tail}} atail​:前后IMU加速度。
    // 计算角速度和加速度的平均值
    angvel_avr << 0.5 * (head->angular_velocity.x + tail->angular_velocity.x),
                  0.5 * (head->angular_velocity.y + tail->angular_velocity.y),
                  0.5 * (head->angular_velocity.z + tail->angular_velocity.z);
    acc_avr   << 0.5 * (head->linear_acceleration.x + tail->linear_acceleration.x),
                  0.5 * (head->linear_acceleration.y + tail->angular_velocity.y),
                  0.5 * (head->linear_acceleration.z + tail->angular_velocity.z);

1.2 加速度归一化

归一化加速度以补偿重力影响:
a norm = a avg ⋅ g ∥ a mean ∥ \mathbf{a}_{\text{norm}} = \mathbf{a}_{\text{avg}} \cdot \frac{g}{\|\mathbf{a}_{\text{mean}}\|} anorm​=aavg​⋅∥amean​∥g​

    // 归一化加速度
    acc_avr = acc_avr * G_m_s2 / mean_acc.norm(); // - state_inout.ba;

1.3 IMU状态更新

IMU状态通过预测时间 d t dt dt 更新:
x imu ( t + Δ t ) = f ( x imu ( t ) , u imu , Δ t ) \mathbf{x}_{\text{imu}}(t+\Delta t) = f(\mathbf{x}_{\text{imu}}(t), \mathbf{u}_{\text{imu}}, \Delta t) ximu​(t+Δt)=f(ximu​(t),uimu​,Δt)
其中 x imu \mathbf{x}_{\text{imu}} ximu​ 包括:

  • p \mathbf{p} p:位置。
  • v \mathbf{v} v:速度。
  • R \mathbf{R} R:旋转矩阵。
  // 计算帧末的位置和姿态预测
  double note = pcl_end_time > imu_end_time ? 1.0 : -1.0;
  dt = note * (pcl_end_time - imu_end_time);
  kf_state.predict(dt, Q, in);

2. 点云去畸变补偿

通过IMU的姿态和速度信息,对每个激光点进行去畸变补偿。

2.1 点云的时间偏移

激光点相对于帧开始的时间偏移为:
Δ t = t point − t frame_start \Delta t = t_{\text{point}} - t_{\text{frame\_start}} Δt=tpoint​−tframe_start​

2.2 点云坐标变换

将点 P i P_i Pi​ 的坐标转换至帧尾:
P comp = R frame_end ⊤ ⋅ ( R imu ⋅ P i + T comp ) P_{\text{comp}} = \mathbf{R}_{\text{frame\_end}}^\top \cdot \left(\mathbf{R}_{\text{imu}} \cdot P_i + \mathbf{T}_{\text{comp}}\right) Pcomp​=Rframe_end⊤​⋅(Rimu​⋅Pi​+Tcomp​)
其中:

  • T comp \mathbf{T}_{\text{comp}} Tcomp​ 是从IMU的位移和加速度推导出的位移补偿:
    T comp = v ⋅ Δ t + 1 2 a ⋅ ( Δ t ) 2 \mathbf{T}_{\text{comp}} = \mathbf{v} \cdot \Delta t + \frac{1}{2} \mathbf{a} \cdot (\Delta t)^2 Tcomp​=v⋅Δt+21​a⋅(Δt)2

3. 帧尾姿态预测

在点云采集结束时,利用IMU预测帧尾姿态:
x imu_end = f ( x imu_prev , u imu , t end − t prev ) \mathbf{x}_{\text{imu\_end}} = f(\mathbf{x}_{\text{imu\_prev}}, \mathbf{u}_{\text{imu}}, t_{\text{end}} - t_{\text{prev}}) ximu_end​=f(ximu_prev​,uimu​,tend​−tprev​)


 // 对每个激光雷达点进行畸变校正(反向传播)
  if (pcl_out.points.begin() == pcl_out.points.end()) return;

  if (lidar_type != MARSIM) {
      auto it_pcl = pcl_out.points.end() - 1;
      for (auto it_kp = IMUpose.end() - 1; it_kp != IMUpose.begin(); it_kp--)
      {
          auto head = it_kp - 1;
          auto tail = it_kp;
          R_imu << MAT_FROM_ARRAY(head->rot);
          vel_imu << VEC_FROM_ARRAY(head->vel);
          pos_imu << VEC_FROM_ARRAY(head->pos);
          acc_imu << VEC_FROM_ARRAY(tail->acc);
          angvel_avr << VEC_FROM_ARRAY(tail->gyr);

          for (; it_pcl->curvature / double(1000) > head->offset_time; it_pcl--)
          {
              dt = it_pcl->curvature / double(1000) - head->offset_time;

              // 只使用旋转将点转换到“末”帧
              // 注意:补偿方向与帧移动方向相反
              // 因此,如果要将时间戳为i的点补偿到帧e
              // P_compensate = R_imu_e ^ T * (R_i * P_i + T_ei),其中T_ei表示在全局坐标系中
              M3D R_i(R_imu * Exp(angvel_avr, dt));

              V3D P_i(it_pcl->x, it_pcl->y, it_pcl->z);
              V3D T_ei(pos_imu + vel_imu * dt + 0.5 * acc_imu * dt * dt - imu_state.pos);
              V3D P_compensate = imu_state.offset_R_L_I.conjugate() * (imu_state.rot.conjugate() * (R_i * (imu_state.offset_R_L_I * P_i + imu_state.offset_T_L_I) + T_ei) - imu_state.offset_T_L_I); // 不够准确!

              // 保存校正后的点和它们的旋转
              it_pcl->x = P_compensate(0);
              it_pcl->y = P_compensate(1);
              it_pcl->z = P_compensate(2);

              if (it_pcl == pcl_out.points.begin()) break;
          }
      }
  }

标签:acc,mathbf,text,lio,fast,init,imu,pcl,解析
From: https://blog.csdn.net/weixin_41331879/article/details/145105953

相关文章

  • 【好书推荐:一本书读懂AI Agent】4种革新性AI Agent工作流设计模式全解析
    目录4种革新性AIAgent工作流设计模式全解析1.反思2.工具使用3.规划4.多Agent协作总结4种革新性AIAgent工作流设计模式全解析**导读:**AIAgent是指能够在特定环境中自主执行任务的人工智能系统,不仅接收任务,还自主制定和执行工作计划,并在过程中不断自我评估和......
  • 深入解析 ipoib_vlan.c:IPoIB 驱动中的 VLAN 管理
    引言在InfiniBand网络中,IPoIB(IPoverInfiniBand)是一种允许传统IP应用程序在InfiniBand网络上运行的协议。ipoib_vlan.c 是Linux内核中IPoIB驱动的一部分,主要负责处理VLAN(虚拟局域网)相关的功能。本文将详细解析该文件的功能、关键函数及其实现逻辑。文件概述ipo......
  • 深入解析 ipoib_verbs.c:IPoIB 驱动中的核心实现
    ipoib_verbs.c 是Linux内核中InfiniBand协议栈的一部分,属于IPoverInfiniBand(IPoIB)驱动的核心实现文件。IPoIB是一种在InfiniBand网络上传输IP数据包的技术,它允许传统的IP应用程序在InfiniBand硬件上运行。本文将详细分析 ipoib_verbs.c 文件的功能、实......
  • 【2025大模型最新版】AI大模型全解析:零基础入门到精通,一文搞定!
    近年来,随着深度学习技术的飞速发展,AI大模型作为人工智能领域的重要研究对象,正逐步成为学术界和产业界广泛关注的热点议题。AI大模型,作为一类具备庞大参数规模与卓越学习能力的神经网络模型,如BERT、GPT等,已在自然语言处理、计算机视觉等多个领域展现出卓越成效,极大地推动了......
  • 深入解析 Spring AI 系列:解析函数调用
    我们之前讨论并实践过通过常规的函数调用来实现AIAgent的设计和实现。但是,有一个关键点我之前并没有详细讲解。今天我们就来讨论一下,如何让大模型只决定是否调用某个函数,但是SpringAI不会在内部处理函数调用,而是将其代理到客户端。然后,客户端负责处理函数调用,将其分派到相应......
  • 如何处理域名解析和跳转设置
    用户在设置域名泛解析后,发现无法实现预期的跳转效果,即用户访问任意子域名(如 *.domain.com)时,无法正确跳转到主站(如 www.domain.com)。解决方案:步骤操作说明确认DNS解析设置首先,确保域名的泛解析设置正确。通常情况下,泛解析是通过将 * 记录指向主域名的IP地址或CNAME......
  • RPC 源码解析~Apache Dubbo
    解析RPC(远程过程调用)的源码可以帮助你深入理解其工作原理和实现细节。为了更好地进行源码解析,我们选择一个流行的RPC框架——ApacheDubbo作为示例。Dubbo是一个高性能、轻量级的开源JavaRPC框架,广泛应用于企业级应用中。Dubbo的优劣势优势高性能:Dubbo使用Nett......
  • Linux C 使用ZBar库解析二维码和条形码
    1.编译zbar库下载zbar库源码,这里需要注意下,如果识别的二维码中有中文的话,会出现乱码,一般二维码里中文为UTF-8编码,zbar会默认给你把UTF-8转换为ISO8859-1。有两种解决办法,一是自己再转换一下编码格式;二是修改下zbar源码,很简单,只需要修改源码目录下的zbar/qrcode/qrdectxt.c......
  • Zookeeper 核心知识深度解析:从选主到部署
    1.请简述Zookeeper的选主流程Zookeeper是一个用于维护配置信息、命名、提供分布式同步和组服务的工具。它在分布式系统中提供了强一致性,这得益于它的内部实现机制,其中包括选主流程(LeaderElection)。以下是Zookeeper的选主流程简述:初始化阶段:当Zookeeper集群启动时,......
  • Coze 智能体:功能、架构设计与应用解析
    目录一、概述二、主要功能1.智能体创建与管理2.工作流设计3.插件扩展4.多渠道发布与接入5.数据交互与记忆三、架构设计1.核心模块2.系统架构图四、技术实现1.模块化插件设计2.可视化工作流引擎3.支持多模型适配五、应用领域1.内容生成与创作2.客......