Skip to content

grisrobotics/fast-lio-loop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FAST-LIO2 建图流程详解

系统概述

FAST-LIO2是一个基于紧耦合的激光雷达-惯性里程计(LIO)系统,采用Error-State Iterated Kalman Filter (ESIKF)进行多传感器融合,实现高精度实时建图和定位。本文档详细解释了整个建图流程、ESIKF的作用机制以及多传感器融合策略。

1. 系统架构

1.1 核心组件

  • LIOBuilder: 核心建图模块,负责ESIKF状态估计和地图构建
  • IMUProcessor: IMU数据处理器,负责IMU初始化和点云去畸变
  • ESIKF: 误差状态迭代卡尔曼滤波器,实现多传感器融合
  • ikd-tree: 增量式KD树,用于高效点云存储和搜索
  • LoopClosureThread: 回环检测线程,基于GTSAM进行后端优化

1.2 数据流

IMU数据 ──┐
          ├─→ MeasureGroup ─→ LIOBuilder ─→ ESIKF ─→ 状态估计
LiDAR数据 ─┘                      ↓
                                地图更新 ─→ ikd-tree
                                   ↓
                              回环检测 ─→ GTSAM优化

2. 状态定义与表示

2.1 ESIKF状态向量

系统状态采用流形表示,包含以下24维状态:

state_ikfom {
    vect3 pos;           // 位置 (3维)
    SO3 rot;             // 旋转 (3维流形)
    SO3 offset_R_L_I;    // 激光雷达到IMU旋转外参 (3维流形)
    vect3 offset_T_L_I;  // 激光雷达到IMU平移外参 (3维)
    vect3 vel;           // 速度 (3维)
    vect3 bg;            // 陀螺仪偏置 (3维)
    vect3 ba;            // 加速度计偏置 (3维)
    S2 grav;             // 重力向量 (2维球面流形)
}

2.2 输入向量

input_ikfom {
    vect3 acc;   // 加速度测量
    vect3 gyro;  // 角速度测量
}

2.3 过程噪声

process_noise_ikfom {
    vect3 ng;   // 陀螺仪噪声
    vect3 na;   // 加速度计噪声
    vect3 nbg;  // 陀螺仪偏置噪声
    vect3 nba;  // 加速度计偏置噪声
}

3. 建图流程详解

3.1 数据同步与预处理

3.1.1 传感器数据同步

bool MeasureGroup::syncPackage(ImuData &imu_data, LivoxData &livox_data)
  • 时间对齐: 确保IMU数据覆盖整个激光雷达扫描周期
  • 数据缓存: 使用双端队列缓存传感器数据
  • 时间戳检查: 防止数据回环和时序错误

3.1.2 激光雷达数据预处理

void LivoxData::livox2pcl(const livox_ros_driver2::CustomMsg::ConstPtr &msg, 
                          fastlio::PointCloudXYZI::Ptr &out)
  • 数据转换: Livox格式转PCL格式
  • 滤波处理: 按比例降采样,去除近距离点
  • 时间戳提取: 提取每个点的相对时间戳

3.2 IMU处理与初始化

3.2.1 静态初始化

bool IMUProcessor::init(const MeasureGroup &meas)

初始化过程:

  1. 静止检测: 检查IMU是否处于静止状态
  2. 重力估计: 通过加速度计测量估计重力方向
  3. 偏置估计: 计算陀螺仪和加速度计的初始偏置
  4. 协方差初始化: 设置初始状态协方差

重力对齐:

if (align_gravity_) {
    // 将估计的重力方向与标准重力对齐
    Eigen::Vector3d gravity_estimated = mean_acc_.normalized() * G_m_s2;
    // 计算对齐旋转矩阵
}

3.2.2 点云去畸变

void IMUProcessor::undistortPointcloud(const MeasureGroup &meas, 
                                       PointCloudXYZI::Ptr &out)

去畸变流程:

  1. IMU状态积分:

    for (auto &imu : meas.imus) {
        input_ikfom inp;
        inp.acc = imu.acc;
        inp.gyro = imu.gyro;
        double dt = imu.timestamp - last_time;
        kf_->predict(dt, Q_, inp);  // ESIKF预测步骤
        // 记录IMU位姿
        imu_poses_.emplace_back(imu.timestamp, ...);
    }
  2. 点云时间插值:

    for (auto &point : cloud->points) {
        double point_time = point.curvature / 1000.0;  // 相对时间
        // 在IMU位姿序列中插值
        Pose interpolated_pose = interpolate(imu_poses_, point_time);
        // 运动补偿
        point_compensated = apply_motion_compensation(point, interpolated_pose);
    }
  3. 坐标变换链:

    点云(t) → IMU(t) → IMU(end) → 激光雷达(end)
    

3.3 ESIKF状态估计

3.3.1 预测步骤

void esekf::predict(double dt, const cov &Q, const input_ikfom &i_in)

状态传播:

// 计算状态转移函数
Eigen::Matrix<double, 24, 1> f = get_f(x_, i_in);
// 状态更新
x_.oplus(f, dt);

状态转移方程:

Eigen::Matrix<double, 24, 1> get_f(state_ikfom &s, const input_ikfom &in) {
    // 位置导数 = 速度
    res.segment<3>(0) = s.vel;
    
    // 旋转导数 = 去偏置角速度
    vect3 omega;
    in.gyro.boxminus(omega, s.bg);
    res.segment<3>(3) = omega;
    
    // 速度导数 = 旋转后的加速度 + 重力
    vect3 a_inertial = s.rot * (in.acc - s.ba);
    res.segment<3>(12) = a_inertial + s.grav;
    
    // 偏置导数 = 0 (随机游走模型)
    // 重力导数 = 0 (常量)
    
    return res;
}

协方差传播:

// 计算雅可比矩阵
Matrix f_x = df_dx(x_, i_in);  // 状态雅可比
Matrix f_w = df_dw(x_, i_in);  // 噪声雅可比

// 协方差更新
P_ = f_x * P_ * f_x.transpose() + f_w * Q * f_w.transpose();

3.3.2 更新步骤

void esekf::update_iterated_dyn_share_modified(double R, double &solve_time)

观测模型 - 点到平面距离:

  1. 平面拟合:

    bool esti_plane(Eigen::Vector4d &plane_coeff, 
                    const PointVector &points, 
                    const double &thresh) {
        // 最小二乘平面拟合
        // 平面方程: ax + by + cz + d = 0
    }
  2. 残差计算:

    // 点到平面距离作为观测残差
    double residual = plane_coeff(0) * point.x + 
                      plane_coeff(1) * point.y + 
                      plane_coeff(2) * point.z + 
                      plane_coeff(3);
  3. 雅可比矩阵:

    void LIOBuilder::sharedUpdateFunc(state_ikfom &state, 
                                      esekfom::dyn_share_datastruct<double> &share_data) {
        // 位置雅可比
        share_data.h_x.block<1, 3>(i, 0) = norm_vec.transpose();
        
        // 旋转雅可比
        Matrix3d temp_mat = SKEW_SYM_MATRX(temp_vec);
        Matrix<double, 1, 3> B = -norm_vec.transpose() * state.rot.toRotationMatrix() * temp_mat;
        share_data.h_x.block<1, 3>(i, 3) = B;
        
        // 外参雅可比 (如果估计外参)
        if (extrinsic_est_en_) {
            // 外参旋转雅可比
            share_data.h_x.block<1, 3>(i, 6) = C;
            // 外参平移雅可比
            share_data.h_x.block<1, 3>(i, 9) = D;
        }
    }
  4. 迭代优化:

    // 迭代卡尔曼滤波更新
    for (int i = 0; i < max_iterations; i++) {
        // 计算卡尔曼增益
        K = P * H.transpose() * (H * P * H.transpose() + R).inverse();
        
        // 状态更新
        dx = K * residual;
        x.oplus(dx);
        
        // 收敛检查
        if (dx.norm() < convergence_threshold) break;
    }

3.4 地图管理

3.4.1 局部地图修剪

void LIOBuilder::trimMap()

动态地图边界:

  • 根据当前激光雷达位置动态调整局部地图范围
  • 当接近地图边缘时,移动地图中心
  • 删除超出范围的历史点云

3.4.2 增量地图更新

void LIOBuilder::increaseMap()

智能点云添加策略:

  1. 体素网格降采样:

    // 计算点所在体素中心
    PointType mid_point;
    mid_point.x = floor(point.x / resolution) * resolution + 0.5 * resolution;
    mid_point.y = floor(point.y / resolution) * resolution + 0.5 * resolution;
    mid_point.z = floor(point.z / resolution) * resolution + 0.5 * resolution;
  2. 距离比较策略:

    // 比较新点与现有点到体素中心的距离
    float dist_new = sq_dist(new_point, mid_point);
    for (auto &existing_point : nearby_points) {
        if (sq_dist(existing_point, mid_point) < dist_new) {
            need_add = false;  // 现有点更接近中心,不添加新点
            break;
        }
    }
  3. ikd-tree更新:

    ikdtree_->Add_Points(points_to_add, true);           // 需要降采样
    ikdtree_->Add_Points(points_no_downsample, false);   // 直接添加

4. 多传感器融合机制

4.1 紧耦合融合策略

时间同步:

  • IMU高频率(通常200-1000Hz)提供连续运动信息
  • 激光雷达低频率(通常10-20Hz)提供空间约束
  • 通过时间插值实现精确同步

状态共享:

  • IMU和激光雷达共享同一状态向量
  • 实时估计传感器外参
  • 统一的不确定性传播

4.2 传感器互补性

传感器 优势 劣势 在系统中的作用
IMU 高频率、短期精度高 长期漂移 运动预测、点云去畸变
激光雷达 绝对位置精度高 低频率、计算量大 位置校正、地图构建

4.3 外参标定

在线外参估计:

if (extrinsic_est_en_) {
    // 将外参作为状态变量进行估计
    // 旋转外参: offset_R_L_I
    // 平移外参: offset_T_L_I
}

外参雅可比:

  • 外参旋转对观测的影响
  • 外参平移对观测的影响
  • 通过点到平面约束同时优化外参

5. 回环检测与后端优化

5.1 关键帧选择

void MapBuilderROS::addKeyPose() {
    // 基于运动阈值选择关键帧
    if (diff_pose.norm() > dist_thresh || 
        abs(rpy(0)) > rad_thresh || 
        abs(rpy(1)) > rad_thresh || 
        abs(rpy(2)) > rad_thresh) {
        // 添加新的关键帧
    }
}

5.2 回环检测

候选回环搜索:

  1. 基于欧几里得距离的粗搜索
  2. 时间间隔约束
  3. ICP精配准验证

GTSAM后端优化详解:

GTSAM (Georgia Tech Smoothing and Mapping) 后端优化是FAST-LIO2系统中的关键组件,负责全局一致性优化和回环检测。以下是详细的技术实现:

5.2.1 核心数据结构

LoopClosureThread类:

class LoopClosureThread {
private:
    std::shared_ptr<gtsam::ISAM2> isam2_;                    // ISAM2增量优化器
    gtsam::NonlinearFactorGraph gtsam_graph_;               // 因子图
    gtsam::Values initialized_estimate_;                    // 初始估计值
    gtsam::Values optimized_estimate_;                      // 优化后的估计值
    pcl::KdTreeFLANN<pcl::PointXYZ>::Ptr kdtree_history_poses_;  // 历史位姿KD树
    pcl::IterativeClosestPoint<...>::Ptr icp_;             // ICP配准器
};

关键数据结构定义:

// 6DOF位姿结构
struct Pose6D {
    int index;                    // 关键帧索引
    double time;                  // 时间戳
    Eigen::Matrix3d local_rot;    // 局部坐标系旋转
    Eigen::Vector3d local_pos;    // 局部坐标系位置
    Eigen::Matrix3d global_rot;   // 全局坐标系旋转
    Eigen::Vector3d global_pos;   // 全局坐标系位置
};

// 回环约束对
struct LoopPair {
    int pre_idx, cur_idx;         // 回环关键帧索引
    Eigen::Matrix3d diff_rot;     // 相对旋转
    Eigen::Vector3d diff_pos;     // 相对位移
    double score;                 // ICP匹配得分
};

// 共享数据结构
struct SharedData {
    std::vector<Pose6D> key_poses;                           // 关键帧位姿
    std::vector<LoopPair> loop_pairs;                        // 回环约束
    std::vector<std::pair<int, int>> loop_history;           // 回环历史
    std::vector<fastlio::PointCloudXYZI::Ptr> cloud_history; // 点云历史
    Eigen::Matrix3d offset_rot;   // 全局偏移旋转
    Eigen::Vector3d offset_pos;   // 全局偏移位移
};

5.2.2 ISAM2初始化

void LoopClosureThread::init() {
    // ISAM2参数配置
    gtsam::ISAM2Params isam2_params;
    isam2_params.relinearizeThreshold = 0.01;  // 重线性化阈值
    isam2_params.relinearizeSkip = 1;          // 重线性化跳过步数
    isam2_ = std::make_shared<gtsam::ISAM2>(isam2_params);
    
    // ICP配准器配置
    icp_->setMaxCorrespondenceDistance(100);   // 最大对应距离
    icp_->setMaximumIterations(50);            // 最大迭代次数
    icp_->setTransformationEpsilon(1e-6);     // 变换收敛阈值
    icp_->setEuclideanFitnessEpsilon(1e-6);   // 欧几里得适应度阈值
}

5.2.3 回环检测算法

候选回环搜索:

void LoopClosureThread::loopCheck() {
    // 1. 构建历史位姿点云
    for (Pose6D &p : temp_poses_) {
        pcl::PointXYZ point;
        point.x = p.global_pos(0);
        point.y = p.global_pos(1);
        point.z = p.global_pos(2);
        cloud_history_poses_->push_back(point);
    }
    
    // 2. KD树半径搜索
    kdtree_history_poses_->setInputCloud(cloud_history_poses_);
    std::vector<int> ids;
    std::vector<float> sqdists;
    kdtree_history_poses_->radiusSearch(
        cloud_history_poses_->back(), 
        loop_params_.loop_pose_search_radius, 
        ids, sqdists, 0);
    
    // 3. 时间间隔约束
    for (int i = 0; i < ids.size(); i++) {
        int id = ids[i];
        if (std::abs(temp_poses_[id].time - temp_poses_.back().time) > 
            loop_params_.time_thresh) {
            pre_index = id;
            break;
        }
    }
}

子地图构建与ICP配准:

// 构建当前帧和历史帧的子地图
fastlio::PointCloudXYZI::Ptr cur_cloud = 
    getSubMaps(temp_poses_, shared_data_->cloud_history, cur_index, 0);
fastlio::PointCloudXYZI::Ptr sub_maps = 
    getSubMaps(temp_poses_, shared_data_->cloud_history, pre_index, 
               loop_params_.submap_search_num);

// ICP精配准
icp_->setInputSource(cur_cloud);
icp_->setInputTarget(sub_maps);
fastlio::PointCloudXYZI::Ptr aligned(new fastlio::PointCloudXYZI);
icp_->align(*aligned, Eigen::Matrix4f::Identity());

// 验证回环质量
float score = icp_->getFitnessScore();
if (!icp_->hasConverged() || score > loop_params_.loop_icp_thresh)
    return;  // 回环检测失败

// 计算相对变换
Eigen::Matrix4d T_pre_cur = icp_->getFinalTransformation().cast<double>();
Eigen::Matrix3d R12 = temp_poses_[pre_index].global_rot.transpose() * 
                      T_pre_cur.block<3, 3>(0, 0) * 
                      temp_poses_[cur_index].global_rot;
Eigen::Vector3d t12 = temp_poses_[pre_index].global_rot.transpose() * 
                      (T_pre_cur.block<3, 3>(0, 0) * temp_poses_[cur_index].global_pos + 
                       T_pre_cur.block<3, 1>(0, 3) - temp_poses_[pre_index].global_pos);

5.2.4 因子图构建

里程计因子添加:

void LoopClosureThread::addOdomFactor() {
    for (int i = previous_index_; i < lastest_index_; i++) {
        Pose6D &p1 = temp_poses_[i];
        Pose6D &p2 = temp_poses_[i + 1];
        
        // 添加先验因子(仅第一帧)
        if (i == 0) {
            initialized_estimate_.insert(i, gtsam::Pose3(
                gtsam::Rot3(p1.local_rot), gtsam::Point3(p1.local_pos)));
            gtsam::noiseModel::Diagonal::shared_ptr noise = 
                gtsam::noiseModel::Diagonal::Variances(gtsam::Vector6::Ones() * 1e-12);
            gtsam_graph_.add(gtsam::PriorFactor<gtsam::Pose3>(
                i, gtsam::Pose3(gtsam::Rot3(p1.local_rot), gtsam::Point3(p1.local_pos)), noise));
        }
        
        // 添加相邻帧之间的里程计因子
        initialized_estimate_.insert(i + 1, gtsam::Pose3(
            gtsam::Rot3(p2.local_rot), gtsam::Point3(p2.local_pos)));
        
        // 计算相对变换
        Eigen::Matrix3d R12 = p1.local_rot.transpose() * p2.local_rot;
        Eigen::Vector3d t12 = p1.local_rot.transpose() * (p2.local_pos - p1.local_pos);
        
        // 添加BetweenFactor
        gtsam::noiseModel::Diagonal::shared_ptr noise = 
            gtsam::noiseModel::Diagonal::Variances(
                (gtsam::Vector(6) << 1e-6, 1e-6, 1e-6, 1e-4, 1e-4, 1e-6).finished());
        gtsam_graph_.add(gtsam::BetweenFactor<gtsam::Pose3>(
            i, i + 1, gtsam::Pose3(gtsam::Rot3(R12), gtsam::Point3(t12)), noise));
    }
}

回环因子添加:

void LoopClosureThread::addLoopFactor() {
    if (!loop_found_ || shared_data_->loop_pairs.empty())
        return;
        
    for (LoopPair &lp : shared_data_->loop_pairs) {
        // 构建回环约束
        gtsam::Pose3 pose_between(gtsam::Rot3(lp.diff_rot), gtsam::Point3(lp.diff_pos));
        
        // 基于ICP得分的自适应噪声模型
        gtsam::noiseModel::Diagonal::shared_ptr loop_noise = 
            gtsam::noiseModel::Diagonal::Variances(gtsam::Vector6::Ones() * lp.score);
            
        // 添加回环因子
        gtsam_graph_.add(gtsam::BetweenFactor<gtsam::Pose3>(
            lp.pre_idx, lp.cur_idx, pose_between, loop_noise));
    }
    shared_data_->loop_pairs.clear();
}

Z轴先验因子(可选):

class ZaxisPriorFactor : public gtsam::NoiseModelFactor1<gtsam::Pose3> {
public:
    ZaxisPriorFactor(gtsam::Key key, const gtsam::SharedNoiseModel &noise, double z)
        : gtsam::NoiseModelFactor1<gtsam::Pose3>(noise, key), z_(z) {}
        
    virtual gtsam::Vector evaluateError(const gtsam::Pose3 &p, 
                                       boost::optional<gtsam::Matrix &> H = boost::none) const {
        auto z = p.translation()(2);
        if (H) {
            gtsam::Matrix Jac = gtsam::Matrix::Zero(1, 6);
            Jac << 0.0, 0.0, 0.0, 0.0, 0.0, 1.0;  // 仅对Z轴位移求导
            *H = Jac;
        }
        return (gtsam::Vector(1) << z - z_).finished();
    }
};

5.2.5 ISAM2增量优化

void LoopClosureThread::smoothAndUpdate() {
    // 第一次更新:添加新因子和变量
    isam2_->update(gtsam_graph_, initialized_estimate_);
    isam2_->update();  // 执行一次优化迭代
    
    // 回环检测后的多次迭代优化
    if (loop_found_) {
        for (int i = 0; i < 5; i++) {
            isam2_->update();  // 多次迭代确保收敛
        }
        loop_found_ = false;
    }
    
    // 清空临时数据
    gtsam_graph_.resize(0);
    initialized_estimate_.clear();
    
    // 获取优化结果
    optimized_estimate_ = isam2_->calculateBestEstimate();
    
    // 更新最新关键帧的全局位姿
    gtsam::Pose3 latest_estimate = optimized_estimate_.at<gtsam::Pose3>(lastest_index_);
    temp_poses_[lastest_index_].global_rot = latest_estimate.rotation().matrix().cast<double>();
    temp_poses_[lastest_index_].global_pos = latest_estimate.translation().matrix().cast<double>();
    
    // 计算全局偏移
    Eigen::Matrix3d offset_rot;
    Eigen::Vector3d offset_pos;
    temp_poses_[lastest_index_].getOffset(offset_rot, offset_pos);
    
    // 更新共享数据
    shared_data_->mutex.lock();
    shared_data_->offset_rot = offset_rot;
    shared_data_->offset_pos = offset_pos;
    
    // 更新所有历史关键帧的全局位姿
    for (int i = 0; i < lastest_index_; i++) {
        gtsam::Pose3 temp_pose = optimized_estimate_.at<gtsam::Pose3>(i);
        shared_data_->key_poses[i].global_rot = temp_pose.rotation().matrix().cast<double>();
        shared_data_->key_poses[i].global_pos = temp_pose.translation().matrix().cast<double>();
    }
    
    // 应用偏移到未优化的关键帧
    int current_size = shared_data_->key_poses.size();
    for (int i = lastest_index_; i < current_size; i++) {
        shared_data_->key_poses[i].addOffset(offset_rot, offset_pos);
    }
    shared_data_->mutex.unlock();
}

5.2.6 优化参数配置

struct LoopParams {
    double rad_thresh = 0.4;                    // 旋转阈值(弧度)
    double dist_thresh = 2.5;                   // 距离阈值(米)
    double time_thresh = 30.0;                  // 时间间隔阈值(秒)
    double loop_pose_search_radius = 10.0;      // 回环搜索半径(米)
    int loop_pose_index_thresh = 5;             // 最小回环间隔(关键帧数)
    double submap_resolution = 0.2;             // 子地图分辨率(米)
    int submap_search_num = 20;                 // 子地图搜索范围
    double loop_icp_thresh = 0.3;               // ICP匹配阈值
    bool activate = true;                       // 回环检测开关
};

5.2.7 技术特点

  1. 增量式优化: ISAM2支持增量式贝叶斯网络优化,避免重复计算
  2. 自适应噪声: 基于ICP匹配得分动态调整回环约束的噪声模型
  3. 多层验证: 距离搜索→时间约束→ICP配准→得分验证的多层回环检测
  4. 异步处理: 后端优化在独立线程中运行,不影响前端实时性
  5. 全局一致性: 通过因子图优化保证全局轨迹和地图的一致性

6. ESIKF (Error State Iterated Kalman Filter) 详解

ESIKF是FAST-LIO2中用于状态估计的核心算法,它是一种基于误差状态的迭代卡尔曼滤波器,用于融合来自IMU和LiDAR等多种传感器的数据,实现高精度的定位和建图。

6.1 ESIKF核心概念

6.1.1 误差状态表示

传统状态 vs 误差状态:

  • 传统状态: 直接估计系统的绝对状态(位置、姿态等)
  • 误差状态: 估计真实状态与名义状态之间的误差

优势:

  • 避免了直接对非线性状态(如旋转)进行滤波
  • 误差状态通常较小,线性化误差更小
  • 更好的数值稳定性

6.1.2 流形状态表示

MTK工具包的使用:

// 状态定义使用流形表示
typedef MTK::vect<3, double> vect3;        // 3维向量
typedef MTK::SO3<double> SO3;              // 旋转群SO(3)
typedef MTK::S2<double, 98090, 10000, 1> S2; // 球面S2

// 完整状态向量(24维DOF,23维流形)
MTK_BUILD_MANIFOLD(state_ikfom,
    ((vect3, pos))           // 位置 (3维)
    ((SO3, rot))             // 旋转 (3维流形)
    ((SO3, offset_R_L_I))    // 激光雷达到IMU旋转外参 (3维流形)
    ((vect3, offset_T_L_I))  // 激光雷达到IMU平移外参 (3维)
    ((vect3, vel))           // 速度 (3维)
    ((vect3, bg))            // 陀螺仪偏置 (3维)
    ((vect3, ba))            // 加速度计偏置 (3维)
    ((S2, grav)));           // 重力向量 (2维球面流形)

6.2 ESIKF算法流程

6.2.1 预测步骤(Prediction)

状态转移函数:

Eigen::Matrix<double, 24, 1> get_f(state_ikfom &s, const input_ikfom &in) {
    Eigen::Matrix<double, 24, 1> res = Eigen::Matrix<double, 24, 1>::Zero();
    vect3 omega;
    in.gyro.boxminus(omega, s.bg);  // 角速度 - 陀螺仪偏置
    vect3 a_inertial = s.rot * (in.acc - s.ba);  // 惯性系加速度
    
    for (int i = 0; i < 3; i++) {
        res(i) = s.vel[i];                    // 位置导数 = 速度
        res(i + 3) = omega[i];                // 旋转导数 = 角速度
        res(i + 12) = a_inertial[i] + s.grav[i]; // 速度导数 = 加速度 + 重力
    }
    return res;
}

状态传播:

void esekf::predict(double dt, const cov &Q, const input_ikfom &i_in) {
    // 1. 计算状态转移函数
    flatted_state f_ = f(x_, i_in);
    
    // 2. 计算雅可比矩阵
    cov_ f_x_ = f_x(x_, i_in);  // 对状态的雅可比
    Matrix<scalar_type, m, process_noise_dof> f_w_ = f_w(x_, i_in); // 对噪声的雅可比
    
    // 3. 状态更新(流形上的加法)
    x_.oplus(f_, dt);
    
    // 4. 协方差传播
    P_ = f_x_final * P_ * f_x_final.transpose() + f_w_final * Q * f_w_final.transpose();
}

6.2.2 观测更新(Update)

观测模型 - 点到平面距离:

void LIOBuilder::sharedUpdateFunc(state_ikfom &state, 
                                  esekfom::dyn_share_datastruct<double> &share_data) {
    // 1. 点云坐标变换
    for (int i = 0; i < size; i++) {
        PointType &point_body = cloud_down_lidar_->points[i];
        Eigen::Vector3d point_body_vec(point_body.x, point_body.y, point_body.z);
        
        // 激光雷达坐标系 -> IMU坐标系 -> 世界坐标系
        Eigen::Vector3d point_world_vec = state.rot.toRotationMatrix() * 
            (state.offset_R_L_I.toRotationMatrix() * point_body_vec + state.offset_T_L_I) + state.pos;
    }
    
    // 2. 最近邻搜索和平面拟合
    for (int i = 0; i < effect_feat_num; i++) {
        // 平面拟合得到法向量和点面距离
        if (esti_plane(pabcd, points_near, 0.1)) {
            double pd2 = pabcd(0) * point_world_vec(0) + pabcd(1) * point_world_vec(1) + 
                        pabcd(2) * point_world_vec(2) + pabcd(3);
        }
    }
    
    // 3. 计算观测雅可比矩阵
    for (int i = 0; i < effect_feat_num; i++) {
        Eigen::Vector3d temp_vec = state.offset_R_L_I.toRotationMatrix() * laser_p_vec + state.offset_T_L_I;
        Eigen::Matrix3d temp_mat = SKEW_SYM_MATRX(temp_vec);
        
        // 对位置的雅可比
        share_data.h_x.block<1, 3>(i, 0) = norm_vec.transpose();
        
        // 对旋转的雅可比
        Eigen::Matrix<double, 1, 3> B = -norm_vec.transpose() * state.rot.toRotationMatrix() * temp_mat;
        share_data.h_x.block<1, 3>(i, 3) = B;
        
        // 对外参的雅可比(如果估计外参)
        if (extrinsic_est_en_) {
            // 外参旋转雅可比
            temp_mat = SKEW_SYM_MATRX(laser_p_vec);
            Eigen::Matrix<double, 1, 3> C = -norm_vec.transpose() * state.rot.toRotationMatrix() * 
                                           state.offset_R_L_I.toRotationMatrix() * temp_mat;
            share_data.h_x.block<1, 3>(i, 6) = C;
            
            // 外参平移雅可比
            Eigen::Matrix<double, 1, 3> D = norm_vec.transpose() * state.rot.toRotationMatrix();
            share_data.h_x.block<1, 3>(i, 9) = D;
        }
        
        // 观测残差
        share_data.h(i) = -norm_p.intensity;  // 点面距离
    }
}

6.2.3 迭代优化

迭代卡尔曼滤波:

void esekf::update_iterated_dyn_share_modified(double R, double &solve_time) {
    state x_propagated = x_;
    cov P_propagated = P_;
    
    // 迭代优化
    for(int i = 0; i < maximum_iter; i++) {
        // 1. 调用观测函数,计算雅可比和残差
        h_dyn_share(x_, dyn_share);
        
        if(!dyn_share.valid) continue;
        
        // 2. 计算卡尔曼增益
        Matrix<scalar_type, n, Eigen::Dynamic> K = 
            P_ * dyn_share.h_x.transpose() * 
            (dyn_share.h_x * P_ * dyn_share.h_x.transpose() + dyn_share.R).inverse();
        
        // 3. 状态更新
        vectorized_state dx = K * (dyn_share.z - dyn_share.h);
        x_.boxplus(dx);
        
        // 4. 协方差更新
        cov I_KH = cov::Identity() - K * dyn_share.h_x;
        P_ = I_KH * P_ * I_KH.transpose() + K * dyn_share.R * K.transpose();
        
        // 5. 收敛性检查
        if(dx.norm() < convergence_threshold) break;
    }
}

6.3 ESIKF技术特点

6.3.1 流形几何处理

旋转群SO(3)操作:

  • boxplus: 流形上的"加法"操作,用于状态更新
  • boxminus: 流形上的"减法"操作,用于计算误差
  • 正确处理旋转的非线性性质

球面S2处理:

  • 重力向量约束在单位球面上
  • 减少参数化维度(3维→2维)
  • 避免奇异性问题

6.3.2 自适应噪声处理

动态观测噪声:

// 基于点面距离和点的距离的自适应权重
double s = 1 - 0.9 * std::fabs(pd2) / std::sqrt(point_body_vec.norm());
if (s > 0.9) {
    point_selected_flag_[i] = true;  // 选择高质量观测
}

6.3.3 多传感器紧耦合

IMU-LiDAR紧耦合:

  • IMU提供高频运动信息
  • LiDAR提供几何约束
  • 外参在线估计
  • 时间同步处理

6.4 ESIKF性能优势

  1. 高精度: 迭代优化处理非线性,流形表示保持几何约束
  2. 实时性: 高效的矩阵运算和稀疏化处理
  3. 鲁棒性: 自适应噪声和异常检测
  4. 可扩展性: 模块化设计支持不同传感器配置

7. 系统特点与优势

7.1 技术特点

  1. 高效数据结构: ikd-tree支持O(log n)的插入、删除和搜索
  2. 流形状态表示: 正确处理旋转群SO(3)和球面S2的几何约束
  3. 迭代优化: 处理非线性观测模型的迭代卡尔曼滤波
  4. 自适应地图: 动态局部地图管理,保持计算效率
  5. 误差状态滤波: ESIKF避免直接对非线性状态进行滤波
  6. 紧耦合融合: IMU和LiDAR数据在统一框架下融合

7.2 性能优势

  1. 实时性: 前端ESIKF实现实时状态估计,后端异步优化
  2. 高精度: 紧耦合融合和迭代优化提供高精度位姿估计
  3. 鲁棒性: 多层次的异常检测、自适应噪声处理机制
  4. 可扩展性: 模块化设计支持不同传感器配置
  5. 全局一致性: GTSAM后端优化保证全局轨迹一致性

7.3 应用场景

  • 移动机器人导航: 室内外环境的实时建图和定位
  • 自动驾驶车辆: 高精度地图构建和车辆定位
  • 无人机SLAM: 空中平台的三维环境感知
  • 增强现实(AR): 实时环境理解和虚拟物体叠加
  • 工业检测: 三维重建和质量检测

8. 总结

FAST-LIO2通过ESIKF前端和GTSAM后端的结合,实现了IMU和激光雷达的高效紧耦合融合,具有以下核心优势:

8.1 前端ESIKF优势

  1. 误差状态表示: 避免非线性状态的直接滤波,提高数值稳定性
  2. 流形几何处理: 正确处理旋转和重力约束的几何性质
  3. 迭代优化: 通过迭代减少线性化误差,提高估计精度
  4. 自适应策略: 动态调整观测权重,增强系统鲁棒性

8.2 后端GTSAM优势

  1. 因子图优化: 全局优化保证轨迹和地图的一致性
  2. 增量式处理: ISAM2实现高效的增量优化
  3. 回环检测: 自动检测和校正累积误差
  4. 异步处理: 不影响前端实时性能

8.3 系统整体优势

  1. 统一状态表示: 所有传感器信息融合到统一的状态空间
  2. 连续运动建模: IMU提供高频运动信息,实现精确的运动补偿
  3. 几何约束优化: 激光雷达提供几何约束,校正IMU累积误差
  4. 实时性能: 前端实时处理,后端异步优化
  5. 自适应管理: 动态地图管理和智能点云更新策略

整个系统形成了一个完整的SLAM解决方案,在保证实时性的同时提供了高精度的建图和定位能力,是当前激光雷达-惯性里程计领域的先进技术代表。

About

fast-lio-with-loopclosure

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published