ros版本KF-GINS(带有rviz可视化结果显示及文件生成)

news2024/11/17 3:55:05

原始的KF-GINS是基于读写文件实现的,在此基础上改进了ros版本,将原始数据文件转换为rosbag格式,并实现了rviz下的可视化结果显示,代码已共享至github
https://github.com/slender1031/kf-gins-ros

感谢武汉大学卫星导航定位技术研究中心多源智能导航实验室(i2Nav)牛小骥教授团队开源的KF-GINS软件平台
https://github.com/i2Nav-WHU/KF-GINS

ros版本KF-GINS(带有rviz可视化结果显示及文件生成)

  • 1 数据格式转换
  • 2 KF-GINS-ROS
    • 2.1 程序编译与运行
    • 2.2 rviz可视化效果
  • 3 程序框架
    • 3.1 ros数据流输入
    • 3.2 时间戳对齐
    • 3.3 结果发布

1 数据格式转换

数据包含GNSS定位结果、IMU原始观测值、配置文件和真值

在这里插入图片描述

参考下面这位大佬的博客,在此代码的基础上做了修改,生成了含有gnss与imu数据的rosbag文件
自采数据转rosbag
https://github.com/zzzzyp-sgg/SLAM-Tool

#include <ros/ros.h>
#include <rosbag/bag.h>
#include <sensor_msgs/Imu.h>
#include <sensor_msgs/Image.h>
#include <sensor_msgs/NavSatFix.h>

#include <opencv2/opencv.hpp>
#include <cv_bridge/cv_bridge.h>

#include <fstream>
#include <sstream>

double dt=1.0/200;  //imu数据采样间隔

/* imu数据转bag,bag包以IMU数据为基础(因为通常imu数据是时间最长的) */
void imu2bag(rosbag::Bag &bag, const std::string imuFile, const std::string outBag, int gpsWeek)
{
    std::ifstream file(imuFile);
    if (!file.is_open())
    {
        ROS_ERROR_STREAM("Failed to open file!");
        return;
    }

    bag.open(outBag, rosbag::bagmode::Write);
    std::string line;

    while (std::getline(file, line))
    {
        // 将每行数据分割为各个字段
        std::istringstream iss(line);
        double time, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z;
        if (!(iss >> time >> gyro_x >> gyro_y >> gyro_z >> accel_x >> accel_y >> accel_z))
        {
            ROS_WARN_STREAM("Failed to parse line: " << line);
            continue;
        }

        // 创建IMU消息
        sensor_msgs::Imu imu_msg;
        // 315964800是GPS起始时间和计算机起始时间的一个固定差值
        time = time + 315964800 + 604800 * gpsWeek - 8 * 3600;
        imu_msg.header.stamp = ros::Time(time);
        imu_msg.angular_velocity.x = gyro_x/dt;
        imu_msg.angular_velocity.y = gyro_y/dt;
        imu_msg.angular_velocity.z = gyro_z/dt;
        imu_msg.linear_acceleration.x = accel_x/dt;
        imu_msg.linear_acceleration.y = accel_y/dt;
        imu_msg.linear_acceleration.z = accel_z/dt;

        // 写入ROSbag文件
        bag.write("/imu/data", ros::Time(time), imu_msg);
    }

    bag.close();
    file.close();
    std::cout << "imu data convert finished!" << std::endl;
}


/* gnss数据转bag */
void gnss2bag(rosbag::Bag &bag, const std::string gnssFile, const std::string outBag, int gpsWeek)
{
     std::ifstream file(gnssFile);
    if (!file.is_open())
    {
        ROS_ERROR_STREAM("Failed to open file!");
        return;
    }

    bag.open(outBag, rosbag::bagmode::Append);

    std::string line;
    while (std::getline(file, line))
    {
        std::istringstream iss(line);
        double time, lat, lon, h, vn, ve, vd;
        if (!(iss >> time >> lat >>lon >>h >>vn >>ve >>vd))
        {
            ROS_WARN_STREAM("Failed to parse line: " << line);
            continue;
        }

        // 创建gnss消息
        sensor_msgs::NavSatFix gnss_msg;
        // 315964800是GPS起始时间和计算机起始时间的一个固定差值
        time = time + 315964800 + 604800 * gpsWeek - 8 * 3600;
        gnss_msg.header.stamp = ros::Time(time);
        gnss_msg.latitude=lat;
        gnss_msg.longitude=lon;
        gnss_msg.altitude=h;

        gnss_msg.position_covariance[0]=0.005*0.005;
        gnss_msg.position_covariance[3]=0.004*0.004;
        gnss_msg.position_covariance[6]=0.008*0.008;

        // 写入ROSbag文件
        bag.write("/gnss", ros::Time(time), gnss_msg);
    }

    bag.close();
    file.close();
    std::cout << "gnss data convert finished!" << std::endl;
}


int main(int argc, char **argv)
{
    ros::init(argc, argv, "data_to_rosbag");
    ros::NodeHandle nh;

    // 创建rosbag文件(注意修改数据文件路径)
    rosbag::Bag bag;
    int gpsWeek = 2017;
    std::string imuFile = "src/data2bag/Leador-A15.txt";                 //imu数据文件
    std::string gnssFile = "src/data2bag/GNSS-RTK.txt";                  //gnss数据文件

    std::string outBag ="src/data2bag/output.bag";                         //生成的结果文件
    
    imu2bag(bag, imuFile, outBag, gpsWeek);         // imu转bag
    gnss2bag(bag, gnssFile, outBag, gpsWeek);      // gnss转bag


    return 0;
}

运行:

rosrun data_convert data_convert_node

生成的rosbag:
请添加图片描述


2 KF-GINS-ROS

2.1 程序编译与运行

环境配置:

  • Ubuntu18.04
  • ros-melodic
  • Eigen3

代码编译:

cd && mkdir /gins_ws/src
git clone https://github.com/slender1031/kf-gins-ros.git
cd ..
catkin_make

运行:

source devel/setup.bash
rosrun data_convert data_convert_node
rosrun gins gins_node [path to YAML]

2.2 rviz可视化效果

rosbag play output.bag
roslaunch gins gins_rviz.launch

这里订阅的是gnss更新时刻,ned系下的路径结果/gins_ned_path,理论上是1s一个,也可以订阅imu时刻

请添加图片描述

请添加图片描述


3 程序框架

整体解算流程和kf-gins是一致的,做imu状态预测,然后在gnss时刻做量测更新,改动的部分主要在于输入ros数据流、ros时间戳对齐、topic发布

3.1 ros数据流输入

主函数订阅topic,读入配置文件yaml

int main(int argc, char **argv)
{
    ros::init(argc, argv, "gins");
    ros::NodeHandle n("~");
    ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Info);

    if(argc != 2)
    {
        printf("please intput: rosrun gins gins_node [config file] \n");
        return 1;
    }

    string config_file = argv[1];
    printf("config_file: %s\n", argv[1]);

    readParameters(config_file);
    ROS_WARN("waiting for gnss and imu...");

    ros::Subscriber sub_imu = n.subscribe(GNSS_TOPIC, 100, gnss_callback);
    ros::Subscriber sub_gnss = n.subscribe(IMU_TOPIC, 1000, imu_callback);

    pub_ins_odometry = n.advertise<nav_msgs::Odometry>("/insmech_odom", 1000);
    pub_ins_path = n.advertise<nav_msgs::Path>("/insmech_path", 1000);
    pub_gins_blh = n.advertise<sensor_msgs::NavSatFix>("/gins_blh", 1000);
    pub_gins_ned = n.advertise<nav_msgs::Path>("/gins_ned_path", 1000);

    std::thread measurement_process{process};
    
    ros::spin();
    return 0;
}

imu和gnss消息的回调函数

void imu_callback(const sensor_msgs::ImuConstPtr &imu_msg)
{
    m_buf.lock();
    imu_buf.push(imu_msg);
    m_buf.unlock();
    con.notify_one();
}

void gnss_callback(const sensor_msgs::NavSatFixConstPtr &gnss_msg)
{
    m_buf.lock();  
    gnss_buf.push(gnss_msg);
    m_buf.unlock();
    con.notify_one();
}

3.2 时间戳对齐

getMeasurements用来划分数据流,以下图中绿色框为一组数据,在最后两个imu时刻做内插,得到gnss时刻对应的观测数据,然后递推到gnss时刻

在这里插入图片描述

//IMU与GNSS的数据流做同步,以GNSS的时间间隔,确定一组imu_msg
bool getMeasurements(std::vector<sensor_msgs::ImuConstPtr> &imu_msg, sensor_msgs::NavSatFixConstPtr &gnss_msg)
{
    //当前imu和gnss数据都为空
    if(imu_buf.empty() || gnss_buf.empty()){
        return false;
    }

    //imu最新的时刻仍然小于gnss最早时刻
    if(imu_buf.back()->header.stamp.toSec()<gnss_buf.front()->header.stamp.toSec()){
        return false;
    }
        
    double front_imu_ts=imu_buf.front()->header.stamp.toSec();
    double front_gnss_ts=gnss_buf.front()->header.stamp.toSec();

    //第一个imu时间戳大于gnss时间戳,就把gnss数据丢掉
    while(!gnss_buf.empty() && front_imu_ts>front_gnss_ts )
    {
        ROS_WARN("throw gnss_msg, only should happen at the beginning");
        gnss_buf.pop();

        if(gnss_buf.empty()) return false;
        front_gnss_ts=gnss_buf.front()->header.stamp.toSec();
    }
    
    gnss_msg=gnss_buf.front();
    gnss_buf.pop();

    //截取两个gnss时刻中间的imu,放入imu_buf,作为一组imu数据
    while (!imu_buf.empty() && imu_buf.front()->header.stamp.toSec() < gnss_msg->header.stamp.toSec())
    {
        imu_msg.emplace_back(imu_buf.front());
        imu_buf.pop();
    }

    //多取出一个gnss时刻之后的imu数据,用来做内插,对齐到gnss时刻
    if(!imu_buf.empty()){
        imu_msg.emplace_back(imu_buf.front());
        imu_buf.pop();
    }

    if (imu_msg.empty()){
        ROS_WARN("no imu between two GNSS");
    }
    
    return true;
}

3.3 结果发布

rviz显示和结果文件输出

void pubGINS(const GINSEngine &gins_engine, const std_msgs::Header &header)
{
    sensor_msgs::NavSatFix gps_position;
	gps_position.header=header;
    gps_position.header.frame_id = "world";

    Vector3d blh = gins_engine.pvacur.blh;
	gps_position.latitude  = blh[0];
	gps_position.longitude = blh[1];
	gps_position.altitude  = blh[2];	
    for(int i=0; i<9; i++){
        gps_position.position_covariance[i]=gins_engine.Cov_(i/3, i%3);
    }
        
	pub_gins_blh.publish(gps_position);  //发布更新后的BLH坐标

    //BLH转NED
    if(!first_gins){
        first_gins=true;
        ned_first=blh;
    }

    geometry_msgs::PoseStamped pose_stamped;
    pose_stamped.header = header;
    pose_stamped.header.frame_id = "world";
    
    Vector3d ned=Earth::global2local(ned_first, blh);
    pose_stamped.pose.position.x=ned(0);
    pose_stamped.pose.position.y=ned(1);
    pose_stamped.pose.position.z=ned(2);

    Quaterniond Q=gins_engine.pvacur.qnb;
    pose_stamped.pose.orientation.x=Q.x();
    pose_stamped.pose.orientation.y=Q.y();
    pose_stamped.pose.orientation.z=Q.z();
    pose_stamped.pose.orientation.w=Q.w();

    Vector3d vec=Q.toRotationMatrix().eulerAngles(2,1,0)*180/M_PI;   // [yaw, pitch, roll]
    Vector3d euler (vec[2], vec[1], vec[0]);

    //Vector3d vec=quaternion2euler(Q)*180/M_PI;

    printf("GINS  time: %f, t: %f %f %f q: %f %f %f  \n", header.stamp.toSec(), blh[0]*180/M_PI, blh[1]*180/M_PI, blh[2],  euler(0), euler(1), euler(2));

    gins_ned_path.header = header;
    gins_ned_path.header.frame_id = "world";
    gins_ned_path.poses.push_back(pose_stamped);
    pub_gins_ned.publish(gins_ned_path);    //发布更新后的ned坐标
}

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

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

相关文章

IT培训的背后,是“韭菜”的躺赚梦!

本文只谈"骗局"&#xff0c;不谈其他&#xff0c;绝不引战&#xff0c;如有错误&#xff0c;希望指出我会及时改正。 实际也有大量做得好的、值得肯定的培训机构&#xff0c;这里需要大家仔细鉴别。 导语 为什么要写这篇文章呢&#xff0c;近些年培训这个话题也比较…

Mhz_c1f靶场-简记

靶机 名称&#xff1a;mhz_cxf:clf download url&#xff1a;https://www.vulnhub.com/entry/mhz_cxf-c1f,471/ IP地址探测 sudo netdiscover -i eth1 -r 192.168.56.0/24 sudo netdiscover -i eth1 -r 192.168.56.0/24 Currently scanning: 192.168.56.0/24 | Screen …

ARM Trace32(劳特巴赫) 使用介绍 1 - Veloce 环境中使用 Trace32 介绍

文章目录 背景1.1 Trace 启动1.1.1 Trace32 系统相关设置 1.2 Trace 常用命令1.2.2 加在bin文件1.2.3 寄存器常用命令1.2.4 内存(寄存器)数据修改 1.3 变量相关命令 背景 Veloce是一款基于FPGA的高速仿真器&#xff0c;可用于验证芯片设计和软件开发, 它和 Trace32 都可以用于…

16.RocketMQ之消费重试以及原理

highlight: arduino-light 1.4 消费重试 对于顺序消息,当消费者消费消息失败后,消费者会在本地自动不断进行消息重试,每次间隔时间为 1 秒,重试最大值是 Integer.MAX_VALUE。 对于无序消息(普通、定时、延时、事务消息)当消费者消费消息失败时可以通过设置返回状态达到重试的目…

湿地环境监测物联网解决方案

湿地作为生态系统的关键组成部分&#xff0c;发挥着涵养水源、调节气候、改善环境、维护生物多样性等生态功能。湿地提供了独特的生境&#xff0c;为许多鸟类、鱼类和其他野生动物提供了栖息地和食物来源。此外&#xff0c;湿地还具有保持水量平衡和水质净化的重要功能&#xf…

最优控制:代数黎卡提方程ARE(Algebraic Riccati Equation)

本文介绍代数黎卡提方程的Matlab解法&#xff0c;包括直接求解和迭代求解 问题描述&#xff1a; 一、数值解法 可以看出&#xff0c;ARE方程是关于P的一个非线性方程&#xff0c;当系统矩阵维度较高时&#xff0c;难以求解&#xff0c;但是MATLAB给出了求解ARE的函数care % 系…

颜色聚合向量 Color Co-ccurrence Vector 介绍以及MATLAB代码实现

这件事情的起因是我想复习一下我在亚太杯数学建模当中使用过的CCV这种方法&#xff0c;但是CSDN平台上找了半天都没有&#xff0c;所以后来决定Google一下&#xff0c;终于找到了&#xff0c;甚至还有实现的代码&#xff0c;因此放上来。原文见Dr. Mahmoud Attia的博客。 一、…

JAVA中的伪共享与缓存行

一.伪共享与缓存行 1.CPU缓存架构 CPU 是计算机的心脏&#xff0c;所有运算和程序最终都要由它来执行。 主内存&#xff08;RAM&#xff09;是数据存放的地方&#xff0c;CPU 和主内存之间有好几级缓存&#xff0c;因为即使直接访问主内存也是非常慢的。 CPU的速度要远远大…

一图看懂CodeArts Board 5大特性,带你玩转看板服务

华为云看板服务CodeArts Board&#xff0c;通过构建研发效能度量体系&#xff0c;实现软件研发过程的可视化、软件交付的可管理可跟踪可量化&#xff0c;及时识别研发过程的堵塞点和改进点&#xff0c;通过数据驱动运营和治理&#xff0c;不断提升企业的软件能力和研发效能。

详解JAVA序列化

目录 1.什么是序列化 2.JAVA中的序列化 2.1.成员变量必须可序列化 2.2.transient关键字&#xff0c;可避免被序列化 2.3.无法更新状态 2.4.serialVersionUID 3.JDK序列化算法 4.序列化在实际中的一些应用 1.什么是序列化 序列化就是将对象转换为二进制格式的过程。对象…

Maven安装和配置详细教程

Maven安装和配置详细教程 1、Maven简介 Maven 是 Apache 软件基金会的一个开源项目,是一个优秀的项目构建工具,它用来帮助开发者管理项目中的 jar,以及 jar 之间的依赖关系、完成项目的编译、测试、打包和发布等工作。 2、Maven下载 点击Maven下载官方地址下载Maven。或者去…

postman持续集成-Jenkins自动构建

自动构建&#xff0c;就是设置一个定时器&#xff0c;定时时间到&#xff0c; Jenkins 自动执行测试用例 比如说,我设置下午五点,那么jenkins就是自动执行命令,自动生成报告,后续还可加上邮箱,会把报告发至邮箱 1. Jenkins 首页&#xff0c;点击任务名&#xff1a;如&#xff…

数据库—关系代数

传统的集合运算 在数据库中的关系代数运算有以下三种基本运算 并交差 必须满足两个表之间的属性个数必须一样。&#xff08;必须具有相容性&#xff09; 投影与选择运算 投影&#xff1a;π L _L L​( R ) 解释->π是投影符号&#xff0c;L是R表中的属性列&#xff0c;从…

临时文件中转服务的搭建-chfs软件的使用

因为经常用到远程桌面连接&#xff0c;所以本地pc和远程pc间的文件传输一直是个经常遇到的问题&#xff0c;尝试过用vftp搭建ftp服务&#xff0c;但是该服务在许多vps上被禁用&#xff0c;且windows上使用也要进行设置&#xff0c;比较麻烦。所幸发现了ods-im/CuteHttpFileServ…

接口测试如何做?你真的会做吗?全网超全整理实战案例...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 API测试的流程 准…

分布式事务Seate

一、Seata简介 1、Seata的核心组件 TC (Transaction Coordinator)事务协调者&#xff1a;维护全局和分支事务的状态&#xff0c;协调全局事务提交或回滚。TM (Transaction Manager)事务管理器&#xff1a;定义全局事务的范围、开始全局事务、提交或回滚全局事务。RM (Resourc…

2023下半年北京/上海/深圳软考(中/高级)认证招生

软考是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资格考试。 系统集成…

Docker安装与启动

Docker安装与启动 文章目录 Docker安装与启动前言容器与虚拟机比较 1、安装Docker2、设置ustc的镜像3、Docker的启动与停止总结 前言 容器与虚拟机比较 虚拟机&#xff08;VM&#xff09;是计算机系统的仿真。简而言之&#xff0c;它可以在实际上是一台计算机的硬件上运行看起…

Docker教程

Docker 能解决的问题 ⾸先&#xff0c;我们先来看⼏个问题&#xff1a; 1. 合作开发的时候&#xff0c;在本机可以运⾏&#xff0c;在别⼈的电脑上跑不起来。 这⾥我们以 Java Web 应⽤程序为例&#xff0c;⼀个 Java Web 应⽤程序涉及很多东⻄&#xff0c;⽐如 JDK 、 Tomc…

机器学习 day21(Tensorflow代码实现)

Tensorflow代码实现 在tensorflow中训练神经网络模型的步骤&#xff1a;第一步&#xff1a;指定模型&#xff0c;并告诉tensorflow按何种方式计算。第二步&#xff1a;使用特定的损失函数编译模型。第三步&#xff1a;训练模型