ROS学习笔记三(TF的类)

news/2024/4/29 12:15:56/文章来源:https://blog.csdn.net/Zero_979/article/details/127241141

1.数据类型

数据类型定义在tf/transform_datatypes.h.里

1.1 基本数据类型(Quaternion, Vector, Point, Pose, Transform)

Typetf
Quaterniontf::Quaternion
Vectortf::Vector3
Pointtf::Point
Posetf::Pose
Transformtf::Transform

1.2 tf::Stamped

tf::Stamped在上面的数据类型(tf::Transform除外)上被模板化,元素有frame_id_和stamp_

template <typename T>
class Stamped : public T{public:ros::Time stamp_;std::string frame_id_;Stamped() :frame_id_ ("NO_ID_STAMPED_DEFAULT_CONSTRUCTION"){}; //仅用于预分配的默认构造函数Stamped(const T& input, const ros::Time& timestamp, const std::string & frame_id);void setData(const T& input);
};

1.3 tf::StampedTransform

tf::StampedTransform是tf::Transforms的一个特例,同时要求具有frame_id,stamp 和 child_frame_id。

/** \tf使用的stamp Transform数据类型*/
class StampedTransform : public tf::Transform
{
public:ros::Time stamp_; ///< The timestamp associated with this transform  std::string frame_id_; ///< The frame_id of the coordinate frame in which this transform is defined                                                            std::string child_frame_id_; ///< The frame_id of the coordinate frame this transform defines                                                                   StampedTransform(const tf::Transform& input, const ros::Time& timestamp, const std::string & frame_id, const std::string & child_frame_id):tf::Transform (input), stamp_ ( timestamp ), frame_id_ (frame_id), child_frame_id_(child_frame_id){ };/** \brief Default constructor only to be used for preallocation */StampedTransform() { };/** \brief Set the inherited Traonsform data */void setData(const tf::Transform& input){*static_cast<tf::Transform*>(this) = input;};};

2. 辅助函数

tf::Quaternion createIdentityQuaternion()//返回一个四元数
tf::Quaternion createQuaternionFromRPY(double roll,double pitch,double yaw)

返回一个由由固定轴横滚角、俯仰角和偏航角组成的tf::Quaternion

geometry_msgs::Quaternion createQuaternionMsgFromRollPitchYaw(double roll,double pitch,double yaw)

返回一个由由固定轴横滚角、俯仰角和偏航角组成的geometry_msgs::Quaternion

3.TF使用方法

当我们使用TF包时,需要编写两个程序,分别用来执行监听TF变换和广播TF变换的功能,我们称它们为TF监听器和TF广播器。

  • TF监听器:监听TF变换,接收并缓存系统中发布的所有参考系变换,并从中查询所需要的参考系变换。
  • TF广播器:广播TF变换,向系统中广播参考系之间的坐标变换关系。系统中可能会存在多个不同部分的tf变换广播,但每个广播都可以直接将参考系变换关系直接插入tf树中,不需要再进行同步。

4. TransformBroadcaster

此类提供了一种发布坐标系变换信息的简便方法。它将处理所有消息和消息填充。函数原型列出了每条消息所需的所有必要数据。

void tf::TransformBroadcaster::sendTransform(const StampedTransform & transform)

发送StampedTransform,stamped数据结构包括frame_id、time和parent_id。

void tf::TransformBroadcaster::sendTransform(const std::vector< StampedTransform > & transforms)

发送一个vector的StampedTransform,stamped数据结构包括frame_id、time和parent_id。

void tf::TransformBroadcaster::sendTransform(const geometry_msgs::TransformStamped & transform)

发送一个StampedTransform message,stamped数据结构包括frame_id、time和parent_id。

void tf::TransformBroadcaster::sendTransform(const std::vector< geometry_msgs::TransformStamped > & transforms)

发送一个vector的StampedTransform message,stamped数据结构包括frame_id、time和parent_id。

测试代码

// 创建tf的广播器static tf::TransformBroadcaster br;// 初始化tf数据tf::Transform transform;transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );tf::Quaternion q;q.setRPY(0, 0, msg->theta);transform.setRotation(q);// 广播world与海龟坐标系之间的tf数据br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));

运行结果

5. TransformListener

此类继承自Transformer,并自动订阅 ROS transform messages。

lookupTransform

函数功能:可以过得两个坐标系之间转换的关系,包括旋转与平移。

(1)这个lookupTransform()API,有六个参数:

  • 变换从坐标系turtle2
  • 到turtle1坐标系
  • 在now时间
  • 变换结果保存的变量

transformPoint

函数功能:这个在传感器数据的坐标变换中使用的比较多,用来将一个传感器的数据从一个坐标系转换到另外一个坐标系下。

waitForTransform

函数功能:等待变换在tf树中生效
(1) waitForTransform()有四个参数:

  • turtle2,父类坐标系
  • carrot1,子类坐标系
  • rospy.Time(),这个时间的变换,就是变换的时刻
  • rospy.Duration(4.0),等待最长的时间段

(2) waitForTransform() 实际上会阻塞,阻塞时间由第四个参数决定,直到两个坐标系变换开始。(通常是几毫秒)
(3) waitForTransform()常常会调用两次,一开始产生turtle2时候 ,在第一次调用waitForTransform()前,TF可能还没有更新。第一次调用waitForTransform()就会等待,直到/turtle2坐标系开始广播。第二次调用waitForTransform()用Now参数才能获取到值。

transfromPose

函数功能:转换位姿,将某位姿转换到指定坐标系下的位姿表示。
 

测试代码

int main(int argc, char** argv)
{// 初始化ROS节点ros::init(argc, argv, "my_tf_listener");// 创建节点句柄ros::NodeHandle node;// 请求产生turtle2ros::service::waitForService("/spawn");ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("/spawn");turtlesim::Spawn srv;add_turtle.call(srv);// 创建发布turtle2速度控制指令的发布者ros::Publisher turtle_vel = node.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel", 10);// 创建tf的监听器tf::TransformListener listener;ros::Rate rate(10.0);while (node.ok()){// 获取turtle1与turtle2坐标系之间的tf数据tf::StampedTransform transform;try{listener.waitForTransform("/turtle2", "/turtle1", ros::Time(0), ros::Duration(3.0));listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform);}catch (tf::TransformException &ex) {ROS_ERROR("%s",ex.what());ros::Duration(1.0).sleep();continue;}// 根据turtle1与turtle2坐标系之间的位置关系,发布turtle2的速度控制指令geometry_msgs::Twist vel_msg;vel_msg.angular.z = 4.0 * atan2(transform.getOrigin().y(),transform.getOrigin().x());vel_msg.linear.x = 0.5 * sqrt(pow(transform.getOrigin().x(), 2) +pow(transform.getOrigin().y(), 2));turtle_vel.publish(vel_msg);rate.sleep();}return 0;
};

运行结果
同上

6. Matrix3x3

它是一个3x3的矩阵,有3个Vector3 m_el[3],(此时使用的是行向量)

定义及赋值
//用四元数构造3x3旋转矩阵
Matrix3x3(const Quaternion& q)Matrix3x3(const tfScalar& xx, const tfScalar& xy, const tfScalar& xz,const tfScalar& yx, const tfScalar& yy, const tfScalar& yz,const tfScalar& zx, const tfScalar& zy, const tfScalar& zz)
//赋值构造函数
Matrix3x3 (const Matrix3x3& other)常用的函数
//四元数转旋转矩阵
void setRotation(const Quaternion& q)
//欧拉角 转旋转矩阵
setRPY(tfScalar roll, tfScalar pitch,tfScalar yaw)
//旋转矩阵转四元数
getRotation(Quaternion& q)
// 旋转矩阵转欧拉角
getRPY(tfScalar& roll, tfScalar& pitch, tfScalar& yaw, unsigned int solution_number = 1)
//矩阵转置
Matrix3x3 transpose() const;
//矩阵求逆
Matrix3x3 inverse() const;

还有如下函数操作
运算符重载=,*=

测试代码

inline void printQuaternion(const tf::Quaternion& q, const std::string& info)
{// std::cout << "quaternion: " << q[0] << ", " << q[1] << ", " << q[2] << ", " << q[3] << std::endl;  // x,y,z,wstd::cout << info << " quaternion: " << q.x() << ", " << q.y() << ", " << q.z() << ", " << q.w() << std::endl; // 两种元素访问方式均可
}inline void printMatrix(const tf::Matrix3x3& mat, const std::string info)
{std::cout << info << "-------------------- " << std::endl;std::cout << std::setprecision(4) << mat[0][0] << "\t" << mat[0][1] << "\t" << mat[0][2] << "\n"<< mat[1][0] << "\t" << mat[1][1] << "\t" << mat[1][2] << "\n"<< mat[2][0] << "\t" << mat[2][1] << "\t" << mat[2][2] << "\n\n";
}int main(int argc, char** argv)
{ros::init(argc, argv, "transform_ros");ros::NodeHandle nh;// 1, 旋转矩阵初始化方法:double theta = M_PI / 3.0;double ct = cos(theta), st = sin(theta);double roll = M_PI / 2.0, pitch = M_PI / 3.0, yaw = M_PI / 4.0;tf::Quaternion q(0, 0, 0, 1);  // x,y,z,w;tf::Matrix3x3 mat1(q);  // 从四元数初始化旋转矩阵, 内部通过 setRotation 实现;tf::Matrix3x3 mat2(ct, -st, 0, st, ct, 0, 0, 0, 1);  // 直接初始化;tf::Matrix3x3 mat3(mat2);tf::Matrix3x3 mat4 = mat3;tf::Matrix3x3 mat5;mat5.setRPY(roll, pitch, yaw);  // 欧拉角到旋转矩阵的变换, 内部直接调用 setEulerYPR(yaw, pitch, roll) 函数来实现;printMatrix(mat1, "mat1:");printMatrix(mat2, "mat2:");printMatrix(mat3, "mat3:");printMatrix(mat4, "mat4:");printMatrix(mat5, "mat5:");// 2, 旋转矩阵与其它旋转量间的转换:// rotation matrix --> euler:mat2.getRPY(roll, pitch, yaw, 1);  // 第四个参数 solution_number, 缺省时默认值为1;// 函数内部是直接使用 getEulerYPR(yaw, pitch, roll, solution_number); 方法实现的;std::cout << "rpy from mat2: " << roll << ", " << pitch << ", " << yaw << std::endl;mat5.getEulerYPR(yaw, pitch, roll);std::cout << "rpy from mat5(solution_number not set): " << roll << ", " << pitch << ", " << yaw << std::endl;mat5.getEulerYPR(yaw, pitch, roll, 1);std::cout << "rpy from mat5(solution_number = 1): " << roll << ", " << pitch << ", " << yaw << std::endl;mat5.getEulerYPR(yaw, pitch, roll, 0);  std::cout << "rpy from mat5(solution_number = 0): " << roll << ", " << pitch << ", " << yaw << std::endl;// 可以看到,solution_number 为0和1时得到的结果时不一样的,它设置为默认值1时的结果与原结果是一致的,所以强烈建议使用getRPY函数时,直接缺省第四个参数;// 其中的差别主要在于solution_number = 0时,对计算得到的pitch有:pitch = M_PI - pitch; 然后利用该pitch计算roll和yaw,使得最后的结果不一样;// rotation matrix --> quaternion:mat2.getRotation(q);printQuaternion(q, "quaternion from matrix mat2: ");// quaternion --> rotation matrix:mat1.setRotation(q);printMatrix(mat1, "\nmat from q(from mat2): ");// rotation matrix --> euler:// mat.getRPY(roll, pitch, yaw);// euler --> rotation matrix:// mat.setRPY(roll, pitch, yaw);// 3, 其它操作:tf::Vector3 vec1, vec2, vec3;vec1 = mat1[2];vec2 = mat1.getColumn(1);vec3 = mat2.getRow(0);std::cout << "get column of matrix[2]: " <<  vec1[0] << ", " << vec1[1] << ", " << vec1[2] << std::endl;std::cout << "get column of matrix[1]: " <<  vec2[0] << ", " << vec2[1] << ", " << vec2[2] << std::endl;std::cout << "get row of matrix[0]: " <<  vec3[0] << ", " << vec3[1] << ", " << vec3[2] << std::endl;return 0;
}

运行结果

7. MessageFilter

tf下的消息过滤器。
tf::MessageFilter可以订阅任何的ROS消息,然后将其缓存,直到这些消息可以转换到目标坐标系,然后进行相应的处理(一般在回调函数中处理)。说白了就是消息订阅+坐标转换。实际上,后者继承于前者:

  • 定义数据:TransformListener、message_filters::Subscriber、tf::MessageFilter
  • 用消息的名称来初始化message_filters::Subscriber
  • 用tf、message_filters::Subscriber、目标坐标系来初始化tf::MessageFilter
  • 给tf::MessageFilter注册callback。
  • 编写callback,并在回调中完成坐标转换。至此完成消息订阅+坐标转换

测试代码

message_filters::Subscriber<std_msgs::UInt32> sub(nh, "my_topic", 1);
sub.registerCallback(myCallback);

8. TimeCache

这个类构建并维护一个带有时间戳的数据列表。并提供查找函数,以根据时间的变化获取数据。

9. TransformException

TF异常
所有在TF里异常继承自tf::TransformException,它继承自std::runtime_error。

异常类型
tf::ConnectivityException
如果由于两个坐标系ID不在同一个连接的树中而无法完成请求,则抛出。

tf::ExtrapolationException
如果请求的坐标系id之间存在连接,但一个或多个变换已过期,则抛出。

tf::InvalidArgument
如果参数无效则抛出。 最常见的情况是非规范化的四元数。

tf::LookupException
如果引用了未发布的坐标系ID,则抛出。
 

测试代码

catch (tf::TransformException &ex) {ROS_ERROR("%s",ex.what());ros::Duration(1.0).sleep();continue;}

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

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

相关文章

RocketMQ 5.0:无状态代理模式的探索与实践

本文作者&#xff1a;金吉祥&#xff0c; Apache RocketMQ PMC Member&#xff0c;阿里云智能高级技术专家 背景 首先&#xff0c;让我们来看下是遇到了哪些痛点问题&#xff0c;促使我们去探索一种无状态代理的RocketMQ新架构的&#xff1b; RocketMQ 拥有一套极简的架构&am…

安卓投屏 QtScrcpy

一、电脑安装adb 版本大于1.0.40以上 40不行 adb 1.0.41下载链接 链接&#xff1a;https://pan.baidu.com/s/1WIPI-p7a4ErTLFYHaTC2kw?pwdadbt 提取码&#xff1a;adbt 安装参考 https://blog.csdn.net/M7_xbc/article/details/122957311 二、打开无线调试并且配对 手机打…

驱动开发(10/10-林雪阵)

终端输入1--->LED1点亮 终端输入2--->LED2点亮 终端输入3--->LED3点亮 终端输入0--->LED熄灭 chdev.c (底层驱动代码&#xff09; #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h>…

【webrtc】rtp 扩展头的ID

ietf 文档地址扩展头的uri 是固定的,因此识别扩展是通过uri地址,而非ID。rtp 扩展头是有个ID的 文档的说法 : 是本地的id,15保留,只可以小于15 本地标识符值 15 保留用于将来的扩展和 不得用作标识符。如果 ID 值 15 是 遇到,它的长度字段应该被忽略,处理 整个扩展应在该…

redis之AOF和RDB持久化

写在前面 因为redis数据是基于内存的&#xff0c;为了避免服务器重启或者是宕机导致数据全部丢失&#xff0c;提供了数据持久化机制&#xff0c;即AOF(Append Only File)日志和RDB快照&#xff0c;接下来我们分别看下。 1&#xff1a;AOF 1.1&#xff1a;AOF日志的实现 首先…

(附源码)计算机毕业设计SSM政府项目管理平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

回顾——PCB绘制

目录 一、原理图库原理图 二、PCB库 三、PCB 一、原理图库原理图 新建工程&#xff1a;文件——New——Project——Name&#xff08;这里复制一下&#xff09;自己选择保存路径 添加文件&#xff1a; 保存工程&#xff1a;(粘贴) 绘制原理图库、原理图 侧边栏消失&…

虚拟社会、区块链和元宇宙

1986年&#xff0c;早期的互联网供应商Quantum Link和娱乐公司Lucasfilm Games发布了第一款MMO游戏名为&#xff1a;《Habitat》基于虚拟角色的社交世界&#xff0c;玩家可以通过300波特的调制解调器(每分钟0.08美元)和用户的Commodore 64(595美元&#xff0c;按今天的价格约为…

Vue 动态换肤

效果如图&#xff1a; 源代码&#xff1a; <template><div :class"[son${temp}]" class"demo3">这是四点零八分的北京<br/>一片手的海浪翻动<br/>这是四点零八分的北京<br/>一声雄伟的汽笛长鸣<br/>北京车站高大的建…

Kafka监控EFAK(Kafka-eagle)部署与踩坑详细记录

环境 阿里云服务器centoskafka 2.4.1 &#xff08;1.1以上版本都能支持&#xff0c;低版本不太清楚了&#xff09;efak 3.0.1 当前时间官网下载的最新版&#xff08;原名叫kafka-eagle&#xff09;efak官网&#xff1a;http://www.kafka-eagle.org/jdk8 部署好的UI 部署流程 …

kafka系列——安装部署,相关命令,配置文件,底层存储结构,log和index文件

点击上方“罗晓胜”&#xff0c;马上关注&#xff0c;您的支持对我帮助很大 / 前言 / Kafka是最初由Linkedin公司开发&#xff0c;用scala语言编写的&#xff0c;是一个分布式、支持分区的&#xff08;partition&#xff09;、多副本的&#xff08;replica&#xff09;&…

时光机特效在哪里?推荐这三个实用软件给你

现如今有一款时光穿梭机的特效软件非常热门&#xff0c;它具有让照片中的人变年轻或者变老的功能&#xff0c;能让我们看到过去以及未来自己的样子&#xff0c;也能给我们枯燥的生活增添一点趣味感。那么大家是不是已经迫不及待地想知道时光穿梭机特效滤镜在哪了呢&#xff1f;…

Linux学习 -- docker的commit命令和本地镜像到云端

commit命令本地镜像到云端 一、commit命令 我们在使用镜像新建容器后&#xff0c;容器只是具有简易的Linux的功能&#xff0c;不具备一些常用的功能&#xff0c;如vim功能&#xff0c;因此我们需要给容器加上一些我们需要的功能。 使用如下命令&#xff1a; docker commit提交容…

不使用第三方库怎么实现【前端引导页】功能?

前言 随着应用功能越来越多&#xff0c;繁多而详细的功能使用和说明文档&#xff0c;已经不能满足时代追求 快速 的需求&#xff0c;而 引导页&#xff08;或分步引导&#xff09; 本质就是 化繁为简&#xff0c;将核心功能以更简单、简短、明了的文字指引用户去使用对应的功能…

Oracle数据库 | SQL语句解析

个人主页&#xff1a;&#x1f497;wei_shuo的个人主页 &#x1f3c0; Hello World &#xff01;&#x1f3c0; 文章目录一.Oracle启动及登录1.1 服务手启动即关闭1.2 SQL* PLUS命令二. 表的创建和维护2.1 创建表2.2 修改表2.3 重命名表2.4 截断表2.5 删除表三. 数据完整性与约…

【C++学习】类与对象(中)

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 类与对象&#xff08;中&#xff09;&#x1f638;构造函数&#x1f639;概念&#x1f639;特性&…

常见数据结构-散列表(上)理论

一&#xff0c;散列表理解 散列表的英文叫“Hash Table”&#xff0c;我们平时也叫它“哈希表”或者“Hash 表”&#xff0c;散列表用的是数组支持按照下标随机访问数据的特性&#xff0c;所以散列表其实就是数组的一种扩展&#xff0c;基于数组演化而来。 散列表是通过散列函…

bp神经网络performance怎么看,BP神经网络用什么软件

1、除了MATLAB能做BP神经网络&#xff0c;还有其他什么软件能做 除了MATLAB能做BP神经网络&#xff0c;还有其他什么软件能做 理论上编程语言都可以&#xff0c;比如VB&#xff0c;C语言&#xff0c;过程也都是建模、量化、运算及结果输出&#xff08;图、表&#xff09;&…

JavaScript设计模式(一):面向对象编程 - 继承

JavaScript设计模式 - 面向对象编程灵活的语言-JavaScript用对象收编变量对象的另一种形式(函数对象)真假对象(闭包和类)一个检测类函数的祖先写的都是看到的-面向对象编程创建一个类&#xff08;三种方式&#xff09;类的属性和方法通过闭包来实现类的静态变量定义&#xff0c…

二十一、JAVA调用存储过程(Oracle专栏)

2022年9月28日16:33:11目录 &#x1f3c6;一、存储过程的创建及调用 ⭐️1.1、PLSQL编程 ⭐️1.2、程序结构 ⭐️1.3、变量 1.3.1、普通变量 1.3.2、引用型变量 1.3.3、记录型变量 ⭐️1.4、流程控制 1.4.1、条件分支 1.4.2、循环 &#x1f3c6;二、游标 ⭐️2.1、…