ROS 2边学边练(33)-- 写一个静态广播(C++)

前言

        通过这一篇我们将了解并学习到如何广播静态坐标变换到tf2(由tf2来转换这些坐标系)。

        发布静态变换对于定义机器人底座与其传感器或非移动部件之间的关系非常有用。例如,在以激光扫描仪中心的坐标系中推理激光扫描测量数据是最简单的。

        这是一个独立的教程,涵盖了静态变换的基础知识,包含两个部分。在第一部分,我们将编写代码以将静态变换发布到tf2。在第二部分,我们将说明如何在tf2_ros中使用命令行工具static_transform_publisher

        后面的接着两篇博文中(下一篇可能会穿插一篇有关ROS世界坐标系的博文),我们将重新学习下上一篇博文(《初识tf2》)的一些知识,在后面的其他博文将会涉及到tf2的更多高级功能。

动动手

创建功能包

        我们在之前创建的某个工作空间(比如ros2_ws)或新建一个工作空间内创建一个learning_tf2_cpp包(工作空间的src路径下):

$ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies geometry_msgs rclcpp tf2 tf2_ros turtlesim -- learning_tf2_cpp

写个静态广播节点

        在learning_tf2_cpp/src下,执行如下命令下载官方为我们准备好的源文件:

$wget https://raw.githubusercontent.com/ros/geometry_tutorials/ros2/turtle_tf2_cpp/src/static_turtle_tf2_broadcaster.cpp

        下载下来的static_turtle_tf2_broadcaster.cpp内容如下:

#include <memory>#include "geometry_msgs/msg/transform_stamped.hpp"
#include "rclcpp/rclcpp.hpp"
#include "tf2/LinearMath/Quaternion.h"
#include "tf2_ros/static_transform_broadcaster.h"class StaticFramePublisher : public rclcpp::Node
{
public:explicit StaticFramePublisher(char * transformation[]): Node("static_turtle_tf2_broadcaster"){tf_static_broadcaster_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);// Publish static transforms once at startupthis->make_transforms(transformation);}private:void make_transforms(char * transformation[]){geometry_msgs::msg::TransformStamped t;t.header.stamp = this->get_clock()->now();t.header.frame_id = "world";t.child_frame_id = transformation[1];//命令行传入的第二个参数,第一个参数为可执行文件的名称t.transform.translation.x = atof(transformation[2]);t.transform.translation.y = atof(transformation[3]);t.transform.translation.z = atof(transformation[4]);tf2::Quaternion q;q.setRPY(atof(transformation[5]),atof(transformation[6]),atof(transformation[7]));t.transform.rotation.x = q.x();t.transform.rotation.y = q.y();t.transform.rotation.z = q.z();t.transform.rotation.w = q.w();tf_static_broadcaster_->sendTransform(t);}std::shared_ptr<tf2_ros::StaticTransformBroadcaster> tf_static_broadcaster_;
};int main(int argc, char * argv[])
{auto logger = rclcpp::get_logger("logger");// Obtain parameters from command line argumentsif (argc != 8) {RCLCPP_INFO(logger, "Invalid number of parameters\nusage: ""$ ros2 run learning_tf2_cpp static_turtle_tf2_broadcaster ""child_frame_name x y z roll pitch yaw");return 1;}// As the parent frame of the transform is `world`, it is// necessary to check that the frame name passed is differentif (strcmp(argv[1], "world") == 0) {RCLCPP_INFO(logger, "Your static turtle name cannot be 'world'");return 1;}// Pass parameters and initialize noderclcpp::init(argc, argv);rclcpp::spin(std::make_shared<StaticFramePublisher>(argv));rclcpp::shutdown();return 0;
}
代码分析
#include "geometry_msgs/msg/transform_stamped.hpp"

        包含transform_stamped.hpp头文件是为了能访问TransformStamped消息类型,并将其发布到转换树当中去。

#include "rclcpp/rclcpp.hpp"

        包含rclcpp.hpp头文件,是为了使用rcl::Node类。

#include "tf2/LinearMath/Quaternion.h"
#include "tf2_ros/static_transform_broadcaster.h"

        包含Quaternion.h是为了利用四元数类中提供的方便转换欧拉角为四元数(或四元数转换为欧拉角)的功能,包含static_transform_broadcaster.h是为了使用TransformBroadcaster从而使得发布静态转换变得简单。

tf_static_broadcaster_ = std::make_shared<tf2_ros::StaticTransformBroadcaster>(this);this->make_transforms(transformation);

        StaticFramePublisher类的构造函数中初始化名称为static_turtle_tf2_broadcaster的节点。然后,创建StaticTransformBroadcaster,它将在启动时发送一个静态转换。 

geometry_msgs::msg::TransformStamped t;t.header.stamp = this->get_clock()->now();
t.header.frame_id = "world";
t.child_frame_id = transformation[1];

        在这里,我们创建一个TransformStamped对象,它将是我们在填充后发送的消息载体。在传递实际的转换值之前,我们需要为其提供适当的元数据初始化一下。 

  1.         t.header.stamp,这个好理解,将当前时间作为时间戳填充上;
  2.         t.hear.frame_id,我们创建连接(link)的父坐标系名称,我们取名“world”;
  3.         t.child_frame_id, 我们创建连接(link)的子坐标系名称;
t.transform.translation.x = atof(transformation[2]);
t.transform.translation.y = atof(transformation[3]);
t.transform.translation.z = atof(transformation[4]);
tf2::Quaternion q;
q.setRPY(atof(transformation[5]),atof(transformation[6]),atof(transformation[7]));
t.transform.rotation.x = q.x();
t.transform.rotation.y = q.y();
t.transform.rotation.z = q.z();
t.transform.rotation.w = q.w();

        这里是填充关于小海龟的6D(六个自由度)位姿数据(偏移及旋转)。

tf_static_broadcaster_->sendTransform(t);

         数据都准备好了之后,我们调用sendTransform函数将消息t(静态转换)发布广播出去。

更新package.xml和CMakeLists.txt

package.xml

<description>Learning tf2 with rclcpp</description>
<maintainer email="mike@qq.com">mike</maintainer>
<license>Apache License 2.0</license>

        根据实际情况手动修改。

CMakeLists.txt

add_executable(static_turtle_tf2_broadcaster src/static_turtle_tf2_broadcaster.cpp)
ament_target_dependencies(static_turtle_tf2_broadcastergeometry_msgsrclcpptf2tf2_ros
)

        添加可执行程序,名字为static_turtle_tf2_broadcaster,我们在使用ros2 run命令时会使用到此生成的可执行程序。

install(TARGETSstatic_turtle_tf2_broadcasterDESTINATION lib/${PROJECT_NAME})

        指定构建安装的路径(可执行程序生成的路径),方便调用程序按图索骥。

构建

        都搞定之后,我们就可以构建这个包了,但,养成好习惯,先检查依赖再构建。

$rosdep install -i --from-path src --rosdistro iron -y
$colcon build --packages-select learning_tf2_cpp

运行

        我们新开一个终端,先进入工作空间根路径然后source下环境:

$source install/setup.bash

        现在我们终于可以运行了,但我们需要在调用可执行文件时还需将位姿数据传进去,方便该节点填充消息(x,y,z,r,p,y->0,0,1,0,0,0):

$ros2 run learning_tf2_cpp static_turtle_tf2_broadcaster mystaticturtle 0 0 1 0 0 0

上面的参数z为1,代表给mystaticturtle坐标系广播了一个离地1米高度的海龟位姿信息(让mystaticturtle坐标系朝着这个小目标前进)。

补充一点机器人学背景专业知识。

在ROS(Robot Operating System)中,6自由度(6DOF,即6 Degrees of Freedom)通常指的是一个刚体在空间中的完全定位所需的参数。这些参数描述了刚体在三维空间中的位置和姿态。具体来说,6自由度包括:三个位置参数(Position):X轴位移(X-axis translation)Y轴位移(Y-axis translation)Z轴位移(Z-axis translation)这三个参数描述了刚体在三维空间中的位置。三个姿态参数(Attitude)或方向参数(Orientation):横滚角(Roll)俯仰角(Pitch)偏航角(Yaw)这三个参数描述了刚体在三维空间中的方向或姿态。这些角度通常使用欧拉角、四元数或其他方式来表示。在ROS中,这些参数经常用于描述传感器数据(如IMU、激光雷达等)、机器人末端执行器的位置、机器人的自身姿态等。例如,tf2库就用于处理这些变换,允许用户在不同坐标系之间转换点、向量和姿态。当处理机器人学中的运动时,了解这些参数和它们如何影响机器人在空间中的位置和姿态是非常重要的。

位置参数(Position)X轴位移(X-axis translation):这个参数描述了刚体在X轴方向上的位置。正值通常表示向右移动,负值表示向左移动。它决定了刚体在三维空间中的水平位置。Y轴位移(Y-axis translation):这个参数描述了刚体在Y轴方向上的位置。正值通常表示向前移动,负值表示向后移动。它决定了刚体在三维空间中的前后位置。Z轴位移(Z-axis translation):这个参数描述了刚体在Z轴方向上的位置。正值通常表示向上移动,负值表示向下移动。它决定了刚体在三维空间中的垂直位置。姿态参数(Attitude)或方向参数(Orientation)姿态参数通常用来描述刚体在三维空间中的方向或朝向。有多种方式可以表示姿态,但最常见的包括欧拉角和四元数。这里,我们简单解释欧拉角:横滚角(Roll):这个参数描述了刚体绕其X轴的旋转。正值通常表示顺时针旋转,负值表示逆时针旋转。它描述了刚体在水平面上的旋转。俯仰角(Pitch):这个参数描述了刚体绕其Y轴的旋转。正值通常表示向上抬头,负值表示向下低头。它描述了刚体在垂直平面内的旋转。偏航角(Yaw):这个参数描述了刚体绕其Z轴的旋转。正值通常表示向右转动,负值表示向左转动。它描述了刚体在水平面上的方向改变。需要注意的是,欧拉角存在万向锁问题,即在某些特定姿态下,横滚角和俯仰角会变得不确定,影响姿态描述的准确性。因此,在实际应用中,有时会更倾向于使用四元数或其他方法来表示姿态。

         我们再来检查一下主题/tf_static发布的静态转换消息(不知道是哪个主题可以ros2 topic list确认一下)。

$ros2 topic echo /tf_static

 如果一切顺利,我们将会看到下面的内容。

发布静态转换的正确方式

        除了我们上面编写构建的StaticTransformBroadcaster可以发布静态转换外,我们还可以利用tf2_ros工具(名字为static_transform_publisher)来实现同样的目的(实际情况就是直接使用现成的工具而不是自己去实现,上面的代码实现是为了让我们了解到其内部的实现,知己知彼嘛),我们可以直接在命令行调用此工具或者在launch文件里添加使用。

        使用以米为单位的x/y/z偏移和以弧度为单位的滚转/俯仰/偏航,将静态坐标变换发布到tf2。我们来看看怎么调用:

$ros2 run tf2_ros static_transform_publisher --x x --y y --z z --yaw yaw --pitch pitch --roll roll --frame-id frame_id --child-frame-id child_frame_id

        可以看出,除了6D参数外还有我们在自己编写的代码里面的父子坐标系名称。我们执行一下,效果与上面的是一样的。

 

 

        使用以米和四元数为单位的x/y/z偏移将静态坐标变换发布到tf2。

$ros2 run tf2_ros static_transform_publisher --x x --y y --z z --qx qx --qy qy --qz qz --qw qw --frame-id frame_id --child-frame-id child_frame_id

补充四元数知识点:

四元素(Quaternion)是一种用于表示旋转的数学工具,在机器人学和计算机图形学等领域有广泛应用。它由一个实部和三个虚部组成,可以表示为(w, x, y, z)或(w, V),其中V是虚部,表示为向量(x, y, z)。四元素满足特定的数学规则,包括虚部的乘法规则,如i^2 = j^2 = k^2 = ijk = −1等。在ROS(Robot Operating System)中,四元素常用于表示机器人的姿态。姿态描述了机器人在三维空间中的位置和朝向,而四元素是描述这种姿态的一种常用方式。ROS提供了处理四元素的库和工具,使得机器人软件能够方便地处理与姿态相关的任务。在使用四元素时,需要注意其标准化问题。标准化四元素意味着将其长度(模长)调整为1,以确保其正确表示旋转。未标准化的四元素可能会导致错误的结果。此外,ROS中的tf2系统也涉及到四元素的使用。tf2用于进行坐标旋转以及tf、msg两种四元素数据结构的变换,它使得在不同坐标系之间转换姿态变得简单高效。综上所述,四元素是机器人学和计算机图形学中的重要工具,尤其在ROS中,它扮演着描述机器人姿态的关键角色。

  static_transform_publisher既可以直接在命令行使用又可以在launch文件里面使用,我们来看看它在launch文件里面是怎么用的。

from launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([Node(package='tf2_ros',executable='static_transform_publisher',arguments = ['--x', '0', '--y', '0', '--z', '1', '--yaw', '0', '--pitch', '0', '--roll', '0', '--frame-id', 'world', '--child-frame-id', 'mystaticturtle']),])

        看起来是不是比较容易接受,但需要注意的是,除了参数--frame-id 和参数 --child-frame-id外的其他参数都是可选的,如果未指定某个特定选项,则会假定使用恒等变换(默认值)。

本篇完。

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

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

相关文章

C++学习进阶版(一):用C++写简单的状态机实现

目录 一、基础知识 1、状态机 2、四大要素 3、描述方式 4、设计步骤 5、实现过程中需注意 &#xff08;1&#xff09; 状态定义 &#xff08;2&#xff09; 状态转换规则 &#xff08;3&#xff09; 输入处理 &#xff08;4&#xff09; 状态机的封装 &#xff08;5…

本地部署Docker容器可视化图形管理工具DockerUI并实现无公网IP远程访问——“cpolar内网穿透”

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

路由过滤与引入

1、实验拓扑 2、实验要求 1、按照图示配置 IP 地址&#xff0c;R1&#xff0c;R3&#xff0c;R4 上使用 1oopback口模拟业务网段 2、运行 oSPF&#xff0c;各自协议内部互通 3、R1 和 R2 运行 RIPv2,R2&#xff0c;R3和R4在 RIP 和 oSPF 间配置双向路由引入,要求除 R4 上的业务…

基于51单片机的温度、烟雾、防盗、GSM上报智能家居系统

基于51单片机的智能家居系统 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.DS18B20检测温度&#xff0c;MQ-2检测烟雾、ADC0832实现模数转换&#xff1b; 2.按键可以设置温度、烟雾浓度阈值&#x…

【Java--数据结构】提升你的编程段位:泛型入门指南,一看就会!

前言 泛型是一种编程概念&#xff0c;它允许我们编写可以适用于多种数据类型的代码。通过使用泛型&#xff0c;我们可以在编译时期将具体的数据类型作为参数传递给代码&#xff0c;从而实现代码的复用和灵活性。 在传统的编程中&#xff0c;我们通常需要为不同的数据类型编写不…

10 JavaScript学习:函数

函数的概念 JavaScript中的函数是一段可重复使用的代码块&#xff0c;它接受输入&#xff08;称为参数&#xff09;&#xff0c;执行特定的任务&#xff0c;并返回一个值。函数可以被调用&#xff08;或者说被执行&#xff09;&#xff0c;并且可以接受不同的输入来产生不同的…

提升效率!微信自动统计数据报表,轻松实现!

在数字化时代&#xff0c;提高工作效率是每个人的追求。下面就给大家分享一个能够自动统计微信号运营数据的神器——个微管理系统&#xff0c;让大家无需手动整理和计算&#xff0c;提高工作效率&#xff01; 1、好友统计报表 它分为通讯录好友统计、新增好友统计和删除好友统…

python创建线程和结束线程

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 python创建线程和结束线程 在 Python 中&#xff0c;线程是一种轻量级的执行单元&#xff…

Mysql 存在多条数据,按时间取最新的那一组数据

1、数据如下&#xff0c;获取每个用户最近的一次登录数据 思路1&#xff1a;order by group by 先根据UserIdLogInTime排序&#xff0c;再利用Group分组&#xff0c;即可得到每个User_Id的最新数据。 1 SELECT * FROM login_db l ORDER BY l.user_id, l.login_time DESC; 排…

【Linux】实现一个进度条

我们之前也学了gcc/vim/make和makefile&#xff0c;那么我们就用它们实现一个进度条。 在实现这个进度条之前&#xff0c;我们要先简单了解一下缓冲区和回车和换行的区别 缓冲区其实就是一块内存空间&#xff0c;我们先看这样一段代码 它的现象是先立马打印&#xff0c;三秒后程…

使用表格法插入公式和编号

如何将公式和编号优雅地插入到论文当中呢&#xff1f; 首先插入一个1行2列的表格 调整一下 输入公式方法一&#xff1a;感觉墨迹公式挺好用的&#xff0c;word自带的 输入公式方法二&#xff1a;图片转LATEX代码 这个方法更快 分享一个公式识别网站 图片识别得到LATEX代码&…

惠海H6212L DCDC同步降压芯片IC 24V30V36V48V转3.3V5V12V3A大电流方案 带线损

同步降压芯片IC 24V30V36V48V转3.3V5V12V3A大电流方案是一种电源管理方案&#xff0c;它采用同步整流技术&#xff0c;将较高的输入电压&#xff08;如24V、30V、36V、48V&#xff09;转换为较低的输出电压&#xff08;如3.3V、5V、12V&#xff09;&#xff0c;并提供高达3A的大…

代码随想录训练营Day 29|Python|Leetcode|● 860.柠檬水找零 ● 406.根据身高重建队列 ● 452. 用最少数量的箭引爆气球

860.柠檬水找零 在柠檬水摊上&#xff0c;每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品&#xff0c;&#xff08;按账单 bills 支付的顺序&#xff09;一次购买一杯。 每位顾客只买一杯柠檬水&#xff0c;然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确…

openGauss学习笔记-269 openGauss性能调优-TPCC性能调优测试指导-数据库服务端及客户端绑核

文章目录 openGauss学习笔记-269 openGauss性能调优-TPCC性能调优测试指导-数据库服务端及客户端绑核269.1 安装openGauss数据库269.2 停止数据库269.3 使用gs_guc工具修改数据库端口、IP等269.4 使用gs_guc工具设置如下参数269.5 执行如下命令以绑核方式启动服务端数据库269.6…

【计算机毕业设计】药品销售系统产品功能介绍——后附源码

&#x1f389;**欢迎来到我的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 一名来自世界500强的资深程序媛&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 在深度学习任务中展现出卓越的能力&#xff0c;包括但不限于…

手把手教数据结构与算法:有序线性表设计

问题描述 设计一个有序线性表类&#xff0c;要求完成初始化&#xff0c;插入和遍历功能&#xff0c;使得表内元素实现有序排列&#xff08;从小到大&#xff09;。同时实现合并功能&#xff0c;使得两个线性表能够合并为一个线性表&#xff08;可能存在重复元素&#xff09;。…

半导体存储器整理

半导体存储器用来存储大量的二值数据&#xff0c;它是计算机等大型数字系统中不可缺少的组成部分。按照集成度划分&#xff0c;半导体存储器属于大规模集成电路。 目前半导体存储器可以分为两大类&#xff1a; 只读存储器&#xff08;ROM&#xff0c;Read Only Memory&#xff…

ThingsBoard服务端使用RPC通过网关给设备发送消息

一、概述 1、发送服务器端网关RPC 二、案例&#xff1a; 1、建立设备与网关之间的通讯 2、查看设备和网关是否在线状态啊 3、通过 仪表盘&#xff0c;创建设备A的模拟RPC调用的窗口链接 4、在客户端的网关设备上订阅RPC网关的主题信息 5、通过服务端的窗口&#xff0c;发…

DaPy:实现数据分析与处理

DaPy&#xff1a;实现数据分析与处理 DaPy是一个用于数据分析和处理的Python库&#xff0c;它提供了一系列强大的工具和功能&#xff0c;使开发者能够高效地进行数据清洗、转换和分析。本文将深入解析DaPy库的特点、功能以及使用示例&#xff0c;帮助读者了解如何利用DaPy库处理…

Oracle使用内部包自定义创建表空间和用户

如果之前有类似的表空间,可以使用dbms自动生成对应的表空间和数据文件 select dbms_metadata.get_ddl(TABLESPACE,ts.tablespace_name) from dba_tablespaces ts; 可以使用类似的 SQL> set echo off SQL> spool /data/logs/create_tablespace.log SQL> select dbms…