话说SLAM中的点云上采样

news2024/12/26 10:34:50

目录

1 什么是点云上采样,为什么需要点云上采样

2 以LIO-SAM为例进行方法1的讲解

2.1 IMU预积分代码解析 + 点云加密思想

2.1.1 收到IMU信息的回调函数  

2.2.2 优化函数置位  resetOptimization

2.2.3 TF类

2.3.3.1 流程图

2.3.3.2 代码详细注释

2.2.4 总结

2.2 图像投影代码解析 + 点云加密思想

2.2.1 本部分流程图

2.2.2 构造函数解析

2.2.3  IMU回调函数解析

2.2.4  后端里程计回调函数

2.2.5 原始点云回调函数

2.2.5.1 缓存点云信息函数cachePointCloud 

2.5.5.2  获取运动补偿所需的信息 deskewInfo

**2.5.5.3 把点云投影到一个矩阵上 保存每个点的信息 projectPointCloud

2.5.5.4  剔除点的信息 cloudExtraction

2.5.5.5 发布点云信息 publishClouds

2.5.5.6 系统置位  resetParameters

2.2.6 总结

2.3 提取特征点代码解析 + 点云加密思想

2.3.1 流程图

2.3.2 遮挡点判断的数学原理

2.3.3 计算曲率calculateSmoothness

2.3.4 移除遮挡点markOccludedPoints

***2.3.5 特征提取extractFeatures

2.3.6 发布点云信息

2.4 地图优化代码解析 + 点云加密思想(地图优化部分)

2.4.1 构造函数分析 mapOptimization()

2.4.2  laserCloudInfoHandler 回调函数解析

2.4.3  updateInitialGuess函数解析

****2.4.4  提取当前帧的相关关键帧并且构建点云局部地图extractSurroundingKeyFrames

****2.4.5  对当前帧进行下采样downsampleCurrentScan

2.4.6 scan2MapOptimization 求得一个Rt使得当前帧能最好的匹配到局部地图上面去

2.4.7 判断是否插入关键帧 saveKeyFramesAndFactor

2.4.8 调整全局轨迹 correctPoses

2.4.9  发布lidar里程计信息 publishOdometry

2.4.10 发布点云关键帧信息

2.5 地图优化代码解析 + 点云加密思想(地图优化部分)

2.5.1 闭环检测主进程

2.5.2 执行回环检测

2.5.3 检测有没有外部通知的回环信息detectLoopClosureExternal

2.5.4 检测内部回环 detectLoopClosureDistance

****2.5.5 取出回环候选帧的点云  loopFindNearKeyframes

2.5.6 执行ICP计算位姿变换  ICP solver

2.5.7 发布可视化回环信息 /lio_sam/mapping/loop_closure_constraints

2.6 地图保存代码解析 + 点云加密思想(地图优化部分)

****2.6.1 发布全局地图 publishGlobalMap

2.6.2 保存地图saveMapService


1 什么是点云上采样,为什么需要点云上采样

        点云上采样,又被称为点云密集化,旨在将稀疏点云作为输入,并生成密集点云作为输出。在众多SLAM(Simultaneous Localization and Mapping,同时定位与地图构建)算法中,许多算法的设计目标是用于自动驾驶或者嵌入到边缘计算设备中(如TX2、STM32)。

由于这些背景要求下的计算时间复杂度至关重要,研究者通常对传感器生成的点云进行处理。例如,对于基于视觉SLAM的算法,点云通常通过深度图、双目测距或三角测量等方式生成。为了控制计算复杂度,研究者会对这些点云进行下采样,以减少点的数量。然而,在某些特定场景下,需要获得密集的点云数据,比如在古建筑扫描、林下建图和激光点云赋色等应用中。

        在SLAM领域,点云上采样的方法可以分为以下几种:

        1. **禁用点云下采样的代码部分**(虽然这会增加计算复杂性,但由于非线性优化迭代次数的增加,也会提升系统精度)。

        2. 通过**线性插值**的方式对稀疏点云进行上采样(虽然会影响SLAM系统的精度,但能够使点云呈现更加平滑的外观)。

        3. **提升传感器性能**(通过投入更多资金购买分辨率更高的设备)。

        4.**基于邻域信息的非线性插值**:使用点云中每个点的邻近点信息,进行更复杂的插值,以生成更真实的密集点云。这种方法考虑了点与点之间的关系,可以在保持准确性的同时提升点云密度。

        5.**基于深度学习的生成模型**:利用深度学习技术,如生成对抗网络(GAN)或变分自编码器(VAE),从稀疏点云中生成密集点云。这种方法在一些场景下可以生成更具细节的点云数据。

        6.**多传感器融合**:结合多个传感器的数据,如相机和激光雷达,利用它们的互补优势进行点云融合。这可以提高点云的密度和准确性,尤其在多传感器SLAM系统中。

        这篇文章我们以LIO-SAM为例子进行方法一的讲解。

2 以LIO-SAM为例进行方法1的讲解

2.1 IMU预积分代码解析 + 点云加密思想

        LIO-SAM中IMU预积分部分的流程图如下:

        我们首先看main函数

int main(int argc, char** argv)
{
    ros::init(argc, argv, "roboat_loam");
    
    IMUPreintegration ImuP;

    TransformFusion TF;

    ROS_INFO("\033[1;32m----> IMU Preintegration Started.\033[0m");

    // 分配4个线程执行这个节点
    ros::MultiThreadedSpinner spinner(4);

    // 程序阻塞,后台等待回调函数的执行
    spinner.spin();
    
    return 0;
}

1. `int main(int argc, char** argv)`: 这是C++程序的入口函数,接受命令行参数`argc`和`argv`。

2. `ros::init(argc, argv, "roboat_loam")`: 这行代码初始化了ROS节点,使用了参数`argc`和`argv`,并指定了节点的名称为"roboat_loam"

3. `IMUPreintegration ImuP;`: 这行代码创建了一个名为`ImuP`的`IMUPreintegration`对象。它是一个自定义类的实例化对象,表示进行IMU预积分的功能。

4. `TransformFusion TF;`: 这行代码创建了一个名为`TF`的`TransformFusion`对象。它是一个自定义类的实例化对象,表示进行坐标变换融合的功能。

5. `ROS_INFO("\033[1;32m----> IMU Preintegration Started.\033[0m")`: 这行代码输出一个ROS信息日志,显示"IMU Preintegration Started"。使用了特殊的字符转义序列来设置输出文本的颜色为绿色。

6. `ros::MultiThreadedSpinner spinner(4)`: 这行代码创建了一个`MultiThreadedSpinner`对象,用于多线程执行节点的回调函数。指定了线程数为4,表示使用4个线程执行回调函数。

7. `spinner.spin()`: 这行代码启动了多线程的回调函数执行,并且会阻塞程序,使其等待回调函数的执行。

8. `return 0;`: 这行代码表示程序正常结束,返回0。

        综上所述,这段代码初始化了ROS节点,创建了`IMUPreintegration`和`TransformFusion`对象,并输出一个ROS信息日志。然后,通过多线程的方式执行节点的回调函数,直到所有回调函数执行完成后,程序结束并返回0。

2.1.1 收到IMU信息的回调函数  

    void odometryHandler(const nav_msgs::Odometry::ConstPtr& odomMsg)
    {
        std::lock_guard<std::mutex> lock(mtx);

        // ROS_TIME函数用于获取当前odomMsg的时间戳的值
        double currentCorrectionTime = ROS_TIME(odomMsg);

        // make sure we have imu data to integrate
        // 判断IMU队列是否有数据
        if (imuQueOpt.empty())
            return;

        // 获取里程计 平移 XYZ 旋转xyzw
        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;

        // 有退化风险(里程计的精度有风险下降)
        bool degenerate = (int)odomMsg->pose.covariance[0] == 1 ? true : false;

        // 位姿转化成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));


        // 0. 初始化
        if (systemInitialized == false)
        {
            // 将优化问题复位
            resetOptimization();

            // 把IMU数据扔掉
            while (!imuQueOpt.empty())
            {
                // 如果IMU数据的时间戳 < 当前里程计的时间戳
                if (ROS_TIME(&imuQueOpt.front()) < currentCorrectionTime - delta_t)
                {
                    // 被扔掉IMU数据的时间戳,这个变量最终保存刚好小于当前里程计时间戳的帧
                    lastImuT_opt = ROS_TIME(&imuQueOpt.front());
                    imuQueOpt.pop_front();
                }
                else
                    break;
            }

             添加约束

            // 将LIDAR的位姿转换到IMU坐标系下
            prevPose_ = lidarPose.compose(lidar2Imu);
            gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, priorPoseNoise);
            graphFactors.add(priorPose);

            // gtsam::Vector3表示三自由度速度,由于刚开始确实不知道是多少,直接置为0了
            prevVel_ = gtsam::Vector3(0, 0, 0);
            // 设置其初始值和置信度
            gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, priorVelNoise);
            // 约束加入到因子中
            graphFactors.add(priorVel);



            // gtsam::imuBias::ConstantBias表示IMU零偏   X(Pose3 xyzrpy)  Y(Vel xyz) B(BIAS axayazgxgygz)
            prevBias_ = gtsam::imuBias::ConstantBias();
            // 这段代码是使用GTSAM库中的PriorFactor类创建一个先验因子(PriorFactor),用于对IMU偏差(imuBias::ConstantBias)进行约束。
            //具体解释如下:
            // gtsam::PriorFactor<gtsam::imuBias::ConstantBias>:这是一个GTSAM库中的PriorFactor类的模板实例化,指定了模板参数为gtsam::imuBias::ConstantBias,即IMU偏差的类型。
            // priorBias:这是创建的先验因子对象的名称。
            // B(0):这是一个表示IMU偏差的变量,其中(0)是索引号,用于区分多个偏差变量。这里使用索引号为0的偏差变量。
            // prevBias_:这是一个先前的IMU偏差的变量,它被用作先验因子的先验值。
            // priorBiasNoise:这是一个表示偏差噪声的对象,用于指定对IMU偏差的不确定性。
            // 综合起来,这段代码创建了一个先验因子,它约束了IMU偏差变量B(0)的值必须接近于先前的偏差值prevBias_,并且在计算图优化过程中使用了指定的偏差噪声来表示对这个约束的不确定性。
            gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, priorBiasNoise);
            // 将约束添加到因子中
            graphFactors.add(priorBias);


             添加状态

            // 添加状态量
            graphValues.insert(X(0), prevPose_);
            graphValues.insert(V(0), prevVel_);
            graphValues.insert(B(0), prevBias_);

             约束graphFactors + 状态 graphValues
            // 约束和状态添加完了,开始优化
            optimizer.update(graphFactors, graphValues);
            // 清空变量 方便下一次进行优化
            graphFactors.resize(0);
            graphValues.clear();

            // 预积分的接口,使用初始零值进行初始化
            imuIntegratorImu_->resetIntegrationAndSetBias(prevBias_);
            imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
            
            key = 1;
            // 初始化标志位置为true
            systemInitialized = true;
            return;
        }


        // reset graph for speed
        // 执行100次 当isam优化器中加入较多的约束后,为了避免运算时间变长,直接清空 初始化因子图优化问题
        if (key == 100)
        {
            // get updated noise before reset
            // 取出最新时刻位姿 速度 零偏的协方差矩阵
            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
            // 复位整个优化问题
            resetOptimization();
            // add pose
            gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, updatedPoseNoise);
            graphFactors.add(priorPose);
            // add velocity
            gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, updatedVelNoise);
            graphFactors.add(priorVel);
            // add bias
            gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, updatedBiasNoise);
            graphFactors.add(priorBias);
            // add values
            graphValues.insert(X(0), prevPose_);
            graphValues.insert(V(0), prevVel_);
            graphValues.insert(B(0), prevBias_);
            // optimize once
            optimizer.update(graphFactors, graphValues);
            graphFactors.resize(0);
            graphValues.clear();

            key = 1;
        }


        // 1. 初始化结束后,我们已经有一个LIDAR坐标系转换到IMU坐标系下的一个位姿,下面开始形成预积分的约束
        while (!imuQueOpt.empty())
        {
            // pop and integrate imu data that is between two optimizations
            sensor_msgs::Imu *thisImu = &imuQueOpt.front();
            double imuTime = ROS_TIME(thisImu);
            // IMU的时间戳要小于LIDAR的时间戳
            if (imuTime < currentCorrectionTime - delta_t)
            {
                // 计算两个IMU之间的时间戳
                double dt = (lastImuT_opt < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_opt);
                // 调用预积分接口将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;
                imuQueOpt.pop_front();
            }
            else
                break;
        }

        // 两帧间IMU预积分完成(LIDAR两帧,IMU很多帧)后,将其转换成预积分约束
        const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements&>(*imuIntegratorOpt_);

        // 预积分的约束对相邻两帧之间的位姿 速度 零偏形成约束
        // 上一帧的位姿、速度 当前帧的位姿、速度 上一帧的零偏
        gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu);
        // 约束加入因子图
        graphFactors.add(imu_factor);

        // 这段代码是使用GTSAM库中的BetweenFactor类创建一个因子(BetweenFactor),用于表示两个IMU偏差变量之间的关系。
        // graphFactors:这是一个图(graph)对象,用于存储因子(factor)。
        // gtsam::BetweenFactor<gtsam::imuBias::ConstantBias>:这是一个GTSAM库中的BetweenFactor类的模板实例化,指定了模板参数为gtsam::imuBias::ConstantBias,即IMU偏差的类型。
        // B(key - 1):这是表示先前的IMU偏差变量的变量,其中key - 1是索引号,用于区分多个偏差变量。这里使用索引号为key - 1的偏差变量。
        // B(key):这是表示当前的IMU偏差变量的变量,其中key是索引号,用于区分多个偏差变量。这里使用索引号为key的偏差变量。
        // gtsam::imuBias::ConstantBias():这是一个默认构造的IMU偏差对象,作为两个偏差变量之间的初始估计。
        // gtsam::noiseModel::Diagonal::Sigmas(sqrt(imuIntegratorOpt_->deltaTij()) * noiseModelBetweenBias):这是一个表示噪声模型的对象,用于表示两个偏差变量之间的不确定性。imuIntegratorOpt_->deltaTij()表示IMU积分器在两个时间步之间的时间差,noiseModelBetweenBias是一个噪声模型,通过对两个时间步之间的时间差进行标准化来定义偏差之间的不确定性。
        // 综合起来,这段代码创建了一个因子,它表示了两个IMU偏差变量之间的关系,并使用指定的噪声模型来表示这个关系的不确定性。然后,该因子被添加到一个图对象中,以便在后续的图优化过程中使用。
        graphFactors.add(gtsam::BetweenFactor<gtsam::imuBias::ConstantBias>(B(key - 1), B(key), gtsam::imuBias::ConstantBias(),
                         gtsam::noiseModel::Diagonal::Sigmas(sqrt(imuIntegratorOpt_->deltaTij()) * noiseModelBetweenBias)));

        // 这段代码将激光雷达位姿(lidarPose)与激光雷达坐标系到IMU坐标系之间的变换关系(lidar2Imu)进行组合操作,得到了当前的位姿(curPose)。
        // 这通常用于将激光雷达位姿转换到IMU坐标系下的位姿
        gtsam::Pose3 curPose = lidarPose.compose(lidar2Imu);
        // LIDAR位姿补偿到IMU坐标系下同时根据是否退化选择不同的置信度作为这一帧的先验估计
        gtsam::PriorFactor<gtsam::Pose3> pose_factor(X(key), curPose, degenerate ? correctionNoise2 : correctionNoise);
        // 加入到因子图
        graphFactors.add(pose_factor);

        // 根据上一时刻的状态 结合预积分的结果 对当前状态进行预测  prevState_上一关键帧的位姿 + prevBias_上一关键帧的零偏
        // 这段代码使用GTSAM库中的ImuIntegrator类的predict()函数,基于先前的状态和偏差预测生成一个新的导航状态。
        // gtsam::NavState propState_:这是一个NavState类型的对象,用于存储生成的预测位姿
        // imuIntegratorOpt_:这是一个ImuIntegrator类的指针或引用,用于执行IMU积分和状态预测操作。
        // prevState_:这是一个先前的导航状态对象,作为IMU积分的起始状态。
        // prevBias_:这是一个先前的IMU偏差对象,用作IMU积分的起始偏差。
        // predict(prevState_, prevBias_):这是ImuIntegrator类的成员函数,根据先前的状态和偏差,通过IMU积分预测生成一个新的位姿状态。
        // 综合起来,这段代码使用ImuIntegrator类的predict()函数,基于先前的导航状态和偏差,进行IMU积分预测,生成一个新的导航状态,并将其存储在propState_中。
        // 这个预测的导航状态通常用于在导航和状态估计问题中进行运动预测和滤波更新。
        gtsam::NavState propState_ = imuIntegratorOpt_->predict(prevState_, prevBias_);

        // 预测量作为初始值插入到因子图中
        graphValues.insert(X(key), propState_.pose());
        graphValues.insert(V(key), propState_.v());
        graphValues.insert(B(key), prevBias_);

        // 执行因子图优化
        optimizer.update(graphFactors, graphValues);
        optimizer.update();

        // 清空
        graphFactors.resize(0);
        graphValues.clear();

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

        // 获取优化后的当前状态作为当前帧的最佳估计
        prevPose_  = result.at<gtsam::Pose3>(X(key));
        prevVel_   = result.at<gtsam::Vector3>(V(key));
        prevState_ = gtsam::NavState(prevPose_, prevVel_);
        prevBias_  = result.at<gtsam::imuBias::ConstantBias>(B(key));

        // 当前约束任务已完成,预积分约束复位,同时需要设置一下零偏作为下一次积分的先诀条件
        imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
        // check optimization
        // 一个简单的失败检测
        if (failureDetection(prevVel_, prevBias_))
        {
            // 状态异常直接复位
            resetParams();
            return;
        }


        // 2. 优化之后,根据最新的IMU状态进行传播
        // 因为我们“同时”获得IMU和LIDAR里程计位姿,通常IMU数据会来的更早一点,LIDAR数据并不是原始数据而是一个里程计的数据(比如我们10s只能拿到第9s的odom的数据)
        // 而IMU数据是实时得到的(系统时间100s 可以收到99s的IMU 只能收到90s的odom),然而我们需要实时性,利用余下的IMU推算位姿
        prevStateOdom = prevState_;
        prevBiasOdom  = prevBias_;
        // first pop imu message older than current correction data
        double lastImuQT = -1;

        // 首先把LIDAR帧之前的IMU数据全部弹出去(和之前的IMU队列不是一个,需要注意)
        while (!imuQueImu.empty() && ROS_TIME(&imuQueImu.front()) < currentCorrectionTime - delta_t)
        {
            lastImuQT = ROS_TIME(&imuQueImu.front());
            imuQueImu.pop_front();
        }

        // 如果有新于LIAR状态的IMU数据
        if (!imuQueImu.empty())
        {
            // 将这个预积分变量复位
            imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom);

            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_->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 到key==100 执行前段代码
        ++key;
        doneFirstOpt = true;
    }

        注释已经很详细了,我们来讲解一下步骤:

        1.检查IMU队列imuQueOpt是否有数据,取出里程计的数据。并将odom的位姿转化成gtsam格式。

        2.如果系统没有初始化(systemInitialized == false):将优化问题置位(初始化graphFactors、graphValues),将imuQueOpt比当前里程计数据时间戳小的数据扔掉。

        计算初始值:
        LIDAR位姿:用传进来的ODOM坐标转换到LIDAR坐标系为初始值

        速度:0

        IMU零偏:设置

        将预积分的接口(imuIntegratorImu_、imuIntegratorOpt_)使用初始零值进行初始化并初始化bias,将key设置为1(第一次优化)。将初始化位置为1。

        3.初始化结束后,我们已经有一个LIDAR坐标系转换到IMU坐标系下的一个位姿,下面开始形成预积分的约束。

using gtsam::symbol_shorthand::X; // Pose3 (x,y,z,r,p,y)
using gtsam::symbol_shorthand::V; // Vel   (xdot,ydot,zdot)
using gtsam::symbol_shorthand::B; // Bias  (ax,ay,az,gx,gy,gz)

        取出imuQueOpt中一帧数据(在两帧ODOM之间的IMU数据帧)。

        imuIntegratorOpt_计算预积分。

const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements&>(*imuIntegratorOpt_);

        将两帧之间的预积分结果存储到preint_imu中。

        增加约束:通过创建`gtsam::ImuFactor`对象,将相邻两帧之间的位姿、速度和零偏之间的约束添加到因子图中。这个因子使用了上一帧的位姿和速度(`X(key - 1)`和`V(key - 1)`),当前帧的位姿和速度(`X(key)`和`V(key)`),以及上一帧的零偏(`B(key - 1)`)作为输入。同时,还使用了预积分的结果(`preint_imu`)来定义约束。

        // 两帧间IMU预积分完成(LIDAR两帧,IMU很多帧)后,将其转换成预积分约束
        const gtsam::PreintegratedImuMeasurements& preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements&>(*imuIntegratorOpt_);

        // 预积分的约束对相邻两帧之间的位姿 速度 零偏形成约束
        // 上一帧的位姿、速度 当前帧的位姿、速度 上一帧的零偏
        gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu);
        // 约束加入因子图
        graphFactors.add(imu_factor);

        取出预积分结果作为约束初始值,执行因子图优化。

        4.更新当前帧最佳优化值

        prevPose_  = result.at<gtsam::Pose3>(X(key));
        prevVel_   = result.at<gtsam::Vector3>(V(key));
        prevState_ = gtsam::NavState(prevPose_, prevVel_);
        prevBias_  = result.at<gtsam::imuBias::ConstantBias>(B(key));

        5.当前约束任务已完成,预积分约束复位,同时需要设置一下零偏作为下一次积分的先诀条件。

imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);

        6.进行失败检测:利用速度和IMU偏置,做一个简单的判断。如果失败了则重置系统。

    bool failureDetection(const gtsam::Vector3& velCur, const gtsam::imuBias::ConstantBias& biasCur)
    {
        Eigen::Vector3f vel(velCur.x(), velCur.y(), velCur.z());
        // 当前速度超过30m/s 108km/h认为是异常状态
        if (vel.norm() > 30)
        {
            ROS_WARN("Large velocity, reset IMU-preintegration!");
            return true;
        }

        Eigen::Vector3f ba(biasCur.accelerometer().x(), biasCur.accelerometer().y(), biasCur.accelerometer().z());
        Eigen::Vector3f bg(biasCur.gyroscope().x(), biasCur.gyroscope().y(), biasCur.gyroscope().z());
        // 零偏太大,那也不太正常
        if (ba.norm() > 1.0 || bg.norm() > 1.0)
        {
            ROS_WARN("Large bias, reset IMU-preintegration!");
            return true;
        }

        return false;
    }
    void resetParams()
    {
        lastImuT_imu = -1;
        doneFirstOpt = false;
        systemInitialized = false;
    }

        7.优化之后,根据最新的IMU状态进行传播,因为我们“同时”获得IMU和LIDAR里程计位姿,通常IMU数据会来的更早一点,LIDAR数据并不是原始数据而是一个里程计的数据(比如我们10s只能拿到第9s的odom的数据)而IMU数据是实时得到的(系统时间100s 可以收到99s的IMU 只能收到90s的odom),然而我们需要实时性,利用余下的IMU推算位姿。

        prevStateOdom = prevState_;
        prevBiasOdom  = prevBias_;
        // first pop imu message older than current correction data
        double lastImuQT = -1;

        // 首先把LIDAR帧之前的IMU数据全部弹出去(和之前的IMU队列不是一个,需要注意)
        while (!imuQueImu.empty() && ROS_TIME(&imuQueImu.front()) < currentCorrectionTime - delta_t)
        {
            lastImuQT = ROS_TIME(&imuQueImu.front());
            imuQueImu.pop_front();
        }

        // 如果有新于LIAR状态的IMU数据
        if (!imuQueImu.empty())
        {
            // 将这个预积分变量复位
            imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom);

            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_->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 到key==100 执行前段代码
        ++key;
        doneFirstOpt = true;

        8.执行100次 当isam优化器中加入较多的约束后,为了避免运算时间变长,直接清空 初始化因子图优化问题。

        if (key == 100)
        {
            // get updated noise before reset
            // 取出最

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/928662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows下cmd快速生成大文件命令

fsutil file createnew [文件名] [文件大小]

Ubuntu安装RabbitMQ

一、安装 更新系统软件包列表&#xff1a; sudo apt update安装RabbitMQ的依赖组件和GPG密钥&#xff1a; sudo apt install -y curl gnupg curl -fsSL https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc | sudo gpg --dearmo…

实验九 根文件系统移植

【实验目的】 熟悉根文件系统的目录结构&#xff0c;构建自己的根文件系统 【实验环境】 ubuntu 14.04 发行版FS4412 实验平台交叉编译工具&#xff1a;arm-none-linux-gnueabi- 【注意事项】实验步骤中以“$”开头的命令表示在 ubuntu 环境下执行 【实验步骤】 一、构建自…

jconsole查看JVM虚拟机使用情况 JDK1.8

首先需要在启动命令加上如下配置命令 -Djava.rmi.server.hostname服务运行ip -Dcom.sun.management.jmxremotetrue-Dcom.sun.management.jmxremote.port端口号-Dcom.sun.management.jmxremote.sslfalse-Dcom.sun.management.jmxremote.authenticatefalse 打开jdk自带的jsonso…

如何利用易查分快速发布学生分班结果?

在即将开学之际&#xff0c;负责分班工作的老师们通常会感到非常辛苦。他们不仅要制作分班情况表格&#xff0c;还需要想方设法将其发布出来&#xff0c;但又不能直接公开&#xff0c;以免无关人员随意加入。为了解决这个问题&#xff0c;一种快捷的发布学生分班情况的方法是自…

文心一言 VS 讯飞星火 VS chatgpt (81)-- 算法导论7.4 6题

六、如果用go语言&#xff0c;考虑对 PARTITION 过程做这样的修改:从数组 A 中随机选出三个元素&#xff0c;并用这三个元素的中位数(即这三个元素按大小排在中间的值)对数组进行划分。求以a 的函数形式表示的、最坏划分比例为 a:(1-a)的近似概率&#xff0c;其中 0<a<1。…

【复制带随机指针的链表】

题目来源 1、将每个 拷贝节点 都 插入 在原来的节点的后面 2、链接每个拷贝结点的 random 注意&#xff1a; 3、将拷贝结点 解 下来&#xff0c;尾插到一起&#xff0c;恢复原来链表。 /*** Definition for a Node.* struct Node {* int val;* struct Node *nex…

Windows平台Unity下播放RTSP或RTMP如何开启硬解码?

我们在做Windows平台Unity播放RTMP或RTSP的时候&#xff0c;遇到这样的问题&#xff0c;比如展会、安防监控等场景下&#xff0c;需要同时播放多路RTMP或RTSP流&#xff0c;这样对设备性能&#xff0c;提出来更高的要求。 虽然我们软解码&#xff0c;已经做的资源占有非常低了…

数据库优化:读写分离,并在SpringBoot项目中代码实现

什么要对数据库做读写分离优化。 存在下面两个问题&#xff0c;所以要进行数据库优化 单表不能太大&#xff1a;mysql官方说法:单表2000万数据&#xff0c;就到达瓶颈了。&#xff0c;所以说为了保证查询效率&#xff0c;得让每张表的大小得到控制。查询压力大&#xff1a;在…

开源微服务如何选型?Spring Cloud、Dubbo、gRPC、Istio 详细对比

作者&#xff1a;刘军 不论您是一名开发者、架构师、CTO&#xff0c; 如果您曾深度参与在微服务开发中&#xff0c;那么相信您一定有过开源微服务框架或体系选型的疑问&#xff1a;Apache Dubbo、Spring Cloud、gRPC 以及 Service Mesh 体系产品如 Istio&#xff0c;到底应该选…

《扩散模型 从原理到实战》Hugging Face (一)

文章目录 前言第一章 扩散模型简介1.1 扩散模型的原理1.1.1 生成模型1.1.2 扩散过程 前言 Hugging Face最近出版了第一本中文书籍《扩散模型 从原理到实战》&#xff0c;其中内容关于扩散模型&#xff08;Diffusion Model&#xff09;&#xff0c;和AIGC相关的内容较多&#x…

Search Ads Toggle有效推广:结合IPIDEA代理IP的TikTok营销策略

TikTok 本月22日推出了一个搜索广告切换(Search Ads Toggle)的新功能&#xff0c;这个功能对于广告商来说&#xff0c;更容易触达有明确搜索意向的目标受众。谷歌有研究显示&#xff0c;现在的年轻用户群体更倾向于把Tik Tok这样的社交媒体软件当做搜索引擎来使用&#xff0c;比…

Redis问题集合(三)在Redis容器里设置键值对

前言 前提是已经拉取了Redis镜像并创建了对应的容器做个记录&#xff0c;方便后续查看 步骤 查看Redis容器的ID&#xff1a;docker ps -a 进入容器&#xff1a;docker exec -it 容器ID /bin/bash进入redis命令行&#xff1a;redis-cli输入密码&#xff1a;auth 配置密码 查看…

L1-033 出生年(Python实现) 测试点全过

题目 以上是新浪微博中一奇葩贴&#xff1a;“我出生于1988年&#xff0c;直到25岁才遇到4个数字都不相同的年份。”也就是说&#xff0c;直到2013年才达到“4个数字都不相同”的要求。本题请你根据要求&#xff0c;自动填充“我出生于y年&#xff0c;直到x岁才遇到n个数字都不…

使用EF Core更新与修改生产数据库

使用EF Core的Code First&#xff0c;在设计阶段&#xff0c;直接使用Database.EnsureCreated()和EnsureDeleted()可以快速删除、更新最新的数据结构。由于没有什么数据&#xff0c;删除的风险非常低。但是对于已经投入生产的数据库&#xff0c;这个方法就绝对不可行了。 考虑…

vue3学习源码笔记(小白入门系列)------ 组件是如何渲染成dom挂载到指定位置的?

文章目录 os准备组件如何被挂载到页面上第一步 createApp 做了哪些工作&#xff1f;ensureRendererbaseCreateRenderercreateAppAPImountrenderpatchprocessComponentprocessElement 总结 os 学习一下vue3 源码&#xff0c;顺便记录分享下 使用vitest 插件调试源码 辅助阅读 …

0825|C++day5 运算符重载+静态成员+类的基础【Xmind+实例】

一、运算符重载 实例&#xff1a;&#xff08;赋值运算符、自增自减运算符、插入提取运算符&#xff09; #include <iostream>using namespace std;class Person {friend Person & operator(Person &L,const Person &R);friend Person & operator(Perso…

RabbitMQ 集群

clustering 最开始我们介绍了如何安装及运行 RabbitMQ 服务&#xff0c;不过这些是单机版的&#xff0c;无法满足目前真实应用的要求。如果 RabbitMQ 服务器遇到内存崩溃、机器掉电或者主板故障等情况&#xff0c;该怎么办&#xff1f;单台 RabbitMQ服务器可以满足每秒 100…

DDR与PCIe:高性能SoC的双引擎

SoC芯片无处不在&#xff0c;小到家电控制的MCU&#xff0c;大到手机芯片&#xff0c;我们都会接触到。如今大部分芯片设计公司都在开发SoC芯片&#xff0c;一颗SoC芯片可以集成越来越多的功能&#xff0c;俨然它已成为IC设计业界的焦点。 高性能、高速、高带宽的互联和存储的…

SpringIoC基于注解配置

目录 一、Bean注解标记和扫描 (IoC) 二、组件&#xff08;Bean&#xff09;作用域和周期方法注解 三、Bean属性赋值&#xff1a;引用类型自动装配 (DI) 四、Bean属性赋值&#xff1a;基本类型属性赋值 (DI) 一、Bean注解标记和扫描 (IoC) 一、注解方式介绍 1.注解介绍 和…