ROS学习之从yaml文件中读取多传感器坐标系之间的静态TF关系并发布Topic

news2024/11/25 7:49:55

文章目录

  • 0 引言
  • 1 工作空间创建并初始化
  • 2 创建ROS程序包
  • 3 创建yaml读取函数
    • 3.1 yaml文件
    • 3.2 读取函数
  • 4 创建静态TF关系的发布主函数
  • 5 创建launch文件
  • 6 编辑CMakeLists.txt
  • 7 编译运行
    • 7.1 编译
    • 7.2 运行

0 引言

机器人中经常使用多种传感器来做定位和建图,而多个传感器数据融合的话,就会进行多传感器标定,一般标定的外参结果都是存放在yaml文件中,了解到ROS中有TF关系树,那就创建一个完整的ROS工程(可参考以下链接),目的是从多传感器外参标定结果的yaml文件中读取旋转矩阵R和平移矩阵t,然后发布静态TF的Topic。

ROS学习之基础包创建的详细流程:包括rosnode, rostopic, rosrun,roslaunch等使用

默认已在Ubuntu系统中安装ROS机器人系统,比如Ubuntu18.04-melodic
并且安装了OpenCV,Eigen

工程目录:

# catkin_ws/src目录下
.
└── tfsensors
    ├── CMakeLists.txt
    ├── config
    │   └── config.yaml
    ├── include
    │   └── config.h
    ├── launch
    │   └── tfsensors.launch
    ├── main
    │   └── tf_broadcaster.cpp
    ├── package.xml
    └── src
        └── config.cpp

1 工作空间创建并初始化

# 新开一个终端
# 创建一个名为catkin_ws工作空间,及src子目录
mkdir -p ~/catkin_ws/src
# 进入到src文件夹
cd ~/catkin_ws/src
# 初始化工作空间
catkin_init_workspace
# 回到工作空间根目录 
cd .. 
# 编译工作空间 
catkin_make

2 创建ROS程序包

# 切换到src文件夹
cd ~/catkin_ws/src
# 创建名为tfsensors的ROS程序包,用到了tf geometry_msgs
catkin_create_pkg tfsensors roscpp tf geometry_msgs

执行后,src目录下新建了一个 tfsensors文件夹及子目录和子文件

.
└── tfsensors
    ├── CMakeLists.txt
    ├── include
    │   └── tfsensors
    ├── package.xml
    └── src

然后根据个人习惯修改成了如下的工作目录

# 切换到src文件夹
cd ~/catkin_ws/src/tfsensors
rm -rf include/tfsensors
mkdir main config

调整后,main文件夹放main函数的cpp文件,include文件夹放头文件,src文件夹放其他cpp文件,config文件夹放配置文件

.
├── CMakeLists.txt
├── config
├── include
├── main
├── package.xml
└── src

3 创建yaml读取函数

3.1 yaml文件

首先把多传感器外参的标定yaml文件放到config下,以下以lidar和imu外参的标定config.yaml文件为例

%YAML:1.0

lidar_to_imu_rotation: !!opencv-matrix
  rows: 3
  cols: 3
  dt: d
  data: [ 7.533745000000e-03, -9.999714000000e-01, -6.166020000000e-04,
          1.480249000000e-02, 7.280733000000e-04, -9.998902000000e-01,
          9.998621000000e-01, 7.523790000000e-03, 1.480755000000e-02]

lidar_to_imu_translation: !!opencv-matrix
  rows: 3
  cols: 1
  dt: d
  data: [ -4.069766000000e-03, -7.631618000000e-02, -2.717806000000e-01 ]

3.2 读取函数

首先在include文件夹中创建 config.h 文件,声明 readParameters 读取函数

#ifndef PROJECT_CONFIG_H
#define PROJECT_CONFIG_H

#include <iostream>

#include <eigen3/Eigen/Dense>  // 小tips:须在 #include <opencv2/core/eigen.hpp> 顺序之前

#include <opencv2/opencv.hpp>
#include <opencv2/core/eigen.hpp>

// #include <vector>

void readParameters(std::string config_file, cv::Mat& R, cv::Mat& t);
#endif

其次在src文件夹中创建对应的 config.cpp 文件,进而定义 readParameters 函数

#include "config.h"

// config_file是输入,R和t是返回值
void readParameters(std::string config_file, cv::Mat& R, cv::Mat& t)
{
    cv::FileStorage fsSettings(config_file, cv::FileStorage::READ);
    if(!fsSettings.isOpened())
    {
        std::cerr << "ERROR: Wrong path to settings" << std::endl;
    }

    cv::Mat cv_R, cv_t;
    fsSettings["lidar_to_imu_rotation"] >> cv_R;
    fsSettings["lidar_to_imu_translation"] >> cv_t;

    R = cv_R;
    t = cv_t;

    fsSettings.release();
}

4 创建静态TF关系的发布主函数

在main文件夹中创建 tf_broadcaster.cpp 文件,并写main函数,主要就是通过一个模板类获取config.yaml的绝对路径,然后调用config.cpp中的读取函数,返回lidar和imu之间的旋转矩阵和平移矩阵,最后借助TransformBroadcaster循环发布静态TF关系


#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include "math.h"
#include "config.h"

// #include <opencv2/opencv.hpp> //config.h已经包含了

template <typename T>
T readParam(ros::NodeHandle &n, std::string name)
{
    // std::cout << name <<std::endl;
    T ans;
    if (n.getParam(name, ans))
    {
    ROS_INFO_STREAM("Loaded " << name << ": " << ans);
    }
    else
    {
    ROS_ERROR_STREAM("Failed to load " << name);
    n.shutdown();
    }
    return ans;
}


int main(int argc, char** argv)
{

    ros::init(argc, argv, "tf_publisher");//初始化ROS节点与节点名称
    ros::NodeHandle n;                    //创建节点的句柄
    
    ros::NodeHandle pnh("~");

    std::string config_file;
    config_file = readParam<std::string>(pnh, "config_file");
    cv::Mat R, t;
    readParameters(config_file, R, t);

    Eigen::Matrix3d eigen_R;
    Eigen::Vector3d eigen_t;
    cv::cv2eigen(R, eigen_R);
    cv::cv2eigen(t, eigen_t);
    Eigen::Vector3d rpy = eigen_R.matrix().eulerAngles(2,1,0);

    ros::Rate loop_rate(100);             //设定节点运行的频率,与loop.sleep共同使用

    tf::TransformBroadcaster broadCaster; //创建TransformBroadcaster对象(tf广播器),是用来发布tf变换树;
    
    tf::Transform lidar2base;             //激光雷达坐标系lidar_link;创建一个Transform对象,用于描述两个坐标系之间的转化关系;
    tf::Quaternion q;
    // M_PI = π; M_PI/2 = π/2;
    q.setRPY(M_PI*rpy[0], M_PI*rpy[1], M_PI*rpy[2]);              //RPY欧拉角(zyx),外参的旋转坐标
                                       
    lidar2base.setRotation(q);
    lidar2base.setOrigin(tf::Vector3(eigen_t[0], eigen_t[1], eigen_t[2])); // 平移坐标, lidar在base的xyz位置

    while (n.ok())
    {
        // 循环发布坐标变换
        broadCaster.sendTransform(tf::StampedTransform(lidar2base,ros::Time::now(),"base_link","lidar_link"));

        loop_rate.sleep();
    }
    return 0;
}

5 创建launch文件

在launch文件夹下,创建tfsensors.launch文件,添加如下xml格式的代码,其中arg对应的是config.yaml路径,作为main函数的参数传入

<launch>
	<arg name="config_path" default = "$(find tfsensors)/config/config.yaml" />
	<node name="tf_publisher" pkg="tfsensors" type="tf_broadcaster" output="screen">

    	<param name="config_file" type="string" value="$(arg config_path)" />

	</node>
</launch>

6 编辑CMakeLists.txt

执行完第二步中后tfsensors文件目录下生成了CMakeLists.txt文件,由于该工程用到了OpenCV,Eigen,并且把主函数放到了新建的main文件夹中,也用到了头文件和对应的cpp文件,所以可按如下来修改CMakeLists.txt

cmake_minimum_required(VERSION 2.8.3)
project(tfsensors)

find_package(catkin REQUIRED COMPONENTS
  geometry_msgs
  roscpp
  tf
)

# find_package(Eigen REQUIRED)
find_package(OpenCV REQUIRED)

catkin_package(
  INCLUDE_DIRS include
#  LIBRARIES tfsensors
#  CATKIN_DEPENDS geometry_msgs roscpp tf
#  DEPENDS system_lib
)

include_directories(include

)

include_directories(
# include
  ${catkin_INCLUDE_DIRS}
  # ${Eigen_INCLUDE_DIRS}
  ${OpenCV_INCLUDE_DIRS}
)

include_directories("/usr/include/eigen3")

file(GLOB_RECURSE cpp_files
        ${PROJECT_SOURCE_DIR}/src/*.cpp
        )
add_library(tfsensorscpp ${cpp_files})


add_executable(tf_broadcaster main/tf_broadcaster.cpp)
target_link_libraries(tf_broadcaster
        ${catkin_LIBRARIES}
        ${OpenCV_LIBRARIES}
        tfsensorscpp
        )

7 编译运行

7.1 编译

# 回到catkin_ws工作空间
cd ~/catkin_ws/src
# 执行catkin_make编译
catkin_make

7.2 运行

# 编译后,重新source
source ~/catkin_ws/devel/setup.bash
# 运行talker_listener.launch
roslaunch tfsensors tfsensors.launch

运行后,新开终端运行rviz查看

# 运行rviz
rviz

运行rviz后,依次 Add --> By display type --> rviz --> TF,然后点击OK,添加TF到,并且下拉选择或直接修改 Fixed Frame 为lidar_link 或 base_link

rviz坐标系符合右手坐标系原则
X轴—红色
Y轴—绿色
Z轴—蓝色
Roll(滚转角)绕X轴旋转
Pitch(俯仰角)绕Y轴旋转
Yaw(偏航角)绕Z轴旋转

请添加图片描述
至此,成功创建一个发布静态TF关系的ROS工程,并包含从yaml中读取旋转矩阵和平移矩阵,可视化该TF关系等操作。




须知少时凌云志,曾许人间第一流。

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

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

相关文章

找出一个List中每个元素出现的次数

文章目录 一、需求&#xff1a;找出一个list中&#xff0c;每个元素出现的次数1. 普通实现&#xff08;hashmap&#xff09;&#xff1a;1.1 代码实现&#xff1a;1.2运行结果&#xff1a;1.3 案例分析&#xff1a; 2. 普通实现&#xff08;HashSet#Collections.frequency&…

Flask学习笔记(2)应用部署

本文将介绍如何部署Flask应用。   部署Flask应用&#xff0c;主要是要运用多线程与多进程&#xff0c;提高接口的并发能力。我们以下面的Python代码&#xff08;server.py&#xff09;为例进行演示&#xff1a; # -*- coding: utf-8 -*- import time import datetime from f…

04-HAL库UART配置及协议解析设计

本节内容介绍 1、HAL库UART 在cubemx中的配置及注意事项;2、HAL库UART详解与结构介绍;3、实现简单地UART数据收发&#xff1b; 源码地址&#xff1a; HAL库UART在cubemx中的配置 串口原理图 串口1咱们已经用作rtt的print使用了&#xff0c;所以使用另外一组串口来进行串口…

android studio 4.0以上隐藏调用方法参数名提示

引入&#xff1a; android studio在编辑代码的时候&#xff0c;调用函数时会接口处会自动提示参数名&#xff0c;方便代码书写时对传参命名的规范性。 可以如果代码是魂效过的&#xff0c;那会适得其反&#xff0c;l,l1,l2,i,i1,i2这样的参数名提醒反而会混淆视听。 这时候可…

图书馆机器人的应用,科技助力新趋势

随着科技的发展和智能化时代的到来&#xff0c;图书馆越来越多地引入了机器人作为服务和管理的工具。图书馆机器人可以轻松地完成多种任务&#xff0c;包括为用户提供导航服务、管理借还书、整理图书、清扫图书馆等。 首先&#xff0c;图书馆机器人可以为用户提供导航服务。在庞…

讲讲仿真软件的文件导入

仿真软件识别导入的设计文档是有区别的&#xff0c;实际的使用经历&#xff0c;ADS只是用于搭建Channel通道仿真&#xff0c;那本文以Cadence的Sigrity和Ansys的SIwave为例&#xff0c;讲讲仿真软件的文件导入。 先以Sigrity为例&#xff0c;打开软件&#xff0c;File 菜单Open…

记一次 JVM 参数调整导致 ShardingSphere-Proxy 性能下降的问题排查过程

问题现象 在性能测试中&#xff0c;分别对两个版本差异间隔一天的 ShardingSphere-Proxy 做性能测试&#xff0c;发现版本更新的 Proxy 比旧的 Proxy 在 TPC-C 场景下峰值 tpmC 下降了 7% 左右。 排查过程 在性能测试期间&#xff0c;使用 async-profiler 分别对两个进程进行…

根据aop实现自定义缓存注解

根据aop实现自定义缓存注解 自定义注解 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit;/*** author: yanche…

ESP32通过Arduino导出编译bin文件并进行量产烧录

ESP32通过Arduino导出编译bin文件并进行量产烧录 文章目录 ESP32通过Arduino导出编译bin文件并进行量产烧录Arduino导出编译的bin文件方法一&#xff1a;通过Arduino IDE提供的工具导出编译文件方法二&#xff1a;到编译生成的临时文件目录进行查找 弄清楚Arduino的编译烧录过程…

Unity3D:工具栏

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 3D工具集&#xff1a; NSDT简石数字孪生 工具栏 在 Unity Editor 顶部可以看到工具栏。 工具栏不是窗口&#xff0c;是 Unity 界面中唯一无法重新排列的部分。 有关场景视图中的其他工具&#xff0c;请参阅叠加。 工具栏…

记录一次使用thinkphp5分页器获取数据

// 输出当前页 $nowPage $data->currentPage(); // 输出总条数 $total $data->total(); // 输出当前页条数 $listRows $data->listRows();db(tablename)->where("id > 0")->paginate(10,true,[page>4]); //每页显示10条记录&#xff0c;且打…

C#(五十一)之特性

特性是用于为程序元素添加额外信息的一种机制。比如记录文件修改时间、提示某方法已经过期等。方法、变量、属性、类、接口、结构体以及程序及都是程序元素 Obsolete第二个参数设置为true,调用此方法会产生警告并引起编译器报错 百度了一下C#还有其他的特性以及自定义特性&am…

基于ssm实现图书商城(spring+springmvc+mybatis)

一、项目功能 前台 图书基本展示,包括推荐图书展示和类图书类型展示.推荐图书包括条幅推荐,热销推荐和新品推荐.按照图书类型展示商品.图书详细信息展示.图书加入购物车.修改购物车内图书信息,例如数量等.用户登录.用户注册.修改个人信息,包括密码和收获信息.购物车付款.用户…

前置声明、源文件include、编译链接顺序问题

TestB.h (前置声明&#xff0c;无需在源文件include)重点&#xff1a; 1.前置声明用在指针变量使用&#xff0c;无需在头文件或源文件include 2.继承或者普通变量在头文件使用的时候(除非所有的编译顺序都正确&#xff0c;才能在源文件include)&#xff0c;最好不要在源文件i…

汇编语言基础--内中断

在8086CPU实模式下有如下内存布局&#xff1a; 我们看到在000-3FF的位置是放着中断向量表。 里面放的其实是4个字节的地址&#xff08;地址处放着对应的中断处理函数&#xff09;。我们知道在8086实模式下&#xff0c;是通过cs:ip来找到要执行的指令。cs是2个字节&#xff0c;i…

chat2DB使用教程

1. chat2DB简介 1-1. 简介 ​ chat2DB是一款有开源免费的多数据库客户端工具&#xff0c;支持windows、mac本地安装&#xff0c;也支持服务器端部署&#xff0c;web网页访问。和传统的数据库客户端软件Navicat、DBeaver 相比Chat2DB集成了AIGC的能力&#xff0c;能够将自然语…

Squid 缓存代理--反向代理

Squid 缓存代理–反向代理 反向代理&#xff1a;如果Squid反向代理服务器中缓存了该请求的资源&#xff0c;则将该请求的资源直接返回给客户端&#xff1a;否则反向代理服务器将向后台的WEB服务器请求资源&#xff0c;然后将请求的应答返回给客户端&#xff0c;同时也将应答缓…

VMware 虚拟磁盘格式

1.如果必须用FT &#xff0c; 只能选eagerzeroedthick 2.如果追求最佳应用性能不考虑空间占用 &#xff0c; 选eagerzeroedthick 3.如果希望最大成都的利用空间&#xff0c;并且对磁盘的增长是可控的&#xff0c;可以选thin格式 4.如果不希望空间的过量分配(oversubsribe)造…

OpenStack组件的基本使用

OpenStack组件的基本使用 Keystone命令行的方式项目用户角色 图形化界面方式项目用户角色 Glance命令行操作镜像上传下载管理镜像开放镜像权限转换镜像格式 图形化界面 Nova和Neutron命令行的方式实例类型密钥对安全组创建网络创建云主机启动关闭云主机 图形化界面创建实例类型…

【数据结构与算法】二叉树中从每个叶子结点到根结点的路径

题目 Qestion: 输出二叉树中从每个叶子结点到根结点的路径 数据结构与定义 #include <stdio.h> #include <stdlib.h>typedef struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right; } TreeNode;二叉树形状 核心代码 void LeafToRoot(TreeNod…