当前位置: 首页 > article >正文

VINS-MONO学习笔记-手动添加ZUPT

近日吗喽,leader让用vslam算法辅助机器人现有的里程计定位算法,意在消除轮速计在部分环境下打滑导致的定位偏移,苦于自己之前一直做的是激光SLAM,花了很长一段时间学习VSLAM,前一个月要我主攻VINS-MONO算法,本文主要针对自己在知乎看到的一篇关于VINS工程落地经验贴,其中提及到在对VINS实现工程落地中,常用到的一个葵花宝典—ZUPT(零速修正),自己也借着前人的肩膀自己手写复现了一下,最后效果在开源数据集euroc的MH_easy_01这个数据包下,精度提升了约10%,效果一般,可能限于我自己本身能力有限,且VSLAM这块我也是第一次接触,对VINS-MONO的源码理解的不深刻,所以请各位读者大佬如果发现有问题或者有更好的方案,能够不吝赐教!

ZUPT–零速修正

其实在笔者读研期间,就已经接触过这方面的知识,只是当时使用的是另一个版本,即车身非完整性约束(NHC),与零速修正不同的是,NHC假设的是载体在运动的过程中侧向和垂向的速度为0,零速修正则是需要判断当前载体的运动状态,如果检测到为静止状态,那么直接构造一个三维的零速向量,进而修正当前载体的速度;

VINS算法

零速修正的理论其实不难,关键在与两点:

1. 载体的运动状态判断

2. 加在哪里

对于问题1,知乎那篇文章已经说的很明白了,我自己的理解就是,计算一个时间段两头的IMU数据的欧式距离,根据这个欧式距离去判断当前载体的运动状态是否处于静止。什么意思呢,比如VINS源码中,有一个imu_callback函数,在这个函数中主要做的是对IMU数据的处理,那么可以在这个位置判断当前载体的运动状态,我的处理方法就是每次获取到imu_buf最新的一个IMU数据,然后将其与前第10帧的IMU数据作运算,得到两帧IMU的欧式距离,当然,计算的是IMU的线加速度信息的欧式距离。根据这个欧式距离,可以判断当前载体的运动状态,至于这个状态阈值的选取,可以根据你自己的机器人数据的情况来定,开源数据集我选的0.05;

对于问题2,我其实也纠结了很久,倒不是因为要选择一个很好的位置,而是我一直不太明白应该加在哪里,最后在阅读了一些关于VINS源码解析的博客后,最后选择加在estimator.cpp中的processIMU()函数处,至于为什么,我自己的想法是因为每一次计算得到的IMU预计分信息都会当作后续图像帧对应IMU的PVQ信息,以用于后续的后端优化;

代码

完成上述的一些准备工作后,直接就加入工程代码看效果:
首先是针对状态的判断,此部分功能函数添加在imu_callback函数中:

imu_buf.push(imu_msg);
    
if(imu_buf.size() > 10){
        sensor_msgs::ImuConstPtr imu_now = imu_buf.back();
        sensor_msgs::ImuConstPtr imu_old;
        int target_index = imu_buf.size() - 10;
        int current_index = 0;

        std::queue<sensor_msgs::ImuConstPtr> temp_buf = imu_buf; // Create a temporary copy of imu_buf
        while (!temp_buf.empty()) {
            if (current_index == target_index) {
                imu_old = temp_buf.front();
                break;
            }
            temp_buf.pop();
            current_index++;
        }
        Eigen::Vector3d accel_now(imu_now->linear_acceleration.x, imu_now->linear_acceleration.y, imu_now->linear_acceleration.z);
        Eigen::Vector3d accel_old(imu_old->linear_acceleration.x, imu_old->linear_acceleration.y, imu_old->linear_acceleration.z);
        
        // Calculate the Euclidean distance between accel_now and accel_old
        double distance = (accel_old - accel_now).norm();

        // ROS_INFO("THE DISTANS IS %f\n",distance);
        if(distance <= 0.05){
            // ROS_INFO("the robot is static\n");
            is_static = true;
        }else{
            is_static = false;
        }
    }

    m_buf.unlock(); 

可以对应源码查看所加代码块的位置,其中is_static是一个全局变量;

ZUPT所加位置:

class VelocityFusionCost : public ceres::SizedCostFunction<3, 3> {
public:
    // 构造函数,初始化观测的速度
    VelocityFusionCost(const Eigen::Vector3d& observed_velocity)
        : observed_velocity_(observed_velocity) {}

    // 残差计算
    virtual bool Evaluate(const double* const* parameters, double* residuals, double** jacobians) const override {
        const double* predicted_velocity = parameters[0];

        // 计算残差:预测速度与观测速度之间的差值
        residuals[0] = predicted_velocity[0] - observed_velocity_[0];
        residuals[1] = predicted_velocity[1] - observed_velocity_[1];
        residuals[2] = predicted_velocity[2] - observed_velocity_[2];

        // 计算雅可比矩阵
        if (jacobians != nullptr && jacobians[0] != nullptr) {
            Eigen::Map<Eigen::Matrix<double, 3, 3, Eigen::RowMajor>> jacobian_v(jacobians[0]);
            jacobian_v.setIdentity();
        }

        return true;
    }

private:
    const Eigen::Vector3d observed_velocity_;  // 观测速度
};

void Estimator::processIMU(double dt, const Vector3d &linear_acceleration, const Vector3d &angular_velocity, const bool is_static)
{
    if (!first_imu)// 第一帧IMU数据,如果是第一帧,那么直接把当前IMU数据放在acc_0和gyr_0
    {
        //如果是系统的第一帧imu数据,则保存数据为 acc_0、gyr_0,由于dt=0 ,在计算预积分以及中值积分时都为0,
        //不过重点是将本次IMU的测量值保存,这样下一次IMU数据到时就可以进行中值积分。
        first_imu = true;
        acc_0 = linear_acceleration;
        gyr_0 = angular_velocity;
    }

    if (!pre_integrations[frame_count])//如果滑动窗口中当前帧frame_count图像预积分对象空,则为其创建一个预积分对象。
    {
        pre_integrations[frame_count] = new IntegrationBase{acc_0, gyr_0, Bas[frame_count], Bgs[frame_count]};
    }
    if (frame_count != 0)
    {
        //如果当前图像帧 frame_count = 0 ,则说明此时处理的是第一帧图像数据,则不需要进行预积分,
        //因为预积分是表示两帧图像之间的约束, frame_count != 0时 ,用imu数据更新该图像帧的预积分
        //pre_integrations[frame_count]->push_back(dt, linear_acceleration, angular_velocity)
        pre_integrations[frame_count]->push_back(dt, linear_acceleration, angular_velocity);
        //if(solver_flag != NON_LINEAR)
            tmp_pre_integration->push_back(dt, linear_acceleration, angular_velocity);

        dt_buf[frame_count].push_back(dt);
        linear_acceleration_buf[frame_count].push_back(linear_acceleration);
        angular_velocity_buf[frame_count].push_back(angular_velocity);

        int j = frame_count;         
        //用中值预积分更新当前图像帧对应的IMU PVQ数组 Ps、Vs、Rs,Ps、Vs、Rs
        //数组保存滑动窗口中每一帧在世界坐标系中的位姿,在初始化完成之前这个数组保存的姿态是相对于参考帧的
        Vector3d un_acc_0 = Rs[j] * (acc_0 - Bas[j]) - g;
        Vector3d un_gyr = 0.5 * (gyr_0 + angular_velocity) - Bgs[j];
        Rs[j] *= Utility::deltaQ(un_gyr * dt).toRotationMatrix();
        Vector3d un_acc_1 = Rs[j] * (linear_acceleration - Bas[j]) - g;
        Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
        Ps[j] += dt * Vs[j] + 0.5 * dt * dt * un_acc;
        Vs[j] += dt * un_acc;

        // 24-10-18 by xinyi ZUPT
        if (is_static) {
            ceres::Problem problem;

            ceres::CostFunction* cost_function = new VelocityFusionCost(Eigen::Vector3d::Zero());
            problem.AddResidualBlock(cost_function, nullptr, Vs[j].data());

            // 配置求解器选项
            ceres::Solver::Options options;
            options.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY;
            options.minimizer_progress_to_stdout = true;

            // 输出优化摘要
            ceres::Solver::Summary summary;

            // 求解问题
            ceres::Solve(options, &problem, &summary);
        }
    }
    acc_0 = linear_acceleration;
    gyr_0 = angular_velocity;
}

我直接吧processIMU函数的所有内容贴过来了,这样也方便查看;

最后的结果:

加了零速约束后:

在这里插入图片描述

没加零速约束:

在这里插入图片描述
可以看到,RMSE提升了约10%,且在EVO生成的轨迹中可以看出,在Z轴上的定位信息会相比没加零速约束效果会好点(图就不贴了)

最后,这个代码不算太难,整体其实也就是个工程化的思路,可能存在一定的错误,如有问题,望各位不吝赐教,最后的最后,贴上我拜读的几篇博文;
VINS工程落地经验帖
VINS源码解读


http://www.kler.cn/a/370614.html

相关文章:

  • 网上订餐系统 javaweb项目 (完整源码)
  • 高效实现 Markdown 转 PDF 的跨平台指南20250117
  • OpenVela——专为AIoT领域打造的开源操作系统
  • SQL-杂记1
  • 【postgres】sqlite格式如何导入postgres数据库
  • PyTorch DAY2: 搭建神经网络
  • 《近似线性可分支持向量机的原理推导》 对偶问题 公式解析
  • 基础知识 表达式 C语言
  • 操作系统学习笔记2.2调度
  • 模型其他压缩方法
  • 前端 eslint 配置,以及在git提交之前自动format
  • Day20 数据结构
  • Python从入门到高手7.3节-列表的常用操作方法
  • 【2024工业3D异常检测文献】LSFA: 面向三维工业异常检测的自监督特征适配
  • Xcode文件默认存储位置-使用c++file保存文件默认路径以及设置为路径为当前项目路径
  • Python 深度学习简单介绍
  • Java表单提交:轻松实现与PHP和Python相同的简便性
  • 力扣刷题(sql)--零散知识点(2)
  • linux系统操作教程小白学习
  • 大数据之Kafka集群的安装部署
  • mysql 十把锁之《小猫钓鱼》
  • 踩坑:关于使用ceph pg repair引发的业务阻塞
  • 【MATLAB源码-第187期】基于matlab的人工蜂群优化算法(ABC)机器人栅格路径规划,输出做短路径图和适应度曲线。
  • js 实现自定义打印模板
  • Java生态系统的完全掌握(5/5)
  • anchor、anchor box、bounding box之间关系