ROS TF坐标变换 - 静态坐标变换

目录

  • 一、静态坐标变换(C++实现)
  • 二、静态坐标变换(Python实现)

如前文所属,ROS通过广播的形式告知各模块的位姿关系,接下来详述这一机制的代码实现。

模块间的位置关系有两种类型,一种是相对固定的,称为静态坐标变换,一种是相对不固定,变化的,称为动态坐标变换。

一、静态坐标变换(C++实现)

所谓静态坐标变换,是指两个坐标系之间的相对位置是固定的。比如机器人底盘上安装了一个激光雷达,他和底盘组成一个刚体,它们的相对位姿不会随机器人的运动而变化,他们之间的坐标变换即属于静态坐标变换。

假设激光雷达相对与底盘的欧拉位姿为(0.5, 0.0, 0.3; 0.0, 0.0, 0.0)

雷达检测到的障碍物位置为(2.0, 2.5, 0.3)

若要计算障碍物和底盘的相对位置,就可以通过雷达到底盘的坐标变换来计算,步骤如下:

  1. 雷达(laser)发布自己和底盘(base_link)的相对静态坐标
  2. 避障模块监听雷达(laser)和底盘(base_link)的相对坐标关系,并通过tf 计算障碍物位置。

首先创建 tf2_learning 包,命令如下:(这一步不是必须,这里只是为了方便清晰的说明,也可以使用已有的包,在包里新增节点等方法)

catkin_creat_pkg tf2_learning roscpp rospy geometry_msgs std_msgs tf2 tf2_geometry_msgs tf2_ros

创建后,文件结构如下:

在这里插入图片描述

在创建的 tf2_learning 包路径下有一个 src 目录,在这里存储C++源码,我们创建 static_frame_broadcast.cppstatic_frame_listen.cpp ,修改 CMakeLists.txt ,添加如下内容:

add_executable(${PROJECT_NAME}_broadcast src/static_frame_broadcast.cpp)
add_executable(${PROJECT_NAME}_listen src/static_frame_listen.cpp)target_link_libraries(${PROJECT_NAME}_broadcast${catkin_LIBRARIES}
)target_link_libraries(${PROJECT_NAME}_listen${catkin_LIBRARIES}
)

static_frame_broadcast.cpp 实现广播子坐标系相对于父坐标系的静态坐标,内容如下:

#include "ros/ros.h"
#include "tf2_ros/static_transform_broadcaster.h"
#include "geometry_msgs/TransformStamped.h"
#include "tf2/LinearMath/Quaternion.h"int main(int argc, char **argv)
{// 初始化 ROS 节点ros::init(argc, argv, "static_frame_broadcast");// 创建静态坐标转换广播器tf2_ros::StaticTransformBroadcaster broadcaster;// 创建坐标系信息geometry_msgs::TransformStamped ts;// --设置头信息ts.header.seq = 100;ts.header.stamp = ros::Time::now();ts.header.frame_id = "base_link";// --设置子级坐标系ts.child_frame_id = "laser";// --设置子坐标系相对于父坐标系的平移偏移量ts.transform.translation.x = 0.5;ts.transform.translation.y = 0.0;ts.transform.translation.z = 0.3;// --设置子坐标系相对于父坐标系的旋转偏移量// --将欧拉角转换成四元数tf2::Quaternion qtn; // tf2的四元数类qtn.setRPY(0, 0, 0); // 设置欧拉角// 获取旋转的四元数值ts.transform.rotation.x = qtn.getX();ts.transform.rotation.y = qtn.getY();ts.transform.rotation.z = qtn.getZ();ts.transform.rotation.w = qtn.getW();// 广播器发布坐标系信息broadcaster.sendTransform(ts);ros::spin();return 0;
}

static_frame_listen.cpp 实现订阅静态坐标转换关系,并利用该关系将雷达坐标系的点转换到 base_link 坐标系,内容如下:

#include "ros/ros.h"
#include "tf2_ros/transform_listener.h"
#include "tf2_ros/buffer.h"
#include "geometry_msgs/PointStamped.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"int main(int argc, char *argv[])
{// 初始化 ROS 节点ros::init(argc, argv, "static_frame_listen");ros::NodeHandle nh;// 创建 TF 订阅节点tf2_ros::Buffer buffer;tf2_ros::TransformListener listener(buffer);ros::Rate rate(1);while (ros::ok()){// 生成一个坐标点, 模拟雷达检测到的障碍物坐标点(雷达坐标系下的坐标)geometry_msgs::PointStamped point_laser;point_laser.header.frame_id = "laser";point_laser.header.stamp = ros::Time::now();point_laser.point.x = 2.0;point_laser.point.y = 2.5;point_laser.point.z = 0.3;// 转换坐标点, 计算障碍物坐标点在 base_link 下的坐标try{geometry_msgs::PointStamped point_base;point_base = buffer.transform(point_laser, "base_link");ROS_INFO("point_base: (%.2f, %.2f, %.2f), frame: %s",point_base.point.x, point_base.point.y, point_base.point.z,point_base.header.frame_id.c_str());}catch (const std::exception &e){ROS_ERROR("%s", e.what());}rate.sleep();ros::spinOnce();}return 0;
}

编译后,执行 rosrun tf2_learning tf2_learning_broadcast 开始广播坐标,此时打开rviz订阅TF看到TF树模型,操作与结果如下:

  • 输入命令:rviz
  • 在启动的 rviz 中设置 Fixed Framebase_link
  • 点击左下的 Add 按钮,在弹出的窗口中选择 TF 组件,即可显示坐标关系。

在这里插入图片描述

继续执行命令rosrun tf2_learning tf2_learning_listen可以看到转换后的坐标,以及所属父坐标系,如下:

在这里插入图片描述

其中,ERROR是由于节点刚起来时,TF数据还未来得及写入缓存,导致base_link不存在,可以发现第二次调用就没有报错了,实际使用中,可以等待要操作的frame存在再做转换,如下:

tf2_ros::Buffer buffer;
tf2_ros::TransformListener listener(buffer);
// _frameExists()返回指定frame是否存在于tf树中
if (!buffer._frameExists("base_link"))
{ROS_WARN("base_link frame does not exist.");
}

二、静态坐标变换(Python实现)

在创建的 tf2_learning 包路径下 src 目录的同级,创建一个 scripts 目录,在这里存储脚本(如python脚本),我们创建 tf2_learning_broadcast.py 以实现坐标广播,编辑内容如下:

#! /usr/bin/env pythonimport rospy
import tf
import tf2_ros
from geometry_msgs.msg import TransformStampedif __name__ == "__main__":# 初始化 ROS 节点rospy.init_node("static_frame_broadcast_py")# 创建静态坐标广播器broadcaster = tf2_ros.StaticTransformBroadcaster()# 创建并组织被广播的消息tfs = TransformStamped()# -- 头信息tfs.header.frame_id = "base_link" # 父坐标系tfs.header.stamp = rospy.Time.now()tfs.header.seq = 101# -- 子坐标系tfs.child_frame_id = "laser"# -- 坐标系相对信息# ---- 相对于父坐标系的平移偏移量tfs.transform.translation.x = 0.5tfs.transform.translation.y = 0.0tfs.transform.translation.z = 0.3# ---- 相对于父坐标系的旋转偏移量# ---- 设置欧拉角,并将欧拉角转换成四元数qtn = tf.transformations.quaternion_from_euler(0, 0, 0)tfs.transform.rotation.x = qtn[0]tfs.transform.rotation.y = qtn[1]tfs.transform.rotation.z = qtn[2]tfs.transform.rotation.w = qtn[3]# 广播器发送消息broadcaster.sendTransform(tfs)# spinrospy.spin()

创建 tf2_learning_listen.py 以订阅静态坐标转换关系,并利用该关系将雷达坐标系的点转换到 base_link 坐标系,编辑内容如下:

#! /usr/bin/env pythonimport rospy
import tf2_ros
# 不要使用 geometry_msgs,需要使用 tf2 内置的消息类型
from tf2_geometry_msgs import PointStamped
# from geometry_msgs.msg import PointStampedif __name__ == "__main__":# 初始化 ROS 节点rospy.init_node("static_frame_listen")# 创建 TF 订阅对象buffer = tf2_ros.Buffer()listener = tf2_ros.TransformListener(buffer)rate = rospy.Rate(1)while not rospy.is_shutdown():# 生成一个坐标点, 模拟雷达检测到的障碍物坐标点(雷达坐标系下的坐标)point_laser = PointStamped()point_laser.header.frame_id = "laser"point_laser.header.stamp = rospy.Time.now()point_laser.point.x = 2.0point_laser.point.y = 2.5point_laser.point.z = 0.3try:# 转换坐标点, 计算障碍物坐标点在 base_link 下的坐标point_base = buffer.transform(point_laser, "base_link")rospy.loginfo("point_base: (%.2f, %.2f, %.2f), frame: %s",point_base.point.x,point_base.point.y,point_base.point.z,point_base.header.frame_id)except Exception as e:rospy.logerr("%s", e)# spinrate.sleep()

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Prototype原型模式(创建对象)

原型模式:Prototype 链接:原型模式实例代码 注解 模式定义 使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。 ——《设计模式》GoF 目的 在软件系统中,经常面临这“某些结构复杂的对象”的创建工作&am…

nginx源码分析-4

这一章内容讲述nginx的模块化。 ngx_module_t:一个结构体,用于描述nginx中的各个模块,其中包括核心模块、HTTP模块、事件模块等。这个结构体包含了一些模块的关键信息和回调函数,以便nginx在运行时能够正确地加载和管理这些模块。…

每日一题(LeetCode)----二叉树--二叉树的层平均值

每日一题(LeetCode)----二叉树–二叉树的层平均值 1.题目(637. 二叉树的层平均值) 给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。 示例 1: 输入:root […

TDD-LTE TAU流程

目录 1. TAU成功流程 1.1 空闲态TAU 1.2 连接态TAU 2. TAU失败流程 当UE进入一个小区,该小区所属TAI不在UE保存的TAI list内时,UE发起正常TAU流程,分为IDLE和CONNECTED(即切换时)下。如果TAU accept分配了一个新的…

计算机组成原理 数据的表示与运算

文章目录 数据的表示与运算数据表示定点数的表示与运算定点数的表示无符号数有符号数定点整数定点小数四码反码补码移码 总结 定点数的运算算术移位原码反码补码总结 逻辑位移循环位移总结 加减运算原码加减法补码加减法 溢出判断采用一位符号位 浮点数的表示与运算表示IEEE 75…

vue3使用vuex

vuex: 状态管理工具 使用场景:用户登录状态 购物车 地理位置 等 数据位置:内存 安装 项目根目录 yarn add vuex 在src目录下新建store文件夹 下面新建index.js src/store/index.js 在main.js中引入并使用 // 导入状态管理工具vuex import store…

PHP特性知识点扫盲 - 下篇

概述 在实际的生产环境中遇到了实际需要解决的问题,需要把服务部署的方式梳理出来,在同一个服务器中部署多个PHP环境,架构图如下: 架构方案 在工作实践中遇到的很多问题的普遍性都是相通的,公司运行的可新项目都是版…

Codeium在IDEA里的3个坑

转载自Codeium在IDEA里的3个坑:无法log in,downloading language server和中文乱码_downloading codeium language server...-CSDN博客文章浏览阅读1.7w次,点赞26次,收藏47次。Codeium安装IDEA插件的3个常见坑_downloading codeiu…

边界判断缺失

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖&…

C#编程-使用循环构造

使用循环构造 循环构造用于反复执行一行或多行代码。在C#中,可以使用以下循环构造: while 循环do…while循环for 循环while循环 while循环构造执行语句块,直至while循环中给出的条件为true。while语句在执行循环中的语句之前总是要检查条件。当执行到while循环中最后一条语…

「微服务」Saga 模式 如何使用微服务实现业务事务-第二部分

在上一篇文章中,我们看到了实现分布式事务的一些挑战,以及如何使用Event / Choreography方法实现Saga的模式。在本文中,我们将讨论如何通过使用另一种类型的Saga实现(称为Command或Orchestration)来解决一些问题&#…

上海亚商投顾:三大指数红盘收官!沪指今年累计跌3.7%

上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。 一.市场情绪 A股12月29日迎来2023年收官之战,三大股指延续反弹走势,最终集体红盘报收。纵观全年&am…