VINS-Fusion源码逐行解析:processMeasurements()及其关于imu数据处理部分

先捋一下顺序

前边是从项目入口rosNodeTest.cpp的主线程中读取配置文件,然后设置估计器参数,订阅一系列回调函数,然后在sync_process线程中通过inputImage函数经trackImage函数完成对特征点进行lk光流追踪后构建featureFrame,并将其图像信息放入featureBuf后,最后调用processMeasurements()函数要对测量数据进行处理

processMeasurements()这个函数是用来处理测量数据的主要循环。它从 featureBuf 中获取特征数据,同时等待IMU数据(如果启用了IMU),然后对图像和IMU数据进行处理。

首先看一下源码:

//这个函数是用来处理测量数据的主要循环。它从 featureBuf 中获取特征数据,同时等待IMU数据(如果启用了IMU),然后对图像和IMU数据进行处理
void Estimator::processMeasurements()
{
    //这里开始一个无限循环,一直等待处理测量数据。
    while (1)
    {
        //printf("process measurments\n");
        //定义了一个 feature 变量,其中包含时间戳和特征点数据的映射。
        pair<double, map<int, vector<pair<int, Eigen::Matrix<double, 7, 1> > > > > feature;
        //定义了用于存储IMU数据的向量。
        vector<pair<double, Eigen::Vector3d>> accVector, gyrVector;
        if(!featureBuf.empty())
        {
            //如果特征点缓冲区不为空,就从中获取下一个特征数据。
            feature = featureBuf.front();
            // 计算当前时间(特征数据的时间戳加上时间偏移)
            curTime = feature.first + td;
            // 等待IMU数据,直到IMU数据在当前时间戳可用
            while(1)
            {
                // 如果不使用IMU或者IMU数据在当前时间戳处可用,则退出循环
                if ((!USE_IMU  || IMUAvailable(feature.first + td)))
                    break;
                // 否则,等待IMU数据
                else
                {
                    printf("wait for imu ... \n");
                    // 如果没有启用多线程,直接返回
                    if (! MULTIPLE_THREAD)
                        return;
                    // 等待5毫秒
                    std::chrono::milliseconds dura(5);
                    std::this_thread::sleep_for(dura);
                }
            }
            // 加锁,获取IMU数据并存储到accVector和gyrVector中
            mBuf.lock();
            if(USE_IMU)
                getIMUInterval(prevTime, curTime, accVector, gyrVector);

            // 从特征数据缓冲区中移除已处理的特征数据
            featureBuf.pop();
            mBuf.unlock();

            // 如果使用IMU数据
            if(USE_IMU)
            {
                // 如果尚未初始化第一个姿态,先初始化第一个IMU姿态
                if(!initFirstPoseFlag)
                    initFirstIMUPose(accVector);
                // 对每个时间间隔的IMU数据进行处理
                for(size_t i = 0; i < accVector.size(); i++)
                {
                    double dt;
                    // 计算时间间隔
                    if(i == 0)
                        dt = accVector[i].first - prevTime;// 第一个数据包的时间戳与上一个处理的时间戳之差
                    else if (i == accVector.size() - 1)
                        dt = curTime - accVector[i - 1].first;// 当前时间与最后一个数据包的时间戳之差
                    else
                        dt = accVector[i].first - accVector[i - 1].first;// 当前数据包的时间戳与前一个数据包的时间戳之差
                    // 调用processIMU处理IMU数据
                    processIMU(accVector[i].first, dt, accVector[i].second, gyrVector[i].second);
                }
            }
            // 加锁,调用processImage函数处理图像数据
            mProcess.lock();
            processImage(feature.second, feature.first);
            prevTime = curTime;

            // 打印统计信息
            printStatistics(*this, 0);

            // 设置ROS消息头部,并发布相关数据
            std_msgs::Header header;
            header.frame_id = "world";
            header.stamp = ros::Time(feature.first);

            pubOdometry(*this, header);
            pubKeyPoses(*this, header);
            pubCameraPose(*this, header);
            pubPointCloud(*this, header);
            pubKeyframe(*this);
            pubTF(*this, header);
            mProcess.unlock();
        }

        // 如果没有启用多线程,退出主循环
        if (! MULTIPLE_THREAD)
            break;

        // 短暂休眠以减少处理器资源的占用
        std::chrono::milliseconds dura(2);
        std::this_thread::sleep_for(dura);
    }
}

 看这个函数,我们首先定义了一些变量,用于存储相应数据,然后如果featureBuf不为空,那么我们取出队列中第一个开始处理

我们等待IMU数据,通过IMUAvailable函数来判断IMU数据是否在当前时间戳可用,如果使用IMU,那么通过getIMUInterval函数将imu数据读取进开始设置的变量accVector, gyrVector中,然后没有初始化则调用initFirstIMUPose对imu信息进行初始化,最后计算完时间间隔后,调用processIMU函数来完成对IMU数据的处理,至此,有关imu数据的处理流程结束

接下来调用processImage函数实现对图像数据的处理(下节介绍)

最后,设置ros消息头,并发布相关数据

我们接下来看看与处理IMU数据相关的函数

首先是IMUAvailable,核心就是加速度计里有数据,并且当前时间在加速度计里最后一个数据的时间点前,那么认为当前时间点就是有IMU数据的

bool Estimator::IMUAvailable(double t)
{
    //检查加速度计数据缓冲区 accBuf 是否不为空
    //并且 检查给定的时间 t 是否在加速度计缓冲区中最后一个数据点的时间点前或刚好等于(确保此时间点有imu数据)。
    if(!accBuf.empty() && t <= accBuf.back().first)
        return true;
    else
        return false;
}

 getIMUInterval函数,该函数确保在给定的时间范围内(从 t0t1)提取IMU数据,并将其存储在 accVectorgyrVector 中。如果在 t1 时刻没有足够的IMU数据,函数将返回 false

//该函数确保在给定的时间范围内(从 t0 到 t1)提取IMU数据,并将其存储在 accVector 和 gyrVector 中。
bool Estimator::getIMUInterval(double t0, double t1, vector<pair<double, Eigen::Vector3d>> &accVector, 
                                vector<pair<double, Eigen::Vector3d>> &gyrVector)
{
    // 检查加速度计缓冲区是否为空
    if(accBuf.empty())
    {
        // 如果为空,打印消息并返回 false
        printf("not receive imu\n");
        return false;
    }
    //printf("get imu from %f %f\n", t0, t1);
    //printf("imu fornt time %f   imu end time %f\n", accBuf.front().first, accBuf.back().first);
    // 检查目标时间范围的结束时间 t1 是否小于或等于IMU缓冲区中最后一个数据点的时间
    if(t1 <= accBuf.back().first)
    {
        // 如果 t1 小于或等于缓冲区中最后一个数据点的时间,执行以下操作
        // 从缓冲区中移除时间小于或等于 t0 的所有数据点
        while (accBuf.front().first <= t0)
        {
            accBuf.pop();
            gyrBuf.pop();
        }
        // 将时间在 t0 和 t1 之间的IMU数据添加到输出向量中
        while (accBuf.front().first < t1)
        {
            accVector.push_back(accBuf.front());
            accBuf.pop();
            gyrVector.push_back(gyrBuf.front());
            gyrBuf.pop();
        }
        // 添加时间等于 t1 的IMU数据点到输出向量中
        accVector.push_back(accBuf.front());
        gyrVector.push_back(gyrBuf.front());
    }
    else
    {
        // 如果 t1 大于缓冲区中最后一个数据点的时间,打印消息并返回 false
        printf("wait for imu\n");
        return false;
    }
    // 返回 true 表示成功获取IMU数据
    return true;
}

initFirstIMUPose函数是初始化imu姿态

void Estimator::initFirstIMUPose(vector<pair<double, Eigen::Vector3d>> &accVector)
{
    // 打印初始化第一个IMU姿态的消息
    printf("init first imu pose\n");
    // 设置初始姿态标志为 true
    initFirstPoseFlag = true;
    //return;
    // 初始化一个三维向量 averAcc,表示平均加速度,初始值为零向量。
    Eigen::Vector3d averAcc(0, 0, 0);
    // 获取加速度向量的大小并存储在变量 n 中。
    int n = (int)accVector.size();
    // 遍历 accVector,计算所有加速度数据的总和。
    for(size_t i = 0; i < accVector.size(); i++)
    {
        averAcc = averAcc + accVector[i].second;
    }
    // 计算加速度向量的平均值
    averAcc = averAcc / n;
    // 打印平均加速度向量的值
    printf("averge acc %f %f %f\n", averAcc.x(), averAcc.y(), averAcc.z());
    // 调用 Utility 类的 g2R 函数,根据平均加速度向量计算初始旋转矩阵 R0
    Matrix3d R0 = Utility::g2R(averAcc);
    // 从旋转矩阵 R0 中提取偏航角 yaw
    double yaw = Utility::R2ypr(R0).x();
    // 根据 yaw 计算校正后的旋转矩阵 R0
    R0 = Utility::ypr2R(Eigen::Vector3d{-yaw, 0, 0}) * R0;
    // 将校正后的旋转矩阵 R0 赋值给姿态数组 Rs 的第一个元素
    Rs[0] = R0;
    // 打印初始旋转矩阵 R0
    cout << "init R0 " << endl << Rs[0] << endl;
    //Vs[0] = Vector3d(5, 0, 0);
}

processIMU函数处理IMU数据,得到预积分值,让我们来看一下源码

void Estimator::processIMU(double t, double dt, const Vector3d &linear_acceleration, const Vector3d &angular_velocity)
{
    // 如果是第一次处理IMU数据,初始化第一次的加速度和角速度
    if (!first_imu)
    {
        //设置标志位 first_imu 为 true,表示已经处理了第一次IMU数据
        first_imu = true;
        //将当前线性加速度赋值给 acc_0
        acc_0 = linear_acceleration;
        //将当前角速度赋值给 gyr_0
        gyr_0 = angular_velocity;
    }

    // 如果当前帧的预积分对象不存在,创建一个新的预积分对象
    if (!pre_integrations[frame_count])
    {
        //创建新的预积分对象,初始化参数包括加速度、角速度、加速度偏差和角速度偏差
        pre_integrations[frame_count] = new IntegrationBase{acc_0, gyr_0, Bas[frame_count], Bgs[frame_count]};
    }
    // 如果当前不是第一帧,进行预积分和状态更新
    if (frame_count != 0)
    {
        //将当前时间间隔、线性加速度和角速度加入预积分对象
        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;
        //计算去除重力后的未校正加速度 un_acc_0。  
        //将IMU测量的加速度数据转换到世界坐标系,并去除了重力的影响
        //Rs[j]:当前帧的旋转矩阵,将IMU坐标系下的向量转换到世界坐标系下。
        //acc_0:前一时刻的线性加速度(IMU测量值)。
        //Bas[j]:当前帧的加速度计偏差
        //g:重力向量      
        Vector3d un_acc_0 = Rs[j] * (acc_0 - Bas[j]) - g;
        //计算未校正的角速度 un_gyr。
        //gyr_0是前一时刻的角速度(通常为上一个IMU数据包的角速度)
        //angular_velocity:当前时刻的角速度(当前IMU数据包的角速度)。
        //0.5 * (gyr_0 + angular_velocity):计算当前和前一时刻角速度的平均值。
        //Bgs[j]:当前帧的陀螺仪偏差
        Vector3d un_gyr = 0.5 * (gyr_0 + angular_velocity) - Bgs[j];
        //更新旋转矩阵 Rs[j]。
        //un_gyr * dt得到一个微小旋转量的角度矢量,然后构造一个四元数Q,再转成旋转矩阵R
        Rs[j] *= Utility::deltaQ(un_gyr * dt).toRotationMatrix();
        //计算当前加速度下的未校正加速度 un_acc_1。
        Vector3d un_acc_1 = Rs[j] * (linear_acceleration - Bas[j]) - g;
        //计算平均未校正加速度 un_acc
        Vector3d un_acc = 0.5 * (un_acc_0 + un_acc_1);
        //更新位置 Ps[j]
        //经典的运动学方程
        //dt * Vs[j]:初始速度Vs[j]在时间 dt 内产生的位移
        //0.5 * dt * dt * un_acc:加速度在时间 dt 内产生的位移。
        //再加上初始位置,就得到更新后的位置
        Ps[j] += dt * Vs[j] + 0.5 * dt * dt * un_acc;
        //更新速度 Vs[j]
        Vs[j] += dt * un_acc;
    }
    //更新当前加速度。
    acc_0 = linear_acceleration;
    //更新当前角速度
    gyr_0 = angular_velocity; 
}

得到当前帧的预积分对象,并计算当前帧的位置和速度

对图像数据处理的函数见下节 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值