浅谈VINS中的global fusion节点

2023-05-16

转载自:https://zhuanlan.zhihu.com/p/75492883

浅谈VINS中的global fusion节点

刘弟弟

刘弟弟

资深小朋友

VINS-Fusion开源已经很长时间了,但是一直也没时间看。最近需要用到gps与VO融合,就先学习了global fusion节点。global fusion节点中数据融合的思路非常巧妙,令人赞叹,并且代码比较简单,很容易读懂。下面是一些学习源码的体会与大家分享,如有不当之处,欢迎批评指正~

1.整体流程分析

首先 global Estimator 节点订阅两个节点:

  • 1.VIO输出的 nav_msgs::Odometry 类型消息,这个定位信息包含了VIO的位置和姿态,其坐标系原点位于VIO的第一帧处。
ros::Subscriber sub_vio = n.subscribe("/vins_estimator/odometry", 100, vio_callback);
  • 2.GPS输出的sensor_msgs::NavSatFixConstPtr 类型消息,这个是全局定位信息,用经纬度来表示,其坐标原点位于该GPS坐标系下定义的0经度0纬度处。
ros::Subscriber sub_GPS = n.subscribe("/gps", 100, GPS_callback);

我们再分别看其回调函数(这里贴出来的代码只保留了主干部分)

先看GPS回调函数,很简单,只是把GPS消息存储到了队列里面

void GPS_callback(const sensor_msgs::NavSatFixConstPtr &GPS_msg)
{
    //printf("gps_callback! \n");
    m_buf.lock();
    gpsQueue.push(GPS_msg);
    m_buf.unlock();
}

VIO回调函数,请看注释:

void vio_callback(const nav_msgs::Odometry::ConstPtr &pose_msg)
{
    double t = pose_msg->header.stamp.toSec();
    last_vio_t = t;
    // 获取VIO输出的位置(三维向量),姿态(四元数)
    Eigen::Vector3d vio_t;
    Eigen::Quaterniond vio_q;
    ......
    /// 位姿传入global Estimator中
    globalEstimator.inputOdom(t, vio_t, vio_q);
    m_buf.lock();
    // 寻找与VIO时间戳相对应的GPS消息
    // 细心的读者可能会疑惑,这里需不需要对GPS和VIO进行硬件上的时间戳同步呢?
    // 这个问题请看总结与讨论
    while(!gpsQueue.empty())
    {
        // 获取最老的GPS数据和其时间
        sensor_msgs::NavSatFixConstPtr GPS_msg = gpsQueue.front();
        double gps_t = GPS_msg->header.stamp.toSec();
        // 10ms sync tolerance
        // +- 10ms的时间偏差
        if(gps_t >= t - 0.01 && gps_t <= t + 0.01)
        {   /// gps的经纬度,海拔高度
            double latitude = GPS_msg->latitude;
            double longitude = GPS_msg->longitude;
            double altitude = GPS_msg->altitude;
            // gps 数据的方差
            double pos_accuracy = GPS_msg->position_covariance[0];
            if(pos_accuracy <= 0)
                pos_accuracy = 1;
            //printf("receive covariance %lf \n", pos_accuracy);
            /// GPS_msg->status.status 这个数字代表了GPS的状态(固定解,浮点解等)
            /// 具体可以谷歌
            // if(GPS_msg->status.status > 8)
            // 向globalEstimator中输入GPS数据
            globalEstimator.inputGPS(t, latitude, longitude, altitude, pos_accuracy);
            gpsQueue.pop();
            break;
        }
        else if(gps_t < t - 0.01)
            gpsQueue.pop();
        else if(gps_t > t + 0.01)
            break;
    }
    m_buf.unlock();
    ......
    // 广播轨迹(略)......
    pub_global_odometry.publish(odometry);
    pub_global_path.publish(*global_path);
    publish_car_model(t, global_t, global_q);
    // 位姿写入文本文件(略)......
}

可以看出,global Fusion的优化策略是收到一帧VIO数据,就寻找相应的GPS数据来进行优化。我们下面主要来看一下globalEstimator中的inputOdom()和inputGPS()这两个函数。

首先看下 inputGPS():

void GlobalOptimization::inputGPS(double t, double latitude, 
                                  double longitude, 
                                  double altitude, 
                                  double posAccuracy)
{
	double xyz[3];
        // 因为经纬度表示的是地球上的坐标,而地球是一个球形,
        // 需要首先把经纬度转化到平面坐标系上
        // 值得一提的是,GPS2XYZ()并非把经纬度转化到世界坐标系下(以0经度,0纬度为原点),
        // 而是以第一帧GPS数据为坐标原点,这一点需要额外注意
	GPS2XYZ(latitude, longitude, altitude, xyz);
        // 存入经纬度计算出的平面坐标,存入GPSPositionMap中
	vector<double> tmp{xyz[0], xyz[1], xyz[2], posAccuracy};
	GPSPositionMap[t] = tmp;
    newGPS = true;
}

再看inputOdom():

void GlobalOptimization::inputOdom(double t, Eigen::Vector3d OdomP, Eigen::Quaterniond OdomQ)
{
    mPoseMap.lock();
    // 把vio直接输出的位姿存入 localPoseMap 中
    vector<double> localPose{OdomP.x(), OdomP.y(), OdomP.z(), 
    			     OdomQ.w(), OdomQ.x(), OdomQ.y(), OdomQ.z()};
    localPoseMap[t] = localPose;
    Eigen::Quaterniond globalQ;
    /// 把VIO转换到GPS坐标系下,准确的说是转换到以第一帧GPS为原点的坐标系下
    /// 转换之后的位姿插入到globalPoseMap 中
    globalQ = WGPS_T_WVIO.block<3, 3>(0, 0) * OdomQ;
    Eigen::Vector3d globalP = 
                       WGPS_T_WVIO.block<3, 3>(0, 0) * OdomP + WGPS_T_WVIO.block<3, 1>(0, 3);
    vector<double> globalPose{globalP.x(), globalP.y(), globalP.z(),
                              globalQ.w(), globalQ.x(), globalQ.y(), globalQ.z()};
    globalPoseMap[t] = globalPose;
    lastP = globalP;
    lastQ = globalQ;
    // 把最新的全局姿态插入轨迹当中(过程略)
    ......
    global_path.poses.push_back(pose_stamped);
    mPoseMap.unlock();
}

现在两种数据都收到以后,万事俱备,我们看一下 void GlobalOptimization::optimize()这个函数:

这个函数开了一个线程来做优化(这个代码太长了,贴一部分把):

  1. 首先使用ceres构建最小二乘问题,这个没啥可说的
  2. 状态量赋初值,添加参数块。可以看出来,迭代的初始值是globalPoseMap中的值,也就是VIO转换到GPS坐标系下的值。
            int length = localPoseMap.size();
            // w^t_i   w^q_i
            double t_array[length][3];
            double q_array[length][4];
            map<double, vector<double>>::iterator iter;
            iter = globalPoseMap.begin();
            for (int i = 0; i < length; i++, iter++)
            {
                t_array[i][0] = iter->second[0];
                t_array[i][1] = iter->second[1];
                t_array[i][2] = iter->second[2];
                q_array[i][0] = iter->second[3];
                q_array[i][1] = iter->second[4];
                q_array[i][2] = iter->second[5];
                q_array[i][3] = iter->second[6];
                problem.AddParameterBlock(q_array[i], 4, local_parameterization);
                problem.AddParameterBlock(t_array[i], 3);
            }

3.然后添加残差:

for (iterVIO = localPoseMap.begin(); iterVIO != localPoseMap.end(); iterVIO++, i++) {
    //vio factor
    // 添加VIO残差,观测量是两帧VIO数据之差,是相对的。而下面的GPS是绝对的
    iterVIONext = iterVIO;
    iterVIONext++;
    if (iterVIONext != localPoseMap.end()) {
        /// 计算两帧VIO之间的相对差(略)......
        ceres::CostFunction *vio_function = RelativeRTError::Create(iPj.x(), iPj.y(), iPj.z(),
                                                                    iQj.w(), iQj.x(), iQj.y(), iQj.z(),
                                                                    0.1, 0.01);
        problem.AddResidualBlock(vio_function, NULL, q_array[i], t_array[i], q_array[i + 1], t_array[i + 1]);
    }
    // gps factor
    // GPS残差,这个观测量直接就是GPS的测量数据,
    // 残差计算的是GPS和优化变量的差,这个是绝对的差。
    double t = iterVIO->first;
    iterGPS = GPSPositionMap.find(t);
    if (iterGPS != GPSPositionMap.end()) {
        ceres::CostFunction *gps_function = TError::Create(iterGPS->second[0], iterGPS->second[1],
                                                           iterGPS->second[2], iterGPS->second[3]);
        problem.AddResidualBlock(gps_function, loss_function, t_array[i]);
    }

}

优化完成后,再根据优化结果更新姿态就ok啦。为了防止VIO漂移过大,每次优化完成还需要计算一下VIO到GPS坐标系的变换。

2.GPS与VIO融合策略

(知乎这个文章编辑器是真的卡........)

根据上文的分析,我们可以看出整体的优化策略和位姿图非常相似,因为观测量是相邻两帧VIO之间的差和GPS坐标,所以global Fusion 节点相当于把对应时间戳的GPS位姿和VIO位姿的差,均匀分布到每两个相邻的VIO之间。使得整体的误差和最小化。

3.总结与讨论

1.思考

根据上文中分析的优化策略,global fusion的应用场景应该是GPS频率较低,VIO频率较高的系统。fusion 默认发布频率位10hz,而现在的GPS可以达到20hz,如果在这种系统上使用,你可能还需要修改下VIO或者GPS频率。

2.GPS与VIO时间不同步

上文提到了,在多传感器融合系统中,传感器往往需要做时钟同步,那么global Fusion需要么?GPS分为为很多种,我们常见的GPS模块精度较低,十几米甚至几十米的误差,这种情况下,同不同步没那么重要了,因为GPS方差太大。另外一种比较常见的是RTK-GPS ,在无遮挡的情况下,室外精度可以达到 2cm之内,输出频率可以达到20hz,这种情况下,不同步时间戳会对系统产生影响,如果VIO要和RTK做松耦合,这点还需要注意。

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

浅谈VINS中的global fusion节点 的相关文章

  • 带有 if 语句的函数中的全局变量

    好吧 我目前正在做一个用 python 制作二十一点游戏的项目 但遇到了一些麻烦 我的问题之一是我不知道何时将变量定义为全局变量 特别是在带有 if 语句的函数中 如果我在 if 语句之外有一个全局变量 我是否必须声明该变量在 if 语句内
  • 如何定义可以在应用程序中的任何位置访问的全局变量? [复制]

    这个问题在这里已经有答案了 可能的重复 全局 int 变量目标 c 我想创建一个全局变量 我想在任何地方访问这个变量 Java 等效项 static var score int 0 例如 如果我在 Game 类中定义一个全局变量 如何访问这
  • 从 PHP 函数内部创建全局变量

    我正在尝试定义动态变量 我为此使用了一个函数 但我不知道如何将新的 var 定义为全局变量 因为它从未在函数之前创建 那可能吗 Thanks edit 好的 这就是我构建的 有那么危险吗 function extract values ro
  • Javascript 和 ESLint 中的全局变量

    我有多个 javascript 文件 并且在一个文件中定义了一些全局变量 该变量先于其他文件加载 因此 第一个文件之后加载的所有文件都可以访问全局变量 然而 ESLint 将全局变量显示为 未定义 我不想改变 ESLint 的规则 我想找到
  • 关于 Objective-C 项目中使用的 extern 的 3 个问题

    当我使用这个词时extern在方法或变量声明之前 我是否将其设置为全局的 从而在整个项目中可读 可写 可用 如果我在关键字之前使用 extern 是否有可能我的项目的一部分仍然无法访问它 例如 仅通过子类 例如当我使用 受保护 时 exte
  • 玩! 2.0 Scala - 访问全局对象

    我已经声明了一个在应用程序启动时实例化的对象 我想在控制器内访问它 这是插件的一部分 我希望能够使用该插件 但我似乎无法通过第一部分 找到MyWebsocketConnection目的 没有一个示例显示如何执行此操作 我不想注入控制器 因为
  • 如何全局 #define 预处理器变量?

    我正在用 C 编写 Arduino 草图 我希望用户能够 define直接在 sketch ino 文件中的常量 编译代码时需要该常量 Arduino IDE 使用 g 编译器 假设我们有三个文件 sketch ino sketch h s
  • 为类变量赋值是为该对象的所有实例分配它

    我有一个带字典的课 我创建了该类的 n 个实例 当我 该字典中某个键上的值时 它会反映在我从该对象实例化的每个对象中 如何使该字典对于该类的每个实例都是唯一的 这是我创建对象的方法 for num in range 0 numOfPlaye
  • 如何查看另一个 php 文件中定义的变量?

    我在所有 php 文件中使用相同的常量 我不想在我的所有文件中分配此变量的值 因此 我想创建一个 parameters php 文件并在那里进行分配 然后在所有其他文件中我include parameters php 并使用 paramet
  • PowerShell 和全局函数

    为什么下面的代码不起作用 根据这篇文章 全局的用法应该是正确的 http technet microsoft com en us library ff730957 aspx http technet microsoft com en us
  • Tkinter 全局绑定

    是否可以用一行将所有小部件绑定到一个命令 如果我可以输入一行而不是单独执行每个小部件 那就太好了 你会使用bind all http epydoc sourceforge net stdlib Tkinter Misc class html
  • Mojolicious 中用于处理不同路径的全局变量和线程

    在我的 Mojolicious perl 代码中 我处理从远程客户端创建和监视的作业 我将作业保存在哈希数组中 这是一个全局变量 然后它在 PUT job create 和 GET job status 的处理程序中使用 当使用 PUT j
  • C++ 中的静态全局变量

    我想通过 malloc 方法创建一个整数数组 我希望这个数组是全局的并且可以在我的程序中的任何地方使用 我将代码放入一个头文件中 如下所示 static int pieces 然后我有一个函数可以用我想要的数字填充它 该函数位于命名空间中
  • 如何使用全局选择器响应除一个元素之外的所有点击事件?

    如果我有一个按钮
  • 如何在非调试模式下获取 Adob​​e AIR 全局运行时错误的堆栈跟踪?

    新版本的AIR使我们能够全局捕获运行时错误并处理它们 问题是 除了错误 ID 错误消息和名称之外 它没有堆栈跟踪或任何有关错误的有用信息 例如 它可能告诉我发生了空指针异常 但它不会告诉我在哪里 哪个方法或任何事情 运行时的调试版本为我们提
  • 无法通过 exec() 语句更改函数中的全局变量?

    为什么我不能使用 exec 从函数内部更改全局变量 当赋值语句位于 exec 之外时 它可以正常工作 这是我的问题的一个例子 gt gt gt myvar test gt gt gt def myfunc global myvar exec
  • git config 命令返回:致命:无法读取配置文件'%HOMEDRIVE%%HOMEPATH%/.gitconfig':没有这样的文件或目录

    在我的 Windows 10 机器上 当我运行时 git config global l 它失败并显示消息 致命 无法读取配置文件 HOMEDRIVE HOMEPATH gitconfig 没有这样的文件或目录 两个环境变量看起来都不错 对
  • 在视图之间传递变量 SwiftUI

    再次基本问题 我想让变量 anytext 对于我要添加的所有未来视图都可见且可访问 在我的例子中 变量将是String 如果是的话 程序会改变吗 Float 我怎样才能将其另存为全局变量 如果我重新启动应用程序 变量会自行删除吗 如何保存即
  • 我应该担心“窗口未定义”JSLint 严格模式错误吗?

    这不会在严格模式下通过 JSLint use strict function w w alert w window 来自 jslint com 的错误如下所示 第 4 行第 3 行字符出现问题 window 未定义 window 隐含全局
  • 可以在 .h 文件中声明静态全局变量吗?

    static 关键字将全局变量的范围限制为该翻译单元 如果我使用static int x在 h 文件中并包含该 h 文件每隔一个文件 它们不会都属于同一个翻译单元吗 那么 x不是到处可见吗 那么现在static有什么作用呢 另外 有没有什么

随机推荐