VINS-Mono代码阅读笔记(十三):posegraph中四自由度位姿优化

2023-05-16

本篇笔记紧接着VINS-Mono代码阅读笔记(十二):将关键帧加入位姿图当中,来学习pose_graph当中的紧耦合优化部分。

在重定位完成之后,进行位姿图优化是为了将已经产生的所有位姿统一到一个全局一致的配置当中。如论文中展示的下图所示,参考帧处于世界坐标系下,当相机运动的时候x,y,z,yaw会相对于参考帧发生变化。而由于重力向量始终不会发生变化,所以从重力方向得到的水平面也不会发生变化,进而该水平面对应的raw,pitch两个向量也不会发生变换。所以,系统中需要计算并且优化的向量只有x,y,z,yaw(也就是位置和旋转),这就是4自由度优化的由来。

1.加入关键帧到位姿图当中

通过代码可以发现,当滑动窗口完成一次边缘化(滑出最旧的帧或者滑动窗口中倒数第二帧)后,在后边的pubKeyframe函数中会将the second latest frame关键帧的位姿信息作为topic发出来。pose_graph节点中接收到这个topic后,构造出对应的关键帧并加入位姿图当中。

1)相关代码

vins-estimator中发送关键帧位姿的代码如下:

void pubKeyframe(const Estimator &estimator)
{
    // pub camera pose, 2D-3D points of keyframe
    //estimator.marginalization_flag == 0,表示MARGIN_OLD,边缘化删除了最旧的关键帧
    //estimator.solver_flag == Estimator::SolverFlag::NON_LINEAR表示视觉和惯导初始化成功
    if (estimator.solver_flag == Estimator::SolverFlag::NON_LINEAR && estimator.marginalization_flag == 0)
    {
        int i = WINDOW_SIZE - 2;
        //Vector3d P = estimator.Ps[i] + estimator.Rs[i] * estimator.tic[0];
        Vector3d P = estimator.Ps[i];
        Quaterniond R = Quaterniond(estimator.Rs[i]);

        nav_msgs::Odometry odometry;
        odometry.header = estimator.Headers[WINDOW_SIZE - 2];
        odometry.header.frame_id = "world";
        odometry.pose.pose.position.x = P.x();
        odometry.pose.pose.position.y = P.y();
        odometry.pose.pose.position.z = P.z();
        odometry.pose.pose.orientation.x = R.x();
        odometry.pose.pose.orientation.y = R.y();
        odometry.pose.pose.orientation.z = R.z();
        odometry.pose.pose.orientation.w = R.w();
        //printf("time: %f t: %f %f %f r: %f %f %f %f\n", odometry.header.stamp.toSec(), P.x(), P.y(), P.z(), R.w(), R.x(), R.y(), R.z());

        pub_keyframe_pose.publish(odometry);

而在pose-graph中将该关键帧加入位姿图的代码如下:

/**
 * process线程入口函数
*/
void process()
{
    .......
    //创建关键帧
    KeyFrame* keyframe = new KeyFrame(pose_msg->header.stamp.toSec(), frame_index, 
                           T, R,             
                           image,point_3d, point_2d_uv, point_2d_normal, point_id, sequence);
    m_process.lock();
    start_flag = 1;
    //位姿图中加入关键帧,flag_detect_loop设置为1
    posegraph.addKeyFrame(keyframe, 1);

2)位姿图中的顶点和边

每一个关键帧在位姿图中作为一个顶点存在,它和其他关键帧以序列变和闭环边两种类型的边进行连接,如下图所示:

序列边(Sequential Edge):

一个关键帧将与其前边的多个关键帧建立序列边(如上图所示)。一个序列边表示两个关键帧之间的相对变换,这个可以直接从VIO中得出。假设关键帧i和其前边的一个关键帧j,两个关键帧构成的序列边只包含相对位置\hat{p}_{ij}^{i}和偏航角\hat{\psi }_{ij},则这两个值表达如下:

                                                                  \hat{p}_{ij}^i = \hat{R}_i^{w^{-1}}(\hat{p}_j^{w}-\hat{p}_i^{w})

                                                                  \hat{\psi }_{ij}=\hat{\psi }_j-\hat{\psi }_i.

闭环边(Loop-Closure Edge):

如果一个关键帧有闭环连接,那么它在位姿图中和闭环帧之间的连接为闭环边。相似的,闭环边只包括一个4自由度的相对位姿变换,定义和上边的序列变相同。闭环边的值,是通过重定位得到的。

2.四自由度的位姿图优化

我们定义关键帧ij之间的边的最小化残差如下:

                                                                r_{i,j}(p_i^{w},\psi _i,p_j^w,\psi _j)=\begin{bmatrix} R(\hat{\phi}_i,\hat{\theta }_i,\psi _i)^{-1}(p_j^w-p_i^w)-\hat{p}_{ij}^i\\ \psi _j-\psi_i-\hat{\psi}_{ij} \end{bmatrix}

这里的\hat{\phi }_i\hat{\theta }_i两个值,是从单目VIO中估计得到的roll和pitch的角度,是固定的。

序列边和闭环边的所有图通过最小化下面的损失函数来进行优化:

                                                                \underset{p,\psi }{min}\left \{ \sum_{(i,j)\in S}\left \| r_{i,j} \right \|^2+\sum_{(i,j)\in L} \rho (\left \| r_{i,j} \right \|^2)\right \}

这里S是所有序列变的集合,L是所有闭环边的集合。尽管紧耦合的重定位已经帮助消除了错误的闭环,在这里增加另一个Huber核函数\rho (\cdot )以进一步减少任何可能的错误闭环的影响。

优化部分在pose_graph节点构造posegraph对象的时候,在构造函数当中新启了一个线程来完成。PoseGraph的构造函数代码如下:

/**
 * PoseGraph构造函数
*/
PoseGraph::PoseGraph()
{
    posegraph_visualization = new CameraPoseVisualization(1.0, 0.0, 1.0, 1.0);
    posegraph_visualization->setScale(0.1);
    posegraph_visualization->setLineWidth(0.01);
    //创建位姿优化线程
	t_optimization = std::thread(&PoseGraph::optimize4DoF, this);
    earliest_loop_index = -1;
    t_drift = Eigen::Vector3d(0, 0, 0);
    yaw_drift = 0;
    r_drift = Eigen::Matrix3d::Identity();
    w_t_vio = Eigen::Vector3d(0, 0, 0);
    w_r_vio = Eigen::Matrix3d::Identity();
    global_index = 0;
    sequence_cnt = 0;
    sequence_loop.push_back(0);
    base_sequence = 1;

}

位姿优化的线程入口函数为optimize4DoF,optimize4DoF代码如下:

/**
 * 位姿图中的优化,这里是4个自由度的位姿优化
*/
void PoseGraph::optimize4DoF()
{
    while(true)
    {
        int cur_index = -1;
        int first_looped_index = -1;
        m_optimize_buf.lock();
        //从优化队列当中获取最新的一个关键帧的index
        while(!optimize_buf.empty())
        {
            cur_index = optimize_buf.front();
            //earliest_loop_index当中存放的是数据库中第一个和滑动窗口中关键帧形成闭环的关键帧的index
            first_looped_index = earliest_loop_index;
            optimize_buf.pop();
        }
        m_optimize_buf.unlock();
        //optimize_buf中取出来的cur_index都是闭环帧的index
        if (cur_index != -1)
        {
            printf("optimize pose graph \n");
            TicToc tmp_t;
            m_keyframelist.lock();
            KeyFrame* cur_kf = getKeyFrame(cur_index);
            //max_length为要优化的变量最大个数
            int max_length = cur_index + 1;

            // w^t_i   w^q_i
            double t_array[max_length][3];//平移数组,其中存放每个关键帧的平移向量
            Quaterniond q_array[max_length];//旋转数组,其中存放每个关键帧的旋转四元数
            double euler_array[max_length][3];
            double sequence_array[max_length];

            ceres::Problem problem;
            ceres::Solver::Options options;
            options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
            //options.minimizer_progress_to_stdout = true;
            //options.max_solver_time_in_seconds = SOLVER_TIME * 3;
            options.max_num_iterations = 5;
            ceres::Solver::Summary summary;
            ceres::LossFunction *loss_function;
            loss_function = new ceres::HuberLoss(0.1);
            //loss_function = new ceres::CauchyLoss(1.0);
            //AngleLocalParameterization类的主要作用是指定yaw角优化变量的迭代更新,重载了括号运算
            ceres::LocalParameterization* angle_local_parameterization =
                AngleLocalParameterization::Create();

            list<KeyFrame*>::iterator it;

            int i = 0;
            //遍历关键帧列表
            for (it = keyframelist.begin(); it != keyframelist.end(); it++)
            {
                //first_looped_index为第一次闭环帧的index,需要优化的关键帧为从第一次闭环帧到当前帧间的所有关键帧
                if ((*it)->index < first_looped_index)
                    continue;
                (*it)->local_index = i;
                Quaterniond tmp_q;
                Matrix3d tmp_r;
                Vector3d tmp_t;
                //获取关键帧it的位姿
                (*it)->getVioPose(tmp_t, tmp_r);
                tmp_q = tmp_r;
                t_array[i][0] = tmp_t(0);
                t_array[i][1] = tmp_t(1);
                t_array[i][2] = tmp_t(2);
                q_array[i] = tmp_q;
                //将矩阵转换为向量
                Vector3d euler_angle = Utility::R2ypr(tmp_q.toRotationMatrix());
                euler_array[i][0] = euler_angle.x();
                euler_array[i][1] = euler_angle.y();
                euler_array[i][2] = euler_angle.z();

                sequence_array[i] = (*it)->sequence;
                //将关键帧列表中所有index>=first_looped_index的关键帧的位姿加入到参数块当中
                problem.AddParameterBlock(euler_array[i], 1, angle_local_parameterization);
                problem.AddParameterBlock(t_array[i], 3);
                //设置约束:如果该帧是最早的闭环帧的情况下,则固定它的位姿
                if ((*it)->index == first_looped_index || (*it)->sequence == 0)
                {   
                    problem.SetParameterBlockConstant(euler_array[i]);
                    problem.SetParameterBlockConstant(t_array[i]);
                }

                //add edge 这里添加的是序列边,是指通过VIO计算的两帧之间的相对位姿,每帧分别与其前边最多四帧构成序列边
                /**
                 * 顺序边的测量方程:p̂_{ij}^{i} = {R̂_i^w}^{-1} (p̂_j^w - p̂_i^w)
                 *                \hat{ψ}_ij = \hat{ψ}_j − \hat{ψ̂}_i
                 *  两个关键帧之间的相对位姿,由两个关键帧之间的VIO位姿估计变换得到
                 *   |------------------------------------|
                 *   |       |----------------------------|
                 *   |       |        |-------------------|
                 *   |       |        |         |---------|
                 * |帧1|    |帧2|    |帧3|     |帧4|     |帧5|
                */
                for (int j = 1; j < 5; j++)
                {
                    if (i - j >= 0 && sequence_array[i] == sequence_array[i-j])
                    {
                        Vector3d euler_conncected = Utility::R2ypr(q_array[i-j].toRotationMatrix());
                        //p̂_j^w - p̂_i^w 计算平移量的偏差
                        Vector3d relative_t(t_array[i][0] - t_array[i-j][0], t_array[i][1] - t_array[i-j][1], t_array[i][2] - t_array[i-j][2]);
                        //p̂_{ij}^{i} = {R̂_i^w}^{-1} (p̂_j^w - p̂_i^w)
                        //p̂ ij= (R̂ iw ) (p̂ jw − p̂ iw )
                        relative_t = q_array[i-j].inverse() * relative_t;
                        //ψ̂ _ij = ψ̂ _j − ψ̂ _i
                        double relative_yaw = euler_array[i][0] - euler_array[i-j][0];
                        ceres::CostFunction* cost_function = FourDOFError::Create( relative_t.x(), relative_t.y(), relative_t.z(),
                                                   relative_yaw, euler_conncected.y(), euler_conncected.z());
                        problem.AddResidualBlock(cost_function, NULL, euler_array[i-j], 
                                                t_array[i-j], 
                                                euler_array[i], 
                                                t_array[i]);
                    }
                }

                //add loop edge
                //这里添加的是闭环边,是指检测到闭环的两帧
                if((*it)->has_loop)
                {
                    assert((*it)->loop_index >= first_looped_index);
                    int connected_index = getKeyFrame((*it)->loop_index)->local_index;
                    Vector3d euler_conncected = Utility::R2ypr(q_array[connected_index].toRotationMatrix());
                    Vector3d relative_t;
                    relative_t = (*it)->getLoopRelativeT();
                    double relative_yaw = (*it)->getLoopRelativeYaw();
                    ceres::CostFunction* cost_function = FourDOFWeightError::Create( relative_t.x(), relative_t.y(), relative_t.z(),
                                                                               relative_yaw, euler_conncected.y(), euler_conncected.z());
                    problem.AddResidualBlock(cost_function, loss_function, euler_array[connected_index], 
                                                                  t_array[connected_index], 
                                                                  euler_array[i], 
                                                                  t_array[i]);
                    
                }
                
                if ((*it)->index == cur_index)
                    break;
                i++;
            }
            m_keyframelist.unlock();
            //开始优化
            ceres::Solve(options, &problem, &summary);
            //std::cout << summary.BriefReport() << "\n";
            
            //printf("pose optimization time: %f \n", tmp_t.toc());
            /*
            for (int j = 0 ; j < i; j++)
            {
                printf("optimize i: %d p: %f, %f, %f\n", j, t_array[j][0], t_array[j][1], t_array[j][2] );
            }
            */
            m_keyframelist.lock();
            //优化完成,使用优化后的位姿来更新关键帧列表中index大于等于first_looped_index的所有关键帧的位姿
            i = 0;
            for (it = keyframelist.begin(); it != keyframelist.end(); it++)
            {
                if ((*it)->index < first_looped_index)
                    continue;
                Quaterniond tmp_q;
                //向量转换为矩阵
                tmp_q = Utility::ypr2R(Vector3d(euler_array[i][0], euler_array[i][1], euler_array[i][2]));
                Vector3d tmp_t = Vector3d(t_array[i][0], t_array[i][1], t_array[i][2]);
                Matrix3d tmp_r = tmp_q.toRotationMatrix();
                (*it)-> updatePose(tmp_t, tmp_r);

                if ((*it)->index == cur_index)
                    break;
                i++;
            }
            //根据计算出当前帧的drift,更新全部关键帧位姿
            Vector3d cur_t, vio_t;
            Matrix3d cur_r, vio_r;
            //获取优化后当前帧的位姿cur_t,cur_r
            cur_kf->getPose(cur_t, cur_r);
            //获取优化前有漂移的当前帧的位姿vio_t,vio_r
            cur_kf->getVioPose(vio_t, vio_r);
            m_drift.lock();
            yaw_drift = Utility::R2ypr(cur_r).x() - Utility::R2ypr(vio_r).x();
            r_drift = Utility::ypr2R(Vector3d(yaw_drift, 0, 0));
            t_drift = cur_t - r_drift * vio_t;
            m_drift.unlock();
            //cout << "t_drift " << t_drift.transpose() << endl;
            //cout << "r_drift " << Utility::R2ypr(r_drift).transpose() << endl;
            //cout << "yaw drift " << yaw_drift << endl;

            it++;
            //下面代码为把当前关键帧it之后的关键帧的位姿通过求解的偏移量转换到world坐标系下
            for (; it != keyframelist.end(); it++)
            {
                Vector3d P;
                Matrix3d R;
                (*it)->getVioPose(P, R);
                P = r_drift * P + t_drift;
                R = r_drift * R;
                (*it)->updatePose(P, R);
            }
            m_keyframelist.unlock();
            //优化完后更新path
            updatePath();
        }

        std::chrono::milliseconds dura(2000);
        std::this_thread::sleep_for(dura);
    }
}

这里除了梳理优化的函数中的代码流程,还需要对比以上的残差公式来看一下序列边和闭环边的残差计算。FourDOFError类中的operator()中是序列边的残差计算代码。

struct FourDOFError
{
	FourDOFError(double t_x, double t_y, double t_z, double relative_yaw, double pitch_i, double roll_i)
				  :t_x(t_x), t_y(t_y), t_z(t_z), relative_yaw(relative_yaw), pitch_i(pitch_i), roll_i(roll_i){}

	template <typename T>
	bool operator()(const T* const yaw_i, const T* ti, const T* yaw_j, const T* tj, T* residuals) const
	{
		T t_w_ij[3];
		t_w_ij[0] = tj[0] - ti[0];
		t_w_ij[1] = tj[1] - ti[1];
		t_w_ij[2] = tj[2] - ti[2];

		// euler to rotation
		T w_R_i[9];
		YawPitchRollToRotationMatrix(yaw_i[0], T(pitch_i), T(roll_i), w_R_i);
		// rotation transpose
		T i_R_w[9];
		//求出旋转矩阵的转置
		RotationMatrixTranspose(w_R_i, i_R_w);
		// rotation matrix rotate point
		T t_i_ij[3];
		RotationMatrixRotatePoint(i_R_w, t_w_ij, t_i_ij);
		//计算残差
		residuals[0] = (t_i_ij[0] - T(t_x));
		residuals[1] = (t_i_ij[1] - T(t_y));
		residuals[2] = (t_i_ij[2] - T(t_z));
		residuals[3] = NormalizeAngle(yaw_j[0] - yaw_i[0] - T(relative_yaw));

		return true;
	}

	static ceres::CostFunction* Create(const double t_x, const double t_y, const double t_z,
									   const double relative_yaw, const double pitch_i, const double roll_i) 
	{
	  return (new ceres::AutoDiffCostFunction<
	          FourDOFError, 4, 1, 3, 1, 3>(
	          	new FourDOFError(t_x, t_y, t_z, relative_yaw, pitch_i, roll_i)));
	}

	double t_x, t_y, t_z;
	double relative_yaw, pitch_i, roll_i;

};

FourDOFWeightError类中的operator()是闭环边的残差计算代码。

struct FourDOFWeightError
{
	FourDOFWeightError(double t_x, double t_y, double t_z, double relative_yaw, double pitch_i, double roll_i)
				  :t_x(t_x), t_y(t_y), t_z(t_z), relative_yaw(relative_yaw), pitch_i(pitch_i), roll_i(roll_i){
				  	weight = 1;
				  }

	template <typename T>
	bool operator()(const T* const yaw_i, const T* ti, const T* yaw_j, const T* tj, T* residuals) const
	{
		T t_w_ij[3];
		t_w_ij[0] = tj[0] - ti[0];
		t_w_ij[1] = tj[1] - ti[1];
		t_w_ij[2] = tj[2] - ti[2];

		// euler to rotation
		T w_R_i[9];
		YawPitchRollToRotationMatrix(yaw_i[0], T(pitch_i), T(roll_i), w_R_i);
		// rotation transpose
		T i_R_w[9];
		RotationMatrixTranspose(w_R_i, i_R_w);
		// rotation matrix rotate point
		T t_i_ij[3];
		RotationMatrixRotatePoint(i_R_w, t_w_ij, t_i_ij);
		//计算残差
		residuals[0] = (t_i_ij[0] - T(t_x)) * T(weight);
		residuals[1] = (t_i_ij[1] - T(t_y)) * T(weight);
		residuals[2] = (t_i_ij[2] - T(t_z)) * T(weight);
		residuals[3] = NormalizeAngle((yaw_j[0] - yaw_i[0] - T(relative_yaw))) * T(weight) / T(10.0);

		return true;
	}

	static ceres::CostFunction* Create(const double t_x, const double t_y, const double t_z,
									   const double relative_yaw, const double pitch_i, const double roll_i) 
	{
	  return (new ceres::AutoDiffCostFunction<
	          FourDOFWeightError, 4, 1, 3, 1, 3>(
	          	new FourDOFWeightError(t_x, t_y, t_z, relative_yaw, pitch_i, roll_i)));
	}

	double t_x, t_y, t_z;
	double relative_yaw, pitch_i, roll_i;
	double weight;

};

VINS-Mono代码阅读笔记(十四):posegraph的存储和加载

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

VINS-Mono代码阅读笔记(十三):posegraph中四自由度位姿优化 的相关文章

随机推荐

  • STL不同容器的优缺点

    一 容器的分类 1 序列容器 xff08 1 xff09 vector 典型的序列容器 xff0c 任意元素的读取 修改具有O 1 xff0c 在序列尾部进行插入 删除是O 1 xff0c 但在序列的头部插入 删除的时间复杂度是O n xf
  • c语言(数组)

    交换算法 xff08 将最小值换到第一位 xff0c 最大值换到最后一位 xff09 include lt stdio h gt void main int o 61 0 int buf 10 接收用户输入的数组 for o lt 10 o
  • STM32外设串口资源用完了怎么办--------串口模拟解决问题(再也不用多个STM32或其它MCU)

    之前做项目的时候遇到了一个问题 xff0c 当把MCU本身的串口资源用完的时候 xff0c 却还需要使用多几个串口 xff0c 又不想使用几个MCU解决这个问题 那么模拟串口是解决这个问题的一种方法 下图是我对串口通信时序图的个人理解 xf
  • scanf函数中的格式字符串及注意事项

    scanf函数称为格式输入函数 xff0c 即按用户指定的格式从键盘上把数据输入到指定的变量之中 scanf函数的一般形式为 xff1a scanf 格式控制字符串 地址表列 xff1b 格式字符串的一般形式为 xff1a 输入数据宽度 长
  • 【C/C++】标准库, STL, Boost等的联系

    Backto C C 43 43 Index 标准库 最最开始 只有 C 语言 使用着使用着 一些常用的功能被写成了库 各种组织都是自己私有的库 后来为了方便统一使用和交流 就制定了标准 标准里的库 就是 C 标准库 后来 C 43 43
  • 数组与链表的优缺点和区别

    1 数组 xff1a 数组是将元素在内存中连续存放 xff0c 由于每个元素占用内存相同 xff0c 可以通过下标迅速访问数组中任何元素 但是如果要 在数组中增加一个元素 xff0c 需要移动大量元素 xff0c 在内存中空出一 个元素的空
  • 堆空间与栈空间的区别

    1 栈区 xff08 stack xff09 xff1a 又编译器自动分配释放 xff0c 存放函数的参数值 xff0c 局部变量的值等 xff0c 其操作方式类似于数据结构的 栈 2 堆区 xff08 heap xff09 xff1a 一
  • strtok函数及其实现

    头文件 xff1a include lt string h gt 定义函数 xff1a char strtok char s const char delim 函数说明 xff1a strtok 用来将字符串分割成一个个片段 参数s 指向欲
  • 服务器和客户机的信息函数以及读写函数

    1 服务器和客户机的信息函数 xff08 1 xff09 字节转换函数 在网络上面有着许多类型的机器 xff0c 这些机器在表示数据的字节顺序是不同的 xff0c 比如i386芯片是低字节在内存地址的低 端 xff0c 高字节在高端 xff
  • TCP协议的拥塞控制机制

    最初的TCP协议只有基于窗口的流控制 xff08 flow control xff09 机制而没有拥塞控制机制 流控制作为接受方管理发送方发送 数据的方式 xff0c 用来防止接受方可用的数据缓存空间的溢出 流控制是一种局部控制机制 xff
  • 构造函数为什么不能是虚函数

    1 从存储空间角度 xff0c 虚函数对应一个指向vtable虚函数表的指针 xff0c 这大家都知道 xff0c 可是这个指向vtable的指针其实是存储在对象的内存空间的 问题出来了 xff0c 如果构造函数是虚的 xff0c 就需要通
  • openmv学习五:OLED

    首先需要将SSD1306x py这个文件放到OPENMV中 代码 from machine import I2C Pin 从 machine 模块导入 I2C Pin 子模块 from ssd1306x import SSD1306 I2C
  • 自旋锁的实现及优化

    自旋锁也是一种互斥锁 xff0c 和mutex锁相比 xff0c 它的特点是不会阻塞 xff0c 如果申请不到锁 xff0c 就会不断地循环检测锁变量的状态 xff0c 也就是自旋 自旋锁的实现算法大多使用TAS算法或者CAS算法 TAS算
  • C语言----详解字符串相关的库函数(建议收藏)

    文章目录 系列文章目录前言一 C语言相关字符串库函数一览表 二 strlen函数 三 strcpy函数四 strcat函数 五 strcmp函数 六 strncpy函数 七 strncat函数 八 strncmp函数 九 strstr函数
  • C/C++头文件与变量的声明和定义

    C C 43 43 头文件与变量的声明和定义 最近遇到了变量重复包含的问题 xff0c 才发现自己有好多知识已经模糊了 xff0c 真惭愧 首先说下头文件 xff0c 其实头文件对计算机而言没什么作用 xff0c 她只是在预编译时在 inc
  • C语言寄存器变量register

    用register声明的变量是寄存器变量 xff0c 是存放在CPU的寄存器里的 而我们平时声明的变量是存放在内存中的 虽说内存的速度已经很快了 xff0c 不过跟寄存器比起来还是差得远 寄存器变量和普通变量比起来速度上的差异很大 xff0
  • Curl(C++)使用教程

    1 Curl简介 2 Easy interface 3 Multi interface 1 Curl简介 libcurl作为是一个多协议的便于客户端使用的URL传输库 xff0c 基于C语言 xff0c 提供C语言的API接口 xff0c
  • GPS原始RMC数据解析之DDMM.MMMM

    环境 GPS BD 定位模块 1 模块输出数据如下 GNRMC 100756 000 V 4000 0005 N 11559 9745 E 6 21 223 00 050313 N 68 2 了解换算规则 ddmm mmmm规则和dd dd
  • VINS-Mono代码阅读笔记(三):vins_estimator中main函数分析

    在VINS Mono代码阅读笔记 xff08 一 xff09 xff1a 从Euroc数据集的运行开始 中我们已经初步知道了vins estimator中订阅和发布的topic xff0c 那么 xff0c 在研究vins estimato
  • VINS-Mono代码阅读笔记(十三):posegraph中四自由度位姿优化

    本篇笔记紧接着VINS Mono代码阅读笔记 xff08 十二 xff09 xff1a 将关键帧加入位姿图当中 xff0c 来学习pose graph当中的紧耦合优化部分 在重定位完成之后 xff0c 进行位姿图优化是为了将已经产生的所有位