【3D激光SLAM】 LIO_SAM学习——IMU预积分imuPreintegration

前言

LIO_SAM一共有四个cpp文件,接下来对其中一个imuPreintegration.cpp文件,根据数据流程进行详细的讲解,不足的地方请大家批评指证
该文件主要包括:一个主函数,两个回调函数(odometryHandler()imuHandler()

一、主函数main()

主要是创建两个对象,调用构造函数

int main(int argc, char** argv)
{
    //节点初始化
    ros::init(argc, argv, "roboat_loam");
    
    //创建 IMUPreintegration 的一个对象,会调用构造函数IMUPreintegration ()
    IMUPreintegration ImuP;
    
    //创建 TransformFusion 的一个对象,会调用构造函数TransformFusion ()
    TransformFusion TF;
    
    //打印
    ROS_INFO("\033[1;32m----> IMU Preintegration Started.\033[0m");
    
    //开四个线程 通过并发的方式使得速度得到提升
    ros::MultiThreadedSpinner spinner(4);
    spinner.spin();
    return 0;
}

二、构造函数IMUPreintegration ()

IMUPreintegration ()构造函数,主要包含:两个订阅与一个发布器
两个订阅的回调函数为:
imuHandler() 订阅高频率的IMU数据;
odometryHandler()订阅 低频率的lidar里程计;

发布器:pubImuOdometry 发布器 ,发布高频率的imu里程计,在imuHandler()中执行

 // @brief 构造函数包含两个订阅与与一个发布
IMUPreintegration()
{
    // 订阅imu原始数据    用下面因子图优化的结果,施加两帧之间的imu预计分量,预测每一时刻(imu频率)的imu里程计
    subImu      = nh.subscribe<sensor_msgs::Imu>  (imuTopic,   2000, &IMUPreintegration::imuHandler, this, ros::TransportHints().tcpNoDelay());
    // 订阅激光里程计,来自mapOptimization,用两帧之间的imu预计分量构建因子图,优化当前帧位姿(这个位姿仅用于更新每时刻的imu里程计,以及下一次因子图优化
    subOdometry = nh.subscribe<nav_msgs::Odometry>("lio_sam/mapping/odometry_incremental", 5,    &IMUPreintegration::odometryHandler, this, ros::TransportHints().tcpNoDelay());
    // 发布imu里程计
    pubImuOdometry = nh.advertise<nav_msgs::Odometry> (odomTopic+"_incremental", 2000);
    // 设置imu预积分重力参数
    boost::shared_ptr<gtsam::PreintegrationParams> p = gtsam::PreintegrationParams::MakeSharedU(imuGravity);
    // 设置加速度噪声
    p->accelerometerCovariance  = gtsam::Matrix33::Identity(3,3) * pow(imuAccNoise, 2); // acc white noise in continuous
    // 设置陀螺仪噪声
    p->gyroscopeCovariance      = gtsam::Matrix33::Identity(3,3) * pow(imuGyrNoise, 2); // gyro white noise in continuous
    // 设置积分噪声
    p->integrationCovariance    = gtsam::Matrix33::Identity(3,3) * pow(1e-4, 2); // error committed in integrating position from velocities
    // 设置初始偏置
    gtsam::imuBias::ConstantBias prior_imu_bias((gtsam::Vector(6) << 0, 0, 0, 0, 0, 0).finished());; // assume zero initial bias
    // 先验位姿噪声 包括姿态以及位置
    priorPoseNoise  = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6) << 1e-2, 1e-2, 1e-2, 1e-2, 1e-2, 1e-2).finished()); // rad,rad,rad,m, m, m
    // 先验速度噪声
    priorVelNoise   = gtsam::noiseModel::Isotropic::Sigma(3, 1e4); // m/s
    // 先验偏置噪声
    priorBiasNoise  = gtsam::noiseModel::Isotropic::Sigma(6, 1e-3); // 1e-2 ~ 1e-3 seems to be good
    // 激光里程计scan-to-map优化过程中若发生退化  则选择一个较大的方差
    // 如果输入的lidar里程计有较大的误差,那么pose噪声就使用correctionNoise2,一般情况用correctionNoise
    correctionNoise = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6) << 0.05, 0.05, 0.05, 0.1, 0.1, 0.1).finished()); // rad,rad,rad,m, m, m
    correctionNoise2 = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6) << 1, 1, 1, 1, 1, 1).finished()); // rad,rad,rad,m, m, m
    noiseModelBetweenBias = (gtsam::Vector(6) << imuAccBiasN, imuAccBiasN, imuAccBiasN, imuGyrBiasN, imuGyrBiasN, imuGyrBiasN).finished();
    //两个imu预计分器
    // imu预积分器   用于预测每一时刻的imu里程计   (转到lidar系了   与激光里程计同一个系)
    imuIntegratorImu_ = new gtsam::PreintegratedImuMeasurements(p, prior_imu_bias); // setting up the IMU integration for IMU message thread
    // imu预积分器  用于因子图优化
    imuIntegratorOpt_ = new gtsam::PreintegratedImuMeasurements(p, prior_imu_bias); // setting up the IMU integration for optimization        
}

三、imuHandler()

订阅imu的原始数据,imu原始数据到来之后,先用imuConverter()转换到雷达系下,但其实和雷达之间仍然差了一个平移,然后将转换后的imu数据,分别放到两个双端队列中(imuQueOpt与 imuQueImu),简单介绍一下这两个队列,后面会详细解释,imuQueOpt用来预测每个imu时刻的imu里程计,而imuQueImu用于因子图优化。初始的时候doneFirstOpt 为false,函数return

void imuHandler(const sensor_msgs::Imu::ConstPtr& imu_raw)
    {
        // 上锁  添加数据时队列不可用
        std::lock_guard<std::mutex> lock(mtx);
        // imu原始测量数据转换到lidar系  数据包括? 加速度  角速度 rpy
        sensor_msgs::Imu thisImu = imuConverter(*imu_raw);

        // 添加当前帧imu数据到队列
        // 两个双端队列分别装着优化前后的imu数据
        imuQueOpt.push_back(thisImu);   //用于预测每个时刻(IMU频率)imu里程计  用来执行预积分和位姿变换的优化
        imuQueImu.push_back(thisImu);   //用于因子图优化     用来更新最新imu状态

        // 要求上一次imu因子图优化执行成功,确保更新了上一帧(激光里程计帧)的状态、偏置,预积分已经被重新计算
        // 这里需要先在odomhandler中优化一次后再进行该函数后续的工作
        if (doneFirstOpt == false)
            return;
            
     后面内容咱暂时省略。。。。。。。。。。。。。。。

四、odometryHandler()

很多篇博客都说,这个函数比较抽象,难理解,接下一起走进这个函数,逐行讲解

4.1 输入的雷达里程计预处理

订阅雷达里程计数据,获取当前激光帧里程计的时间戳,odomMsg来自scan-to-map匹配、因子图优化后的位姿,然后用一标志位,告诉你这个lidar pose准不准,1是不准,0是准,分别对应了不同的方差, 然后将位姿转换为gtsam格式

// 订阅的是激光里程计,"lio_sam/mapping/odometry_incremental"  由mapoptimization发布的 
void odometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
{
    std::lock_guard<std::mutex> lock(mtx);
    // 当前激光帧里程计时间戳     此处的odomMsg数据 是相对与odom坐标系的绝对数据
    double currentCorrectionTime = ROS_TIME(odomMsg);

    // make sure we have imu data to integrate
    // 确保imu优化队列中有imu数据进行预积分
    if (imuQueOpt.empty())
        return;

    // 当前帧激光位姿,来自scan-to-map匹配、因子图优化后的位姿
    float p_x = odomMsg->pose.pose.position.x;
    float p_y = odomMsg->pose.pose.position.y;
    float p_z = odomMsg->pose.pose.position.z;
    float r_x = odomMsg->pose.pose.orientation.x;
    float r_y = odomMsg->pose.pose.orientation.y;
    float r_z = odomMsg->pose.pose.orientation.z;
    float r_w = odomMsg->pose.pose.orientation.w;
    // 判断扫描匹配的过程中是否发生退化 为1 则 雷达里程计有退化风险,该帧位姿精度有一定程序下降
    //  odomMsg->pose.covariance[0],这个东西不是方差,放了一个标志位,
    // 告诉你这个lidar pose准不准,1是不准,0是准,分别对应了不同的方差 
    bool degenerate = (int)odomMsg->pose.covariance[0] == 1 ? true : false;
    //imu预积分用到gtsam  
    // 定义gtsam格式 的当前雷达的位姿(此处应该是基于map坐标系的位姿)
    // 将位姿转成 gtsam的格式
    gtsam::Pose3 lidarPose = gtsam::Pose3(gtsam::Rot3::Quaternion(r_w, r_x, r_y, r_z), gtsam::Point3(p_x, p_y, p_z));

4.2 系统初始化------输入第一帧激光里程计数据

第一帧激光里程计进来时,systemInitialized 初始时为false, resetOptimization()重置isam2优化器

        //initialize system
        // 系统初始化 第一帧
        if (systemInitialized == false)
        {
            // 重置isam2优化器
            resetOptimization();
            紧着下文

          

分析一下resetOptimization()
在GTSAM图优化里,优化器optimizer,因子图graphFactors和状态信息graphValues是三个重要的工具对象。

// 重置优化器   优化器 有什么作用实在想不懂   imu预积分还要什么优化器
    void resetOptimization()
    {
        gtsam::ISAM2Params optParameters;
        optParameters.relinearizeThreshold = 0.1;
        optParameters.relinearizeSkip = 1;
        optimizer = gtsam::ISAM2(optParameters);

        gtsam::NonlinearFactorGraph newGraphFactors;
        graphFactors = newGraphFactors;

        gtsam::Values NewGraphValues;
        graphValues = NewGraphValues;
    }

书接上文,重置isam2优化器后,从mu队列imuQueOpt中删除当前帧激光里程计时刻之前的imu数据, imuQueOpt存储的imu信息用于预积分,lastImuT_opt 记录着,早于当前帧激光里程计时间戳,且离其最近的一帧imu数据时间戳

            // 从imu队列中删除当前帧激光里程计时刻之前的imu数据  delta_t=0
            while (!imuQueOpt.empty())
            {
                // 从imuQueOpt队列中  删除时间戳在 当前激光里程计时间戳之前的imu数据  delta_t=0
                if (ROS_TIME(&imuQueOpt.front()) < currentCorrectionTime - delta_t)
                {
                    //记录当前激光里程计数据中 时间上早于其且离其最近的一帧IMU数据 
                    lastImuT_opt = ROS_TIME(&imuQueOpt.front());
                    //弹出IMU数据
                    imuQueOpt.pop_front();
                }
                else
                    break;
            }
            

初始化pose,往因子图添加里程计位姿先验因子
补充一下:imuConverter()并没有涉及到平移,lidar2Imu or imu2Lidar 却只有平移的内容

            //initial pose
            //lidarPose 为本回调函数收到的激光里程计数据,重组成gtsam格式的pose
            //并转到imu坐标系下,猜测compose可能类似于左乘之类的含义吧
            //得到激光帧先验位姿(lidarPose)转换到imu系下,(相当于从真正的雷达系扭到上面的中间系中)
            prevPose_ = lidarPose.compose(lidar2Imu);   
            
            //X可能是固定搭配(当使用Pose时),如果是速度则是V,bias则是B
            // 此处定义一个模型     第一个参数:X(0)表示模型的名字    第二个参数:模型的均值      第三个参数:模型的噪声  
            gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, priorPoseNoise);
            //通过调用总的因子图模型的add方式,添加第一个因子
            //PriorFactor 概念可看gtsam  包括了位姿 速度  bias 
            //加入PriorFactor在图优化中基本都是必需的前提
            //各种noise都定义在构造函数当中
            
            // 往因子图里添加里程计位姿先验因子:
            graphFactors.add(priorPose);   //理论模型计算值  prevPose_是因子priorPose的数值

初始化速度,添加速度先验因子

            // initial velocity
            prevVel_ = gtsam::Vector3(0, 0, 0);
            gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, priorVelNoise);
            graphFactors.add(priorVel); //添加速度先验因子

初始化imu偏置,添加imu偏置先验因子

            // initial bias
            prevBias_ = gtsam::imuBias::ConstantBias();    //imuBias因子  后面使用时可能会用到
            gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, priorBiasNoise);
            graphFactors.add(priorBias); //添加imu偏置先验因子

添加状态信息

            // add values
            // 变量节点赋初值
            // 实际测量值
            // 添加状态信息
            graphValues.insert(X(0), prevPose_);
            graphValues.insert(V(0), prevVel_);
            graphValues.insert(B(0), prevBias_);

进行优化,输入graphFactors因子图与graphValues状态信息

            // optimize once
            // 优化一次
            // 思考此处的优化 与mapoptimaztion中的优化有何区别(暂时研究不出来区别)    以及其为什么需要优化啊? 预积分还需要优化? 好迷啊
            // 给各个状态量赋成初始值
            
            // graphFactors因子图, graphValues状态信息
            optimizer.update(graphFactors, graphValues);

因子图与状态信息清空

            //图和节点均清零  
            //节点信息保存在gtsam::ISAM2 optimizer,所以要清理后才能继续使用
            graphFactors.resize(0);
            graphValues.clear();
            //积分器重置,重置优化之后的偏置
            // 预积分的接口,使用初始零偏进行初始化 之前imu有两个队列,每个队列对应预积分处理器
           // 把优化后的prevBias_给到预积分器
            imuIntegratorImu_->resetIntegrationAndSetBias(prevBias_);
            imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);

初始化完成,key 用于计数,然后return,多说一句,key累加到100,一个循环

         key = 1;
         systemInitialized = true;
         return;
     }

imu的频率较快,比激光雷达快 ,存在imuTime < currentCorrectionTime,
计算前一帧与当前帧之间的imu预积分量,用前一帧状态施加预积分量得到当前帧初始状态估计,
添加来自mapOptimization的当前帧位姿,进行因子图优化,更新当前帧状态
IMU数据(加速度、角速度、dt)导入到用于优化的IMU预积分器里

while (!imuQueOpt.empty())
    {
        // 提取前一帧与当前帧之间的imu数据,计算预积分

       //获取imu第一帧数据的时间戳
        sensor_msgs::Imu *thisImu = &imuQueOpt.front();
        //转成ROS时间
        double imuTime = ROS_TIME(thisImu);
        
        // cwx
        // 因为初始化后直接return
        // 由于imu频率比较快,比激光雷达快 ,存在imuTime < currentCorrectionTime
        //  currentCorrectionTime  是当前回调函数收到的激光里程计数据的时间
        
        // 初始化时才会剔除, 当前帧之前的imu数据并直接退出,其他非初始化情况时,早于当前currentCorrectionTime的imu数据反倒不会被剔除而是用于积分计算
        if (imuTime < currentCorrectionTime - delta_t)
        {
            // lastImuT_opt在初始化时,被赋值为离激光雷达当前帧最近的imu数据时间戳,imu时间戳小于激光雷达时间戳,
            // imuTime - lastImuT_opt 计算的是两帧连续imu数据的时间差值
            double dt = (lastImuT_opt < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_opt);
            
            // imu预积分数据输入:加速度、角速度、dt
            // imuIntegratorOpt_用来因子图优化的预积分器, 注意加入了上一步算出的dt
            //作者要求的9轴imu数据中欧拉角在本程序文件中没有任何用到,全在地图优化里用到的
            imuIntegratorOpt_->integrateMeasurement(
                    gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z),
                    gtsam::Vector3(thisImu->angular_velocity.x,    thisImu->angular_velocity.y,    thisImu->angular_velocity.z), dt);
             //在弹出一次数据前保存上一个数据的时间戳
            lastImuT_opt = imuTime;    
            // 从队列中删除已经处理的imu数据
            imuQueOpt.pop_front();
        }
        else
            break;
    }

添加imu因子到因子图

     //利用两帧之间的IMU数据完成了预积分后增加imu因子到因子图中,
     //注意后面容易被遮挡,imuIntegratorOpt_的值经过格式转换被传入preint_imu,
     //因此可以推测imuIntegratorOpt_中的integrateMeasurement函数应该就是一个简单的积分轮子(可以直接套用),
     //传入数据和dt,得到一个积分量,数据会被存放在imuIntegratorOpt_中
     // dynamic_cast<将要转换成的新类型> data   显式类型转换
     const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements&>(*imuIntegratorOpt_);

     // 参数:前一帧位姿,前一帧速度,当前帧位姿,当前帧速度,前一帧偏置,预积分量  注意此帧指的是(激光雷达帧)
     // ImuFactor 因子  表征的两帧激光帧之间的imu积分变换量
     gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu);
     graphFactors.add(imu_factor);

添加imu bias因子到因子图

     // add imu bias between factor
     // 添加imu偏置因子,前一帧偏置B(key - 1),当前帧偏置B(key),观测值,噪声协方差;deltaTij()是积分段的时间
     graphFactors.add(gtsam::BetweenFactor<gtsam::imuBias::ConstantBias>(B(key - 1), B(key), gtsam::imuBias::ConstantBias(),
     gtsam::noiseModel::Diagonal::Sigmas(sqrt(imuIntegratorOpt_->deltaTij()) * noiseModelBetweenBias)));

添加位姿因子到因子图:根据位姿准不准,它添加的协方差是不一样的

     // add pose factor
     // 添加位姿因子
     // 转到imu坐标系,只有平移
     gtsam::Pose3 curPose = lidarPose.compose(lidar2Imu);
     // 先验因子  此处定义了位姿因子的模型值curPose来自  mapoptimization
     // 根据位姿准不准,它添加的协方差是不一样的
     gtsam::PriorFactor<gtsam::Pose3> pose_factor(X(key), curPose, degenerate ? correctionNoise2 : correctionNoise);
     graphFactors.add(pose_factor);

prevState_前一帧状态,prevBias_前一帧偏执,这里前一帧是指激光雷达,预测出当前时刻的数据
现在呢,从上一帧到当前帧之间的IMU有了,上一帧状态量也有,当前帧输入的里程计也有,那么就可以获取图优化之后当前帧的一个初始预测值了:

     // insert predicted values
     // 用前一帧的状态、偏置,施加imu预计分量,得到当前帧的状态   (此处封装了成一个轮子   其内部如何实现预积分的我也不清楚)
     // 那么此处得到的是依据上一时刻数据(prevState,prevBias)得到的当前时刻的数据
     gtsam::NavState propState_ = imuIntegratorOpt_->predict(prevState_, prevBias_);

graphValues状态信息赋值

     // 变量节点赋初值
     graphValues.insert(X(key), propState_.pose());
     graphValues.insert(V(key), propState_.v());
     graphValues.insert(B(key), prevBias_);

优化

     // optimize
     optimizer.update(graphFactors, graphValues);
     optimizer.update();

重置

     // 重置
     graphFactors.resize(0);
     graphValues.clear();

优化结果result

     // 优化结果
     gtsam::Values result = optimizer.calculateEstimate();

用当前帧位姿 速度更新上一帧位姿 速度,下个循环要用:

        // 更新当前帧位姿 速度
        // 此处的优化结果 结合了扫描匹配后 经过因子图优化的激光里程计的数据
        // 同时利用上一帧和两帧之间Imu预积分数据  进行一次因子图优化
        prevPose_  = result.at<gtsam::Pose3>(X(key));
        prevVel_   = result.at<gtsam::Vector3>(V(key));
        // 更新当前帧的姿态
        prevState_ = gtsam::NavState(prevPose_, prevVel_);
        // 更新当前帧imu偏置
        prevBias_  = result.at<gtsam::imuBias::ConstantBias>(B(key));
        // Reset the optimization preintegration object.
        // 重置预积分器  设置新的偏置  这样下一帧激光里程计进来的时候  预积分量就是两帧之间的增量
        imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
        // check optimization
        // imu因子图优化的结果  速度过大或者偏置过大  认为失败
        if (failureDetection(prevVel_, prevBias_))
        {
            resetParams();
            return;
        }

4.2.构建用于imu里程计的预积分器

从imuQueImu队列把当前帧之前的imu数据都给直接剔除掉,仅保留当前帧之后的imu数据

     // 优化过后 执行重传播  优化更新了Imu的偏置
     // 用最新的偏置重新计算当前激光里程计时刻之后的Imu预积分  这个预积分用于计算每时刻的位姿
     prevStateOdom = prevState_;
     prevBiasOdom  = prevBias_;

     // 从imu队列中删除当前激光里程计时刻之前的imu数据
     double lastImuQT = -1;
     
     //注意,这里是要“删除”当前帧“之前”的imu数据,是想根据当前帧“之后”的累积递推。 
      
     //前面imuIntegratorOpt_做的事情是,“提取”当前帧“之前”的imu数据,用两帧之间的imu数据进行积分,处理过的数据就弹出来。
     //因此,新到一帧激光帧里程计数据时,imuQueOpt队列变化如下:
     //当前帧之前的数据被提出来做积分,用一个删一个(这样下一帧到达后,队列中就不会有现在这帧之前的数据了)
     //那么在更新完以后,imuQueOpt队列不再变化,剩下的原始imu数据用作下一次优化时的数据。

     //而imuQueImu队列则是把当前帧之前的imu数据都给直接剔除掉,仅保留当前帧之后的imu数据,
     //用作两帧lidar里程计到达时刻之间发布的imu增量式里程计的预测。
     //imuQueImu和imuQueOpt的区别要明确,imuIntegratorImu_和imuIntegratorOpt_的区别也要明确,见imuhandler中的注释

     // imuQueImu   当前帧雷达之前的imu数据都给直接剔除掉,仅保留当前帧之后的imu数据,
     // 用这之后imu的数据,计算相邻两个imu之间的imu增量式里程计
     while (!imuQueImu.empty() && ROS_TIME(&imuQueImu.front()) < currentCorrectionTime - delta_t)
     {
         lastImuQT = ROS_TIME(&imuQueImu.front());
         imuQueImu.pop_front();
     }

对剩余的imu数据计算预积分

     // 对剩余的imu数据计算预积分
     if (!imuQueImu.empty())
     {
         // reset bias use the newly optimized bias
         // 传入状态 重置预积分器和最新的偏置
         imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom);
         // integrate imu message from the beginning of this optimization
         // 计算预积分
         // 利用imuQueImu中的数据进行预积分  主要区别在于上一行更新了bias
         for (int i = 0; i < (int)imuQueImu.size(); ++i)
         {
             sensor_msgs::Imu *thisImu = &imuQueImu[i];
             double imuTime = ROS_TIME(thisImu);
             double dt = (lastImuQT < 0) ? (1.0 / 500.0) :(imuTime - lastImuQT);
            
             // 注意:加入的是这个用于传播的的预积分器imuIntegratorImu_,(之前用来计算的是imuIntegratorOpt_,)
             //注意加入了上一步算出的dt
             //结果被存放在imuIntegratorImu_中
             imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y, thisImu->linear_acceleration.z),
                                                     gtsam::Vector3(thisImu->angular_velocity.x,    thisImu->angular_velocity.y,    thisImu->angular_velocity.z), dt);
             lastImuQT = imuTime;
         }
     }

     ++key;
     //设置成True,用来通知另一个负责发布imu里程计的回调函数imuHandler“可以发布了”
     doneFirstOpt = true;
 }
/**

再进入imuHandler之前,插播一个key==100

4.3 每100个激光雷达里程计,重置优化器(与初始化时对比)

     // 每隔100帧激光里程计,重置 ISAM2 优化器,保证优化效率
     if (key == 100)
     {
         // get updated noise before reset
         // 前一帧的位姿、速度、偏置噪声模型
         //保存最后的噪声值  
         // 将上一imu原始时刻 预积分得到的   边界方差(边缘分布)   作为新模型的起始协方差
         gtsam::noiseModel::Gaussian::shared_ptr updatedPoseNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(X(key-1)));
         gtsam::noiseModel::Gaussian::shared_ptr updatedVelNoise  = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(V(key-1)));
         gtsam::noiseModel::Gaussian::shared_ptr updatedBiasNoise = gtsam::noiseModel::Gaussian::Covariance(optimizer.marginalCovariance(B(key-1)));
         // reset graph
         // 重置isam2优化器
         resetOptimization();
         
         // 添加位姿先验因子,用前一帧的值初始化
         //重置之后还有类似与初始化的过程 区别在于噪声值不同
         //prevPose_,prevVel_,priorBias三项,也是上一时刻得到的,
         //(初始时刻是lidar里程计的pose直接用lidar2IMU变量转到imu坐标系下,而此处则是通过上一时刻,即接下来的后续优化中得到)
         gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, updatedPoseNoise);
         graphFactors.add(priorPose);
         
        // 添加速度先验因子,用前一帧的值初始化
         gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, updatedVelNoise);
         graphFactors.add(priorVel);

       //添加偏置先验因子
         gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, updatedBiasNoise);
         graphFactors.add(priorBias);
         
         // 添加状态值
         graphValues.insert(X(0), prevPose_);
         graphValues.insert(V(0), prevVel_);
         graphValues.insert(B(0), prevBias_);
         
         //优化
         optimizer.update(graphFactors, graphValues);
         graphFactors.resize(0);
         graphValues.clear();

         key = 1;
     }

五 imu里程计回调函数imuHandler()

 void imuHandler(const sensor_msgs::Imu::ConstPtr& imu_raw)
 {
     // 上锁  添加数据时队列不可用
     std::lock_guard<std::mutex> lock(mtx);
     // imu原始测量数据转换到lidar系,只有旋转没有平移  数据包括 加速度  角速度 rpy
     sensor_msgs::Imu thisImu = imuConverter(*imu_raw);

添加当前帧imu数据到imuQueOpt与imuQueImu队列

     // 添加当前帧imu数据到队列
     // 两个双端队列分别装着优化前后的imu数据
     imuQueOpt.push_back(thisImu);   //用于预测每个时刻(IMU频率)imu里程计  用来执行预积分和位姿变换的优化
     imuQueImu.push_back(thisImu);   //用于因子图优化     用来更新最新imu状态

确保更新了上一帧(激光里程计帧)的状态

     // 要求上一次imu因子图优化执行成功,确保更新了上一帧(激光里程计帧)的状态、偏置,预积分已经被重新计算
     // 这里需要先在odomhandler中优化一次后再进行该函数后续的工作
     if (doneFirstOpt == false)
         return;

     double imuTime = ROS_TIME(&thisImu);
     //lastImuT_imu变量初始被赋值为-1
     // 获得时间间隔, 第一次为1/500,之后是两次imuTime间的差
     double dt = (lastImuT_imu < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_imu);
     // 更新上一帧的时间
     lastImuT_imu = imuTime;

imu预积分器添加最新一帧imu数据

     // imu预积分器添加一帧imu数据,注:这个预积分器的起始时刻是上一帧激光里程计时刻
     imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu.linear_acceleration.x, thisImu.linear_acceleration.y, thisImu.linear_acceleration.z),
                                             gtsam::Vector3(thisImu.angular_velocity.x,    thisImu.angular_velocity.y,    thisImu.angular_velocity.z), dt);

当前帧激光里程计的预测值
用上一帧激光里程计时刻对应的状态、偏置,施加从该时刻开始到当前时刻的imu预计分量,得到当前时刻的状态

      // 用上一帧激光里程计时刻对应的状态、偏置,施加从该时刻开始到当前时刻的imu预计分量,得到当前时刻的状态  imu_incremental
     gtsam::NavState currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom);

发布imu里程计

     // 发布imu里程计(转到lidar系,与激光里程计同一个系)
     nav_msgs::Odometry odometry;
     odometry.header.stamp = thisImu.header.stamp;
     odometry.header.frame_id = odometryFrame;      //odom坐标系
     odometry.child_frame_id = "odom_imu";

预测值currentState获得imu位姿, 再由imu坐标变换到雷达坐标, 获得雷达位姿

     // transform imu pose to ldiar
     //预测值currentState获得imu位姿, 再由imu到雷达变换, 获得雷达位姿
     gtsam::Pose3 imuPose = gtsam::Pose3(currentState.quaternion(), currentState.position());
     // 将中间坐标系的姿态转移的真正的lidar坐标系下
     gtsam::Pose3 lidarPose = imuPose.compose(imu2Lidar);
   
     //在更新的过程中不用担心会被imuHandler中的imuIntegratorImu_->integrateMeasurement给顶掉,
     //这是因为imuHandler要根据doneFirstOpt来检查odometryHandler是不是已经更新完bias了。
     //因为更新并不是实时的,而是一帧激光数据到了才更新。
     //可是下一帧激光并没有到,但是在此期间imu增量式里程计还是得照常发布,
     //如果当前帧已经更新完bias了,然后就可以直接利用这个bias计算后面新到的ImuIntegratorImu_,

     odometry.pose.pose.position.x = lidarPose.translation().x();
     odometry.pose.pose.position.y = lidarPose.translation().y();
     odometry.pose.pose.position.z = lidarPose.translation().z();
     odometry.pose.pose.orientation.x = lidarPose.rotation().toQuaternion().x();
     odometry.pose.pose.orientation.y = lidarPose.rotation().toQuaternion().y();
     odometry.pose.pose.orientation.z = lidarPose.rotation().toQuaternion().z();
     odometry.pose.pose.orientation.w = lidarPose.rotation().toQuaternion().w();
     
     odometry.twist.twist.linear.x = currentState.velocity().x();
     odometry.twist.twist.linear.y = currentState.velocity().y();
     odometry.twist.twist.linear.z = currentState.velocity().z();
     odometry.twist.twist.angular.x = thisImu.angular_velocity.x + prevBiasOdom.gyroscope().x();
     odometry.twist.twist.angular.y = thisImu.angular_velocity.y + prevBiasOdom.gyroscope().y();
     odometry.twist.twist.angular.z = thisImu.angular_velocity.z + prevBiasOdom.gyroscope().z();
    //imu_incremental
     pubImuOdometry.publish(odometry);
 }
 // 这里留疑问,本cpp读完后补充:
    // 为什么currentState获得的是imu的位姿?原始imu数据难道不是先转换成雷达坐标系下的数据(this imu)才再送到imu预积分器中吗?
    //答: 在之前的优化函数odometryHandler中,thisIMU是直接从imuQueOpt中取值.
    //而imuQueOpt中的内容,是已经从imu原始测量数据转换到了lidar"中间系"系下(在本函数第二行)。离真正的雷达系还差了一个平移
    //odometryHandler函数中根据prevPose_ = lidarPose.compose(lidar2Imu)得到激光帧先验位姿(lidarPose)转换到imu系下,(相当于从真正的雷达系扭到上面的中间系中)
    //作为初值构建了因子进行优化;
    //在其imuIntegratorOpt_->integrateMeasurement中得到的应该是dt之间的预积分量,
    //由于其处在循环中,因此是在递推累积计算两帧之间的预积分量。
    //(相当于是每到一帧,就把二者之间的预积分量计算一遍,并且优化一遍,存储进imuIntegratorOpt_)中。

    //因为本函数为imu回调函数,每收到一个imu数据,当之前因子图算完的情况下,
    //在imuIntegratorImu_的基础上继续递推新收到的imu数据,并且进行预测。
    //最后把imu再转回lidar系下进行发布。
    //注意:这里发布的是两帧之间的“增量”imu里程计信息,
    //imuIntegratorImu_本身是个积分器,只有两帧之间的预积分,但是发布的时候发布的实际是结合了前述里程计本身有的位姿
    //如这个predict里的prevStateOdom:
    //currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom);

    //关于imuIntegratorImu_在两个回调函数中都进行integrateMeasurement操作,之间是否会有冲突呢?
    //我觉得关键在于odometryHandler中有一句:imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom),
    //在imuIntegratorOpt_优化完两帧imu数据之后,imuIntegratorImu_直接把积分和bias给reset掉,
    //然后开始根据imuIntegratorOpt_优化出的bias来更新imuIntegratorImu_。

    //imuIntegratorImu_和imuIntegratorOpt_的区别在于,opt存储的是新到一帧,和上一帧之间的预积分量,作为约束,执行优化。
    //优化后,imuIntegratorImu_利用新的bias,在新到的这一帧的基础上,递推“之后”的预积分量。
    //(绝对不能把imuIntegratorOpt_和imuIntegratorImu_理解为同一批imu数据在优化前后的不同值)
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值