ROS笔记之TF坐标变换

ROS笔记之TF坐标变换

文章目录

  • ROS笔记之TF坐标变换
    • 一些相关函数的用法
      • tf::TransFormBroadcaster tf1; tf1.sendTransform()
      • tf::StampedTransform()
      • tf::Transform()
      • tf::Vector3()详解
      • br.sendTransform(tf::StampedTransform(tf::Transform(tf::Quaternion::getIdentity(),tf::Vector3),),)详解
      • CMakeLists.txt和package.xml中添加对tf库的支持
      • tf::Quaternion::normalize()
      • tf::quaternionMsgToTF
      • [WARN]:MSG to TF:Quaternion Not Properly Normalized
  • ROS笔记之TF坐标变换原文地址:http://www.autolabor.com.cn/book/ROSTutorials
    • 一.坐标msg消息
    • 二.静态坐标变换
    • 三.动态坐标变换
    • 四.多坐标变换
    • 五.坐标关系查看
    • 六.TF坐标变换实操
    • 七.TF2与TF
    • 八.小结

一些相关函数的用法

tf::TransFormBroadcaster tf1; tf1.sendTransform()

tf::TransformBroadcaster是ROS(机器人操作系统)库中的一个类,允许在ROS系统中的不同坐标系之间广播变换信息。sendTransform()方法是tf::TransformBroadcaster类的成员函数,用于向ROS系统发送变换消息。

以下是对br.sendTransform()函数的详细解释:

  1. br:这是tf::TransformBroadcaster类的实例。通常在需要广播变换信息时创建该变量。

  2. sendTransform():该方法在tf::TransformBroadcaster的实例上调用,用于发送变换消息。它向ROS系统广播两个坐标系之间的变换。

    ``sendTransform()`方法需要多个参数来指定变换信息:

    • const geometry_msgs::TransformStamped& transform:此参数指定要广播的变换数据。它的类型为geometry_msgs::TransformStamped,其中包含平移、旋转和变换的时间戳等信息。

    • const std::string& parent_frame_id:此参数指定变换的父坐标系。它表示变换所定义的参考坐标系。

    • const std::string& child_frame_id:此参数指定变换的子坐标系。它表示相对于父坐标系进行变换的参考坐标系。

    • const ros::Time& time:此参数指定与变换关联的时间戳。它表示变换的有效时间。

    ``sendTransform()`方法将变换消息发布到ROS系统,使其他节点可以订阅并使用此信息进行各种用途,例如传感器融合、定位或机器人控制。

总而言之,br.sendTransform()函数用于使用tf::TransformBroadcaster类在ROS系统中从一个坐标系广播到另一个坐标系的变换消息。

下面是一个简单的示例,演示如何使用tf::TransformBroadcaster类广播一个变换消息:

#include <ros/ros.h>
#include <tf/transform_broadcaster.h>int main(int argc, char** argv)
{// 初始化ROS节点ros::init(argc, argv, "transform_broadcaster_example");ros::NodeHandle nh;// 创建一个tf::TransformBroadcaster对象tf::TransformBroadcaster br;// 循环发布变换消息ros::Rate rate(1.0);  // 发布频率为1Hzwhile (ros::ok()){// 创建一个tf::Transform对象,表示变换关系tf::Transform transform;transform.setOrigin(tf::Vector3(1.0, 2.0, 0.0));  // 平移部分tf::Quaternion q;q.setRPY(0, 0, 1.57);  // 旋转部分(绕Z轴旋转90度)transform.setRotation(q);// 发布变换消息br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "parent_frame", "child_frame"));// 等待一段时间rate.sleep();}return 0;
}

在上述示例中,我们首先创建一个tf::TransformBroadcaster对象,然后在一个循环中发布变换消息。在每次循环中,我们创建一个tf::Transform对象来表示变换关系,设置其平移和旋转部分。然后,使用br.sendTransform()方法将变换消息发布到ROS系统中,指定父坐标系为"parent_frame",子坐标系为"child_frame"。变换消息的时间戳使用ros::Time::now()获取当前时间。最后,通过rate.sleep()等待一段时间,以控制变换消息的发布频率。

这个示例仅用于演示如何使用tf::TransformBroadcaster类广播变换消息。在实际应用中,您需要根据自己的坐标系变换需求进行适当的修改。

tf::StampedTransform()

tf::StampedTransform()是ROS中tf库中的一个类,用于表示具有时间戳的变换信息。它是tf::Transform类的子类,可以包含变换矩阵、平移、旋转以及关联的时间戳。

以下是对tf::StampedTransform()类的详细解释:

  1. tf::StampedTransform类:它表示一个具有时间戳的变换信息,用于描述坐标系之间的变换关系。它继承自tf::Transform类,因此拥有tf::Transform类的所有成员函数和属性。

  2. 构造函数:tf::StampedTransform类提供了多个构造函数,用于创建具有时间戳的变换信息。构造函数的参数包括:

    • const tf::Transform& transform:一个tf::Transform对象,表示变换的平移和旋转部分。
    • const ros::Time& timestamp:一个ROS时间戳,表示与变换关联的时间。
  3. 成员函数:tf::StampedTransform类还提供了一些成员函数,用于获取和设置变换信息的各个部分,如平移、旋转和时间戳。

    • getOrigin():获取变换的平移部分,返回一个tf::Vector3对象。
    • getRotation():获取变换的旋转部分,返回一个tf::Quaternion对象。
    • stamp_:表示与变换关联的时间戳的成员变量。

tf::StampedTransform()类的主要作用是在ROS系统中描述具有时间戳的坐标系之间的变换关系。它可以用于发布和接收具有时间戳的变换消息,并与其他ROS节点共享坐标系变换信息,从而实现传感器融合、定位和控制等应用。

tf::Transform()

tf::Transform是ROS中tf库中的一个类,用于表示坐标系之间的变换关系。它包含了平移和旋转的信息,可以用来描述一个坐标系相对于另一个坐标系的变换。

以下是对tf::Transform类的详细解释:

  1. 构造函数:tf::Transform类提供了多个构造函数,用于创建变换对象。构造函数的参数包括:

    • const tf::Quaternion& rotation:一个tf::Quaternion对象,表示变换的旋转部分。
    • const tf::Vector3& translation:一个tf::Vector3对象,表示变换的平移部分。
  2. 成员函数:tf::Transform类提供了一些成员函数,用于获取和设置变换的不同部分,如平移和旋转。

    • setOrigin(const tf::Vector3& translation):设置变换的平移部分。
    • getOrigin():获取变换的平移部分,返回一个tf::Vector3对象。
    • setRotation(const tf::Quaternion& rotation):设置变换的旋转部分。
    • getRotation():获取变换的旋转部分,返回一个tf::Quaternion对象。
  3. 变换操作:tf::Transform类支持一些常见的变换操作,如乘法和逆变换。

    • operator*(const tf::Transform& other):将当前变换与另一个变换相乘,返回一个新的变换结果。
    • inverse():获取当前变换的逆变换,返回一个新的变换对象。

tf::Transform类的主要作用是描述一个坐标系相对于另一个坐标系的变换关系。通过设置平移和旋转部分,可以定义一个坐标系相对于另一个坐标系的位置和姿态关系。在ROS系统中,tf::Transform类经常与tf::TransformBroadcaster类一起使用,用于发布和接收坐标系变换信息。

例如,可以使用tf::Transform来表示一个机器人在全局坐标系中的位置和姿态,或者表示一个传感器相对于机器人坐标系的位置和姿态。通过将不同的tf::Transform对象相乘,可以组合多个坐标系之间的变换关系,实现坐标系的链式变换。

下面是一个简单的示例,演示如何使用tf::Transform类来表示坐标系之间的变换关系:

#include <ros/ros.h>
#include <tf/transform_broadcaster.h>int main(int argc, char** argv)
{// 初始化ROS节点ros::init(argc, argv, "transform_example");ros::NodeHandle nh;// 创建一个tf::Transform对象,表示变换关系tf::Transform transform;transform.setOrigin(tf::Vector3(1.0, 2.0, 0.0));  // 平移部分tf::Quaternion q;q.setRPY(0, 0, 1.57);  // 旋转部分(绕Z轴旋转90度)transform.setRotation(q);// 输出变换关系的平移和旋转部分tf::Vector3 translation = transform.getOrigin();tf::Quaternion rotation = transform.getRotation();ROS_INFO("Translation: %f, %f, %f", translation.x(), translation.y(), translation.z());ROS_INFO("Rotation: %f, %f, %f, %f", rotation.x(), rotation.y(), rotation.z(), rotation.w());return 0;
}

在上述示例中,我们创建了一个tf::Transform对象,表示一个坐标系相对于另一个坐标系的变换关系。我们设置了平移部分为(1.0, 2.0, 0.0),表示相对于父坐标系的平移向量。旋转部分使用欧拉角表示,我们设置了绕Z轴旋转90度。

然后,我们使用getOrigin()getRotation()成员函数获取变换关系的平移和旋转部分,并通过ROS_INFO输出到控制台。

这个示例仅用于演示如何使用tf::Transform类来表示坐标系之间的变换关系,并获取其平移和旋转信息。在实际应用中,您需要根据自己的需求进行适当的修改和使用。

tf::Vector3()详解

tf::Vector3是ROS中tf库中的一个类,用于表示三维空间中的向量。它包含了三个分量(x、y、z),可以用来表示位置、位移、速度等。

以下是对tf::Vector3类的详细解释:

  1. 构造函数:tf::Vector3类提供了多个构造函数,用于创建向量对象。构造函数的参数包括:

    • double x:向量的x分量。
    • double y:向量的y分量。
    • double z:向量的z分量。
  2. 成员函数:tf::Vector3类提供了一些成员函数,用于获取和设置向量的各个分量。

    • setX(double x):设置向量的x分量。
    • setY(double y):设置向量的y分量。
    • setZ(double z):设置向量的z分量。
    • getX():获取向量的x分量。
    • getY():获取向量的y分量。
    • getZ():获取向量的z分量。
  3. 向量操作:tf::Vector3类支持一些常见的向量操作,如加法、减法、数乘和点积等。

    • operator+(const tf::Vector3& other):将当前向量与另一个向量相加,返回一个新的向量结果。
    • operator-(const tf::Vector3& other):将当前向量与另一个向量相减,返回一个新的向量结果。
    • operator*(double scalar):将当前向量与一个标量相乘,返回一个新的向量结果。
    • dot(const tf::Vector3& other):计算当前向量与另一个向量的点积。

tf::Vector3类的主要作用是表示三维空间中的向量。在ROS系统中,它经常与tf::Transform类一起使用,用于表示坐标系的平移部分。它还可以用于表示位移向量、速度向量等。

例如,可以使用tf::Vector3来表示一个机器人在三维空间中的位置,或者表示一个物体的位移向量。通过对向量进行加法、减法、数乘等操作,可以进行向量运算,实现位置偏移、速度调整等功能。

br.sendTransform(tf::StampedTransform(tf::Transform(tf::Quaternion::getIdentity(),tf::Vector3),),)详解

在您提供的代码片段中,br.sendTransform()函数用于发布一个带有时间戳的变换消息。下面是对代码片段中的参数的详细解释:

br.sendTransform(tf::StampedTransform(tf::Transform(tf::Quaternion::getIdentity(), tf::Vector3),ros::Time::now(),parent_frame,child_frame)
);
  • tf::StampedTransform:这是一个带有时间戳的变换消息类,用于在发布坐标系变换时指定时间戳。它是tf::Transform的子类。构造函数的参数包括:

    • tf::Transform transform:一个tf::Transform对象,表示坐标系之间的变换关系。
    • ros::Time stamp:一个ros::Time对象,表示变换消息的时间戳。
    • std::string parent_frame:一个std::string对象,表示父坐标系的名称。
    • std::string child_frame:一个std::string对象,表示子坐标系的名称。
  • tf::Transform(tf::Quaternion::getIdentity(), tf::Vector3):这是一个tf::Transform对象的构造函数调用,用于创建一个表示单位变换的对象。参数包括:

    • tf::Quaternion::getIdentity():一个静态函数调用,返回一个代表恒等旋转的tf::Quaternion对象。
    • tf::Vector3:一个空的tf::Vector3对象,表示零平移。
  • ros::Time::now():这是一个静态函数调用,返回当前系统时间的ros::Time对象。它用作变换消息的时间戳。

  • parent_frame:一个std::string对象,表示父坐标系的名称。

  • child_frame:一个std::string对象,表示子坐标系的名称。

br.sendTransform()函数通过tf::TransformBroadcaster对象发布一个带有时间戳的坐标系变换消息。该消息描述了父坐标系到子坐标系的变换关系,并指定了变换消息的时间戳。

CMakeLists.txt和package.xml中添加对tf库的支持

在CMakeLists.txt文件中,您可以使用以下方法来添加对tf库的依赖:

find_package(catkin REQUIRED COMPONENTS...tf...
)catkin_package(...CATKIN_DEPENDS...tf...
)...target_link_libraries(your_executable_name${catkin_LIBRARIES}
)

在上述示例中,假设您的项目是一个ROS项目。通过find_package(catkin REQUIRED COMPONENTS ... tf ...),您告诉CMake在ROS环境中查找和导入tf库。然后,通过在catkin_package()CATKIN_DEPENDS部分添加tf,您指定了您的软件包依赖于tf库。

最后,通过在target_link_libraries()中添加${catkin_LIBRARIES},您将tf库添加到您的可执行文件的链接器指令中。

请确保您的CMakeLists.txt文件中包含了正确的ROS构建指令和其他必要的依赖项,具体根据您的项目进行相应的修改。

在package.xml文件中,您可以使用以下方式将tf库添加为依赖项:

<build_depend>tf</build_depend>
<exec_depend>tf</exec_depend>

在package.xml文件中,build_depend元素用于指定构建时的依赖项,而exec_depend元素用于指定运行时的依赖项。通过在这两个元素中添加tf,您将tf库添加为构建和运行时的依赖项。

确保在package.xml文件中的其他部分包含了正确的ROS包描述信息和其他依赖项,具体根据您的项目进行相应的修改。

tf::Quaternion::normalize()

以下是一个示例代码片段,演示如何归一化四元数:

#include <iostream>
#include <cmath>
#include <tf/tf.h>int main() {// 创建一个未归一化的四元数tf::Quaternion quaternion(1.0, 2.0, 3.0, 4.0);// 输出未归一化的四元数std::cout << "未归一化的四元数:" << quaternion.x() << ", " << quaternion.y() << ", " << quaternion.z() << ", " << quaternion.w() << std::endl;// 归一化四元数quaternion.normalize();// 输出归一化后的四元数std::cout << "归一化的四元数:" << quaternion.x() << ", " << quaternion.y() << ", " << quaternion.z() << ", " << quaternion.w() << std::endl;return 0;
}

在这个示例中,我们使用了ROS的tf库来处理四元数。首先,我们创建了一个未归一化的四元数对象quaternion,其中包含了一些任意的值。然后,我们调用normalize()函数对该四元数进行归一化操作。最后,我们输出未归一化和归一化后的四元数值。

请注意,在实际使用中,您需要根据您的具体应用场景和变换消息的来源,将归一化操作应用到适当的地方。这个示例提供了一个基本的框架,供您参考如何进行四元数的归一化操作。

tf::quaternionMsgToTF

tf::quaternionMsgToTF是ROS tf库中的一个函数,用于将ROS消息中的Quaternion消息类型转换为tf库中的tf::Quaternion类型。

函数签名如下:

void quaternionMsgToTF(const geometry_msgs::Quaternion& msg, tf::Quaternion& bt);

参数说明:

  • msg:geometry_msgs包中的Quaternion消息类型的引用,表示要转换的ROS消息中的四元数。
  • bt:tf库中的tf::Quaternion类型的引用,表示转换后的四元数结果。

此函数用于将ROS中定义的geometry_msgs/Quaternion消息类型转换为tf库中的tf::Quaternion类型,以便在tf坐标转换系统中使用。

使用示例:

#include <ros/ros.h>
#include <geometry_msgs/Quaternion.h>
#include <tf/transform_datatypes.h>int main(int argc, char** argv)
{ros::init(argc, argv, "quaternion_conversion_example");ros::NodeHandle nh;// 创建一个ROS的Quaternion消息geometry_msgs::Quaternion rosQuaternion;rosQuaternion.x = 1.0;rosQuaternion.y = 2.0;rosQuaternion.z = 3.0;rosQuaternion.w = 4.0;// 将ROS Quaternion消息转换为tf::Quaternion类型tf::Quaternion tfQuaternion;tf::quaternionMsgToTF(rosQuaternion, tfQuaternion);// 输出转换后的tf::Quaternion值ROS_INFO("tf::Quaternion: x=%f, y=%f, z=%f, w=%f",tfQuaternion.x(), tfQuaternion.y(), tfQuaternion.z(), tfQuaternion.w());return 0;
}

在示例中,我们首先创建一个ROS的Quaternion消息类型,并为其赋予一些任意的值。然后,我们使用quaternionMsgToTF函数将ROS消息转换为tf::Quaternion类型。最后,我们输出转换后的tf::Quaternion的值。

通过quaternionMsgToTF函数,您可以方便地在ROS和tf库之间进行四元数的转换,以满足不同模块之间的坐标变换和旋转表示需求。

[WARN]:MSG to TF:Quaternion Not Properly Normalized

"[WARN]: MSG to TF: Quaternion Not Properly Normalized"警告消息表明将ROS消息中的四元数转换为tf库中的tf::Quaternion类型时,发现四元数没有被正确归一化。

为了解决这个警告,您可以在转换之前对ROS消息中的四元数进行归一化操作,以确保其长度为1。以下是一个示例代码片段,演示如何归一化ROS消息中的四元数:

#include <ros/ros.h>
#include <geometry_msgs/Quaternion.h>
#include <tf/transform_datatypes.h>int main(int argc, char** argv)
{ros::init(argc, argv, "quaternion_conversion_example");ros::NodeHandle nh;// 创建一个ROS的Quaternion消息geometry_msgs::Quaternion rosQuaternion;rosQuaternion.x = 1.0;rosQuaternion.y = 2.0;rosQuaternion.z = 3.0;rosQuaternion.w = 4.0;// 归一化ROS Quaternion消息tf::Quaternion tfQuaternion;tf::quaternionMsgToTF(rosQuaternion, tfQuaternion);tfQuaternion.normalize(); // 归一化操作// 输出归一化后的tf::Quaternion值ROS_INFO("Normalized tf::Quaternion: x=%f, y=%f, z=%f, w=%f",tfQuaternion.x(), tfQuaternion.y(), tfQuaternion.z(), tfQuaternion.w());return 0;
}

在示例中,我们使用quaternionMsgToTF函数将ROS Quaternion消息转换为tf::Quaternion类型。然后,我们对tf::Quaternion进行归一化操作,确保其长度为1。最后,我们输出归一化后的tf::Quaternion值。

通过在转换之前进行归一化操作,您可以避免警告消息并确保准确的旋转表示。请注意,具体的归一化操作可能因您的应用场景而有所不同,您可以根据需要进行适当的调整。

ROS笔记之TF坐标变换原文地址:http://www.autolabor.com.cn/book/ROSTutorials

在这里插入图片描述

在这里插入图片描述

一.坐标msg消息

在这里插入图片描述

二.静态坐标变换

在这里插入图片描述

方案A:C++实现
1.创建功能包
创建项目功能包依赖于 tf2、tf2_ros、tf2_geometry_msgs、roscpp rospy std_msgs geometry_msgs
2.发布方

/* 静态坐标变换发布方:发布关于 laser 坐标系的位置信息 实现流程:1.包含头文件2.初始化 ROS 节点3.创建静态坐标转换广播器4.创建坐标系信息5.广播器发布坐标系信息6.spin()
*/// 1.包含头文件
#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[])
{setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"static_brocast");// 3.创建静态坐标转换广播器tf2_ros::StaticTransformBroadcaster broadcaster;// 4.创建坐标系信息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.2;ts.transform.translation.y = 0.0;ts.transform.translation.z = 0.5;//----设置四元数:将 欧拉角数据转换成四元数tf2::Quaternion qtn;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();// 5.广播器发布坐标系信息broadcaster.sendTransform(ts);ros::spin();return 0;
}

配置文件此处略。

3.订阅方

/*  订阅坐标系信息,生成一个相对于 子级坐标系的坐标点数据,转换成父级坐标系中的坐标点实现流程:1.包含头文件2.初始化 ROS 节点3.创建 TF 订阅节点4.生成一个坐标点(相对于子级坐标系)5.转换坐标点(相对于父级坐标系)6.spin()
*/
//1.包含头文件
#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" //注意: 调用 transform 必须包含该头文件int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"tf_sub");ros::NodeHandle nh;// 3.创建 TF 订阅节点tf2_ros::Buffer buffer;tf2_ros::TransformListener listener(buffer);ros::Rate r(1);while (ros::ok()){// 4.生成一个坐标点(相对于子级坐标系)geometry_msgs::PointStamped point_laser;point_laser.header.frame_id = "laser";point_laser.header.stamp = ros::Time::now();point_laser.point.x = 1;point_laser.point.y = 2;point_laser.point.z = 7.3;// 5.转换坐标点(相对于父级坐标系)//新建一个坐标点,用于接收转换结果  //--------------使用 try 语句或休眠,否则可能由于缓存接收延迟而导致坐标转换失败------------------------try{geometry_msgs::PointStamped point_base;point_base = buffer.transform(point_laser,"base_link");ROS_INFO("转换后的数据:(%.2f,%.2f,%.2f),参考的坐标系是:",point_base.point.x,point_base.point.y,point_base.point.z,point_base.header.frame_id.c_str());}catch(const std::exception& e){// std::cerr << e.what() << '\n';ROS_INFO("程序异常.....");}r.sleep();  ros::spinOnce();}return 0;
}

配置文件此处略。

4.执行

可以使用命令行或launch文件的方式分别启动发布节点与订阅节点,如果程序无异常,控制台将输出,坐标转换后的结果。

在这里插入图片描述

三.动态坐标变换

所谓动态坐标变换,是指两个坐标系之间的相对位置是变化的。

需求描述:

启动 turtlesim_node,该节点中窗体有一个世界坐标系(左下角为坐标系原点),乌龟是另一个坐标系,键盘控制乌龟运动,将两个坐标系的相对位置动态发布。

在这里插入图片描述

实现分析:

乌龟本身不但可以看作坐标系,也是世界坐标系中的一个坐标点

订阅 turtle1/pose,可以获取乌龟在世界坐标系的 x坐标、y坐标、偏移量以及线速度和角速度

将 pose 信息转换成 坐标系相对信息并发布

实现流程:C++ 与 Python 实现流程一致

新建功能包,添加依赖

创建坐标相对关系发布方(同时需要订阅乌龟位姿信息)

创建坐标相对关系订阅方

执行

方案A:C++实现

1.创建功能包

创建项目功能包依赖于 tf2、tf2_ros、tf2_geometry_msgs、roscpp rospy std_msgs geometry_msgs、turtlesim

2.发布方

/*  动态的坐标系相对姿态发布(一个坐标系相对于另一个坐标系的相对姿态是不断变动的)需求: 启动 turtlesim_node,该节点中窗体有一个世界坐标系(左下角为坐标系原点),乌龟是另一个坐标系,键盘控制乌龟运动,将两个坐标系的相对位置动态发布实现分析:1.乌龟本身不但可以看作坐标系,也是世界坐标系中的一个坐标点2.订阅 turtle1/pose,可以获取乌龟在世界坐标系的 x坐标、y坐标、偏移量以及线速度和角速度3.将 pose 信息转换成 坐标系相对信息并发布实现流程:1.包含头文件2.初始化 ROS 节点3.创建 ROS 句柄4.创建订阅对象5.回调函数处理订阅到的数据(实现TF广播)5-1.创建 TF 广播器5-2.创建 广播的数据(通过 pose 设置)5-3.广播器发布数据6.spin
*/
// 1.包含头文件
#include "ros/ros.h"
#include "turtlesim/Pose.h"
#include "tf2_ros/transform_broadcaster.h"
#include "geometry_msgs/TransformStamped.h"
#include "tf2/LinearMath/Quaternion.h"void doPose(const turtlesim::Pose::ConstPtr& pose){//  5-1.创建 TF 广播器static tf2_ros::TransformBroadcaster broadcaster;//  5-2.创建 广播的数据(通过 pose 设置)geometry_msgs::TransformStamped tfs;//  |----头设置tfs.header.frame_id = "world";tfs.header.stamp = ros::Time::now();//  |----坐标系 IDtfs.child_frame_id = "turtle1";//  |----坐标系相对信息设置tfs.transform.translation.x = pose->x;tfs.transform.translation.y = pose->y;tfs.transform.translation.z = 0.0; // 二维实现,pose 中没有z,z 是 0//  |--------- 四元数设置tf2::Quaternion qtn;qtn.setRPY(0,0,pose->theta);tfs.transform.rotation.x = qtn.getX();tfs.transform.rotation.y = qtn.getY();tfs.transform.rotation.z = qtn.getZ();tfs.transform.rotation.w = qtn.getW();//  5-3.广播器发布数据broadcaster.sendTransform(tfs);
}int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"dynamic_tf_pub");// 3.创建 ROS 句柄ros::NodeHandle nh;// 4.创建订阅对象ros::Subscriber sub = nh.subscribe<turtlesim::Pose>("/turtle1/pose",1000,doPose);//     5.回调函数处理订阅到的数据(实现TF广播)//        // 6.spinros::spin();return 0;
}

配置文件此处略。

3.订阅方

//1.包含头文件
#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" //注意: 调用 transform 必须包含该头文件int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ROS 节点ros::init(argc,argv,"dynamic_tf_sub");ros::NodeHandle nh;// 3.创建 TF 订阅节点tf2_ros::Buffer buffer;tf2_ros::TransformListener listener(buffer);ros::Rate r(1);while (ros::ok()){// 4.生成一个坐标点(相对于子级坐标系)geometry_msgs::PointStamped point_laser;point_laser.header.frame_id = "turtle1";point_laser.header.stamp = ros::Time();point_laser.point.x = 1;point_laser.point.y = 1;point_laser.point.z = 0;// 5.转换坐标点(相对于父级坐标系)//新建一个坐标点,用于接收转换结果  //--------------使用 try 语句或休眠,否则可能由于缓存接收延迟而导致坐标转换失败------------------------try{geometry_msgs::PointStamped point_base;point_base = buffer.transform(point_laser,"world");ROS_INFO("坐标点相对于 world 的坐标为:(%.2f,%.2f,%.2f)",point_base.point.x,point_base.point.y,point_base.point.z);}catch(const std::exception& e){// std::cerr << e.what() << '\n';ROS_INFO("程序异常:%s",e.what());}r.sleep();  ros::spinOnce();}return 0;
}

配置文件此处略。

4.执行

可以使用命令行或launch文件的方式分别启动发布节点与订阅节点,如果程序无异常,与演示结果类似。

可以使用 rviz 查看坐标系相对关系。

四.多坐标变换

在这里插入图片描述

1.创建功能包
创建项目功能包依赖于 tf2、tf2_ros、tf2_geometry_msgs、roscpp rospy std_msgs geometry_msgs、turtlesim

2.发布方

为了方便,使用静态坐标变换发布

<launch><node pkg="tf2_ros" type="static_transform_publisher" name="son1" args="0.2 0.8 0.3 0 0 0 /world /son1" output="screen" /><node pkg="tf2_ros" type="static_transform_publisher" name="son2" args="0.5 0 0 0 0 0 /world /son2" output="screen" />
</launch>

3.订阅方

/*需求:现有坐标系统,父级坐标系统 world,下有两子级系统 son1,son2,son1 相对于 world,以及 son2 相对于 world 的关系是已知的,求 son1 与 son2中的坐标关系,又已知在 son1中一点的坐标,要求求出该点在 son2 中的坐标
实现流程:1.包含头文件2.初始化 ros 节点3.创建 ros 句柄4.创建 TF 订阅对象5.解析订阅信息中获取 son1 坐标系原点在 son2 中的坐标解析 son1 中的点相对于 son2 的坐标6.spin*/
//1.包含头文件
#include "ros/ros.h"
#include "tf2_ros/transform_listener.h"
#include "tf2/LinearMath/Quaternion.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"
#include "geometry_msgs/TransformStamped.h"
#include "geometry_msgs/PointStamped.h"int main(int argc, char *argv[])
{   setlocale(LC_ALL,"");// 2.初始化 ros 节点ros::init(argc,argv,"sub_frames");// 3.创建 ros 句柄ros::NodeHandle nh;// 4.创建 TF 订阅对象tf2_ros::Buffer buffer; tf2_ros::TransformListener listener(buffer);// 5.解析订阅信息中获取 son1 坐标系原点在 son2 中的坐标ros::Rate r(1);while (ros::ok()){try{//   解析 son1 中的点相对于 son2 的坐标geometry_msgs::TransformStamped tfs = buffer.lookupTransform("son2","son1",ros::Time(0));ROS_INFO("Son1 相对于 Son2 的坐标关系:父坐标系ID=%s",tfs.header.frame_id.c_str());ROS_INFO("Son1 相对于 Son2 的坐标关系:子坐标系ID=%s",tfs.child_frame_id.c_str());ROS_INFO("Son1 相对于 Son2 的坐标关系:x=%.2f,y=%.2f,z=%.2f",tfs.transform.translation.x,tfs.transform.translation.y,tfs.transform.translation.z);// 坐标点解析geometry_msgs::PointStamped ps;ps.header.frame_id = "son1";ps.header.stamp = ros::Time::now();ps.point.x = 1.0;ps.point.y = 2.0;ps.point.z = 3.0;geometry_msgs::PointStamped psAtSon2;psAtSon2 = buffer.transform(ps,"son2");ROS_INFO("在 Son2 中的坐标:x=%.2f,y=%.2f,z=%.2f",psAtSon2.point.x,psAtSon2.point.y,psAtSon2.point.z);}catch(const std::exception& e){// std::cerr << e.what() << '\n';ROS_INFO("异常信息:%s",e.what());}r.sleep();// 6.spinros::spinOnce();}return 0;
}

配置文件此处略。

4.执行
可以使用命令行或launch文件的方式分别启动发布节点与订阅节点,如果程序无异常,将输出换算后的结果。

五.坐标关系查看

在这里插入图片描述

六.TF坐标变换实操

需求描述:

程序启动之初: 产生两只乌龟,中间的乌龟(A) 和 左下乌龟(B), B 会自动运行至A的位置,并且键盘控制时,只是控制 A 的运动,但是 B 可以跟随 A 运行

结果演示:

在这里插入图片描述

实现分析:

乌龟跟随实现的核心,是乌龟A和B都要发布相对世界坐标系的坐标信息,然后,订阅到该信息需要转换获取A相对于B坐标系的信息,最后,再生成速度信息,并控制B运动。

  • 启动乌龟显示节点
  • 在乌龟显示窗体中生成一只新的乌龟(需要使用服务)
  • 编写两只乌龟发布坐标信息的节点
  • 编写订阅节点订阅坐标信息并生成新的相对关系生成速度信息

实现流程:C++ 与 Python 实现流程一致

  • 新建功能包,添加依赖

  • 编写服务客户端,用于生成一只新的乌龟

  • 编写发布方,发布两只乌龟的坐标信息

  • 编写订阅方,订阅两只乌龟信息,生成速度信息并发布

  • 运行

准备工作:

1.了解如何创建第二只乌龟,且不受键盘控制

创建第二只乌龟需要使用rosservice,话题使用的是 spawn

rosservice call /spawn “x: 1.0
y: 1.0
theta: 1.0
name: ‘turtle_flow’”
name: “turtle_flow”
Copy
键盘是无法控制第二只乌龟运动的,因为使用的话题: /第二只乌龟名称/cmd_vel,对应的要控制乌龟运动必须发布对应的话题消息

2.了解如何获取两只乌龟的坐标

是通过话题 /乌龟名称/pose 来获取的

x: 1.0 //x坐标
y: 1.0 //y坐标
theta: -1.21437060833 //角度
linear_velocity: 0.0 //线速度
angular_velocity: 1.0 //角速度
Copy
方案A:C++实现

1.创建功能包
创建项目功能包依赖于 tf2、tf2_ros、tf2_geometry_msgs、roscpp rospy std_msgs geometry_msgs、turtlesim

2.服务客户端(生成乌龟)

/* 创建第二只小乌龟*/
#include "ros/ros.h"
#include "turtlesim/Spawn.h"int main(int argc, char *argv[])
{setlocale(LC_ALL,"");//执行初始化ros::init(argc,argv,"create_turtle");//创建节点ros::NodeHandle nh;//创建服务客户端ros::ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn");ros::service::waitForService("/spawn");turtlesim::Spawn spawn;spawn.request.name = "turtle2";spawn.request.x = 1.0;spawn.request.y = 2.0;spawn.request.theta = 3.12415926;bool flag = client.call(spawn);if (flag){ROS_INFO("乌龟%s创建成功!",spawn.response.name.c_str());}else{ROS_INFO("乌龟2创建失败!");}ros::spin();return 0;
}

配置文件此处略。

3.发布方(发布两只乌龟的坐标信息)
可以订阅乌龟的位姿信息,然后再转换成坐标信息,两只乌龟的实现逻辑相同,只是订阅的话题名称,生成的坐标信息等稍有差异,可以将差异部分通过参数传入:

该节点需要启动两次
每次启动时都需要传入乌龟节点名称(第一次是 turtle1 第二次是 turtle2)

/*  该文件实现:需要订阅 turtle1 和 turtle2 的 pose,然后广播相对 world 的坐标系信息注意: 订阅的两只 turtle,除了命名空间(turtle1 和 turtle2)不同外,其他的话题名称和实现逻辑都是一样的,所以我们可以将所需的命名空间通过 args 动态传入实现流程:1.包含头文件2.初始化 ros 节点3.解析传入的命名空间4.创建 ros 句柄5.创建订阅对象6.回调函数处理订阅的 pose 信息6-1.创建 TF 广播器6-2.将 pose 信息转换成 TransFormStamped6-3.发布7.spin*/
//1.包含头文件
#include "ros/ros.h"
#include "turtlesim/Pose.h"
#include "tf2_ros/transform_broadcaster.h"
#include "tf2/LinearMath/Quaternion.h"
#include "geometry_msgs/TransformStamped.h"
//保存乌龟名称
std::string turtle_name;void doPose(const turtlesim::Pose::ConstPtr& pose){//  6-1.创建 TF 广播器 ---------------------------------------- 注意 staticstatic tf2_ros::TransformBroadcaster broadcaster;//  6-2.将 pose 信息转换成 TransFormStampedgeometry_msgs::TransformStamped tfs;tfs.header.frame_id = "world";tfs.header.stamp = ros::Time::now();tfs.child_frame_id = turtle_name;tfs.transform.translation.x = pose->x;tfs.transform.translation.y = pose->y;tfs.transform.translation.z = 0.0;tf2::Quaternion qtn;qtn.setRPY(0,0,pose->theta);tfs.transform.rotation.x = qtn.getX();tfs.transform.rotation.y = qtn.getY();tfs.transform.rotation.z = qtn.getZ();tfs.transform.rotation.w = qtn.getW();//  6-3.发布broadcaster.sendTransform(tfs);} int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ros 节点ros::init(argc,argv,"pub_tf");// 3.解析传入的命名空间if (argc != 2){ROS_ERROR("请传入正确的参数");} else {turtle_name = argv[1];ROS_INFO("乌龟 %s 坐标发送启动",turtle_name.c_str());}// 4.创建 ros 句柄ros::NodeHandle nh;// 5.创建订阅对象ros::Subscriber sub = nh.subscribe<turtlesim::Pose>(turtle_name + "/pose",1000,doPose);//     6.回调函数处理订阅的 pose 信息//         6-1.创建 TF 广播器//         6-2.将 pose 信息转换成 TransFormStamped//         6-3.发布// 7.spinros::spin();return 0;
}

配置文件此处略。

4.订阅方(解析坐标信息并生成速度信息)

/*  订阅 turtle1 和 turtle2 的 TF 广播信息,查找并转换时间最近的 TF 信息将 turtle1 转换成相对 turtle2 的坐标,在计算线速度和角速度并发布实现流程:1.包含头文件2.初始化 ros 节点3.创建 ros 句柄4.创建 TF 订阅对象5.处理订阅到的 TF6.spin*/
//1.包含头文件
#include "ros/ros.h"
#include "tf2_ros/transform_listener.h"
#include "geometry_msgs/TransformStamped.h"
#include "geometry_msgs/Twist.h"int main(int argc, char *argv[])
{setlocale(LC_ALL,"");// 2.初始化 ros 节点ros::init(argc,argv,"sub_TF");// 3.创建 ros 句柄ros::NodeHandle nh;// 4.创建 TF 订阅对象tf2_ros::Buffer buffer;tf2_ros::TransformListener listener(buffer);// 5.处理订阅到的 TF// 需要创建发布 /turtle2/cmd_vel 的 publisher 对象ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel",1000);ros::Rate rate(10);while (ros::ok()){try{//5-1.先获取 turtle1 相对 turtle2 的坐标信息geometry_msgs::TransformStamped tfs = buffer.lookupTransform("turtle2","turtle1",ros::Time(0));//5-2.根据坐标信息生成速度信息 -- geometry_msgs/Twist.hgeometry_msgs::Twist twist;twist.linear.x = 0.5 * sqrt(pow(tfs.transform.translation.x,2) + pow(tfs.transform.translation.y,2));twist.angular.z = 4 * atan2(tfs.transform.translation.y,tfs.transform.translation.x);//5-3.发布速度信息 -- 需要提前创建 publish 对象pub.publish(twist);}catch(const std::exception& e){// std::cerr << e.what() << '\n';ROS_INFO("错误提示:%s",e.what());}rate.sleep();// 6.spinros::spinOnce();}return 0;
}

配置文件此处略。

5.运行
使用 launch 文件组织需要运行的节点,内容示例如下:

<!--tf2 实现小乌龟跟随案例
-->
<launch><!-- 启动乌龟节点与键盘控制节点 --><node pkg="turtlesim" type="turtlesim_node" name="turtle1" output="screen" /><node pkg="turtlesim" type="turtle_teleop_key" name="key_control" output="screen"/><!-- 启动创建第二只乌龟的节点 --><node pkg="demo_tf2_test" type="Test01_Create_Turtle2" name="turtle2" output="screen" /><!-- 启动两个坐标发布节点 --><node pkg="demo_tf2_test" type="Test02_TF2_Caster" name="caster1" output="screen" args="turtle1" /><node pkg="demo_tf2_test" type="Test02_TF2_Caster" name="caster2" output="screen" args="turtle2" /><!-- 启动坐标转换节点 --><node pkg="demo_tf2_test" type="Test03_TF2_Listener" name="listener" output="screen" />
</launch>

七.TF2与TF

在这里插入图片描述

八.小结

在这里插入图片描述

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

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

相关文章

RT-DETR 项目【训练】【验证】【推理】脚本

文章目录 训练 --train.py推理 --detect.py验证 --val.py不训练,只查看模型结构/参数量 --test.py有同学问 RT-DETR 怎么训练,其实和 YOLOv8 几乎一样,但是有很多同学没接触过 v8 我这里直接给大家写好几个脚本,大家直接在我的脚本上调节参数就可以训练了, 脚本包含【训…

java数据机构.冒泡排序,选择排序 插入排序 递归算法,递归求阶乘,快速排序

排序算法 冒泡排序选择排序插入排序递归算法递归求1~100的和递归求阶乘 快速排序总结 冒泡排序 相邻两个元素比较,大的放右边,小的放左边 第一轮循环结束最大值已经找到,在数组最右边(归为算法) 第二轮在剩余的元素比较找到次大值,第二轮可以少循环一次 如果有n个数据,总共我们…

《python深度学习》笔记(二十):神经网络的解释方法之CAM、Grad-CAM、Grad-CAM++、LayerCAM

原理优点缺点GAP将多维特征映射降维为一个固定长度的特征向量①减少了模型的参数量&#xff1b;②保留更多的空间位置信息&#xff1b;③可并行计算&#xff0c;计算效率高&#xff1b;④具有一定程度的不变性①可能导致信息的损失&#xff1b;②忽略不同尺度的空间信息CAM利用…

Servlet对象生命周期

Servlet 生命周期包括加载与实例化、初始化、服务请求、销毁等阶段。 ervlet 的生命周期包括以下阶段&#xff1a; 加载与实例化&#xff1a;当容器启动或者第一次请求到达时&#xff0c;Servlet 容器加载 Servlet 类并创建 Servlet 实例。 初始化&#xff1a;在 Servlet 实例…

网络安全演练(一句话木马)

在享受互联网带来的便利的同时&#xff0c;也充满了各种网络安全风险&#xff0c;本文通过搭建实验环境&#xff0c;演示一句话木马获取主机权限。 演示环境 服务端&#xff1a;安装LAMP环境&#xff0c;部署web网站&#xff0c;上传一句话木马文件 客户端&#xff1a;安装A…

qt6-QPushButton无法显示为类

问题 在编写QT程序时&#xff0c;不同颜色表示不同的含义。在设计基本的界面&#xff0c;需要使用QRadioButton时&#xff0c;相应的字符为紫色&#xff0c;紫色为类名。这篇简单说明了下&#xff0c;也可以鼠标点击页面&#xff0c;可以出现提示。 但是上面图片中显示&#…

视频转序列图片:掌握技巧,轻松转换

随着社交媒体和视频平台的日益普及&#xff0c;视频已成为我们生活中不可或缺的一部分。有时&#xff0c;我们需要将视频转换为图片序列&#xff0c;例如制作GIF动图或提取视频中的特定画面。现在一起来看云炫AI智剪如何将视频转换为序列图片&#xff0c;并轻松实现转换。 操作…

OpenShift - 利用容器的特权配置实现对OpenShift攻击

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.14 的环境中验证 本文是《容器安全 - 利用容器的特权配置实现对Kubernetes攻击》的后续篇&#xff0c;来介绍 在 OpenShift 环境中的容器特权配置和攻击过程和 Kubernetes 环境的差异。 文…

框架安全-CVE 复现Apache ShiroApache Solr漏洞复现

文章目录 服务攻防-框架安全&CVE 复现&Apache Shiro&Apache Solr漏洞复现中间件列表常见开发框架Apache Shiro-组件框架安全暴露的安全问题漏洞复现Apache Shiro认证绕过漏洞&#xff08;CVE-2020-1957&#xff09;CVE-2020-11989验证绕过漏洞CVE_2016_4437 Shiro-…

城市内涝解决方案:实时监测,提前预警,让城市更安全

城市内涝积水问题是指城市地区在短时间内遭遇强降雨后&#xff0c;地面积水过多&#xff0c;导致城市交通堵塞、居民生活不便、财产损失等问题。近年来&#xff0c;随着全球气候变化和城市化进程的加速&#xff0c;城市内涝积水问题越来越突出&#xff0c;成为城市发展中的一大…

基于设深度学习的人脸性别年龄识别系统 计算机竞赛

文章目录 0 前言1 课题描述2 实现效果3 算法实现原理3.1 数据集3.2 深度学习识别算法3.3 特征提取主干网络3.4 总体实现流程 4 具体实现4.1 预训练数据格式4.2 部分实现代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习机器视觉的…

软件测试/测试开发丨ChatGPT能否成为PPT最佳伴侣

点此获取更多相关资料 简介 PPT 已经渗透到我们的日常工作中&#xff0c;无论是工作汇报、商务报告、学术演讲、培训材料都常常要求编写一个正式的 PPT&#xff0c;协助完成一次汇报或一次演讲。PPT相比于传统文本的就是有布局、图片、动画效果等&#xff0c;可以给到观众更好…

【leetcode】26. 删除有序数组中的重复项(图解)

目录 1. 思路&#xff08;图解&#xff09;2. 代码 题目链接&#xff1a; leetcode 26. 删除有序数组中的重复项 题目描述&#xff1a; 注意返回的是去重后的数组长度&#xff0c;但是输出的是去重后的数组元素。 1. 思路&#xff08;图解&#xff09; 思路&#xff1a;快慢…

在maven官网中如何下载低版本的maven

链接&#xff1a;https://archive.apache.org/dist/maven/maven-3/

快速了解相似检索方法

一、相似检索方法总体分析 相似检索方法是一种用于从大量数据中找到与查询数据相似的数据项的技术。这种方法通常用于信息检索、推荐系统、图像处理、自然语言处理等领域。相似检索主要方法可以总体分为以下几类&#xff1a; 基于距离度量的方法&#xff1a; 余弦相似度&…

Postman接口测试工具,提高SpringBoot开发效率

文章目录 &#x1f33a;工具—postman⭐作用&#x1f3f3;️‍&#x1f308;安装&#x1f388;创建工作空间 &#x1f384;简单参数⭐原始方式&#x1f388;我们建立springboot项目&#xff0c;输入下面的代码&#x1f388;运行 ⭐SpringBoot方式 &#x1f384;实体参数&#x…

正点原子嵌入式linux驱动开发——Linux 音频驱动

音频是最常用到的功能&#xff0c;音频也是linux和安卓的重点应用场合。STM32MP1带有SAI接口&#xff0c;正点原子的STM32MP1开发板通过此接口外接了一个CS42L51音频DAC芯片&#xff0c;本章就来学习一下如何使能CS42L51驱动&#xff0c;并且CS42L51通过芯片来完成音乐播放与录…

Day39 QTableWidget类的使用

1.简介 介绍QtableWidget各种属性的用法&#xff0c;以及常用的一些信号&#xff0c;最后利用这些特性&#xff0c;制作一个用于下发设备运行参数的表格。该表格可以实现折叠和取消折叠&#xff0c;在源代码中用了事件过滤器实现&#xff0c;也可以用自带的click信号。显示了图…

“第五十九天”

这是昨天那道题&#xff0c;这个后面自己的处理思路还是差了点&#xff0c;这道题关键感觉就是对进位的处理的&#xff0c;由于进位的存在&#xff0c;所以处理数据的时候只能从最低位开始&#xff0c;我一开始是从高位处理的&#xff0c;而且后面越来越迷&#xff0c;这个点一…

自家开发VS第三方美颜SDK:技术和资源的比较

开发直播平台时&#xff0c;开发人员面临一个关键决策&#xff1a;是选择使用第三方美颜SDK&#xff0c;还是自家开发美颜算法&#xff1f;本文将深入探讨这两种方法的技术和资源方面的比较&#xff0c;帮助开发者更好地决定哪种途径最适合他们的应用。 一、第三方美颜SDK&am…