当前位置:   article > 正文

ROS学习之路——第八章:tf坐标变换_rostf轨迹转换

rostf轨迹转换

translation:平移量         rotation:翻转量

一、静态坐标变换

1.概述

两个坐标系之间的相对位置是固定的,知道主体的坐标和雷达的坐标,通过雷达感知可以推算出主体距离障碍物的距离是多少

2.发布方发布

  1. #include "ros/ros.h"
  2. #include "tf2_ros/static_transform_broadcaster.h" //静态的依赖文件
  3. #include "geometry_msgs/TransformStamped.h" //用于实现坐标转换
  4. #include "tf2/LinearMath/Quaternion.h" //数据处理用的文件
  5. /*
  6. 发布两个坐标的相对关系
  7. */
  8. int main(int argc, char *argv[])
  9. {
  10. /* code */
  11. setlocale(LC_ALL,"");
  12. ros::init(argc, argv, "static_pub");
  13. ros::NodeHandle nh;
  14. //创建发布对象
  15. tf2_ros::StaticTransformBroadcaster pub;
  16. geometry_msgs::TransformStamped tfs;
  17. //stamp指时间戳,now可以获取当前时刻
  18. tfs.header.stamp = ros::Time::now();
  19. //frame指的是相对坐标系中被参考的那一个
  20. tfs.header.frame_id = "base";
  21. //child_frame表示雷达的坐标系
  22. tfs.child_frame_id="laser";
  23. //偏移量
  24. tfs.transform.translation.x=0.2;
  25. tfs.transform.translation.y=0.0;
  26. tfs.transform.translation.z=0.5;
  27. //四元数根据欧拉角来转换
  28. tf2::Quaternion qtn; //创建四元数对象
  29. //向该对象设置欧拉角
  30. qtn.setRPY(0,0,0); //单位是弧度!
  31. tfs.transform.rotation.x=qtn.getX();
  32. tfs.transform.rotation.y=qtn.getY();
  33. tfs.transform.rotation.z=qtn.getZ();
  34. tfs.transform.rotation.w=qtn.getW();
  35. //发布数据
  36. pub.sendTransform(tfs);
  37. ros::spin();
  38. return 0;
  39. }

3.订阅方订阅(tf算法实现坐标变换)

  1. #include "ros/ros.h"
  2. #include "tf2_ros/transform_listener.h" //创建订阅对象(监听)
  3. #include "tf2_ros/buffer.h" //将订阅到的数据存到buffer中
  4. #include "geometry_msgs/PointStamped.h" //组织一个坐标点数据
  5. #include "tf2_geometry_msgs/tf2_geometry_msgs.h" //tf自己封装的消息
  6. int main(int argc, char *argv[])
  7. {
  8. /* code */
  9. setlocale(LC_ALL,"");
  10. ros::init(argc, argv, "static_sub");
  11. ros::NodeHandle nh; //必须创建,内部函数需要用到
  12. //创建订阅对象(需要对象+缓存)
  13. //创建buffer缓存
  14. tf2_ros::Buffer buffer;
  15. //创建监听对象
  16. tf2_ros::TransformListener listener(buffer);
  17. //创建坐标点对象
  18. geometry_msgs::PointStamped ps;
  19. ps.header.frame_id = "laser";
  20. ps.header.stamp = ros::Time::now();
  21. ps.point.x=2.0;
  22. ps.point.y=3.0;
  23. ps.point.z=5.0;
  24. //添加休眠:以便坐标点被订阅方订阅(这个不加会报错!)
  25. ros::Duration(2).sleep();
  26. //转换算法,需要调用TF内置实现
  27. ros::Rate rate(10); //发布频率10Hz
  28. while(ros::ok())
  29. {
  30. //将ps转换为base(pub中)的坐标点
  31. geometry_msgs::PointStamped ps_out; //输出的坐标
  32. /*buffer.transform()
  33. 第一个参数为被转化的坐标,
  34. 第二个参数为视为参考点的名称
  35. 返回值是转化后的坐标
  36. */
  37. ps_out=buffer.transform(ps,"base");
  38. ROS_INFO("参考坐标系为:%s 转化后的坐标值:%.2f, %.2f, %.2f",
  39. ps_out.header.frame_id.c_str(),
  40. ps_out.point.x,
  41. ps_out.point.y,
  42. ps_out.point.z);
  43. rate.sleep();
  44. ros::spinOnce();
  45. }
  46. return 0;
  47. }
  48. //调用时必须包含头文件 tf2_geometry_msgs/tf2_geometry_msgs.h

4.可能会遇到的问题

(1)由于发布的坐标点不能马上被订阅方订阅到,所以在运行发布方后,可能会报错,因此我们需要再发布完坐标点后,让发布方休眠一段时间。

二、动态坐标变换

1.概述

获取动态的相对坐标

2.坐标发布方(同时需要订阅乌龟位姿信息)

  1. #include "ros/ros.h"
  2. #include "turtlesim/Pose.h"
  3. #include "tf2_ros/transform_broadcaster.h" //用于发布动态坐标消息
  4. #include "geometry_msgs/TransformStamped.h"
  5. #include "tf2/LinearMath/Quaternion.h" //欧拉角
  6. /*
  7. 订阅乌龟的位姿信息,转换成相对窗体的坐标
  8. 准备
  9. 话题:/turtle1/pose
  10. 消息:/turtlesim/Pose
  11. */
  12. void PoseCallback(const turtlesim::Pose::ConstPtr& pose)
  13. {
  14. //获取位姿信息,转化为坐标系相对关系并发布
  15. //创建TF发布对象
  16. static tf2_ros::TransformBroadcaster pub; //加static是为了防止每次进入回调函数都要重新定义一次
  17. //创建发布数据:从参数pose中进行转换
  18. geometry_msgs::TransformStamped ts;
  19. ts.header.frame_id="world"; //参考的坐标系(名字是固定的)
  20. ts.header.stamp=ros::Time::now();
  21. ts.child_frame_id="turtle1"; //相对的坐标系(名字是固定的)
  22. //坐标系平移量的设置
  23. ts.transform.translation.x = pose->x; //这些值都可以通过rosmsg info turtlesim/Pose来查看的
  24. ts.transform.translation.y = pose->y;
  25. ts.transform.translation.z = 0;
  26. //坐标系四元数
  27. //乌龟是2D的,因此没有翻滚和俯仰角,因此乌龟的欧拉角为0,0,theta
  28. tf2::Quaternion qtn;
  29. qtn.setRPY(0,0,pose->theta);
  30. //设置四元数
  31. ts.transform.rotation.x=qtn.getX();
  32. ts.transform.rotation.y=qtn.getY();
  33. ts.transform.rotation.z=qtn.getZ();
  34. ts.transform.rotation.w=qtn.getW();
  35. //发布
  36. pub.sendTransform(ts);
  37. }
  38. int main(int argc, char *argv[])
  39. {
  40. /* code */
  41. setlocale(LC_ALL,"");
  42. ros::init(argc, argv, "dynamic_pub");
  43. ros::NodeHandle nh;
  44. //参数一:话题名称 参数二:队列长度 参数三:回调函数
  45. ros::Subscriber sub=nh.subscribe("turtle1/pose",100,PoseCallback);
  46. ros::spin();
  47. return 0;
  48. }

3.坐标订阅方

  1. #include "ros/ros.h"
  2. #include "tf2_ros/transform_listener.h" //创建订阅对象(监听)
  3. #include "tf2_ros/buffer.h" //将订阅到的数据存到buffer中
  4. #include "geometry_msgs/PointStamped.h" //组织一个坐标点数据
  5. #include "tf2_geometry_msgs/tf2_geometry_msgs.h" //tf自己封装的消息
  6. int main(int argc, char *argv[])
  7. {
  8. /* code */
  9. setlocale(LC_ALL,"");
  10. ros::init(argc, argv, "static_sub");
  11. ros::NodeHandle nh; //必须创建,内部函数需要用到
  12. //创建订阅对象(需要对象+缓存)
  13. //创建buffer缓存
  14. tf2_ros::Buffer buffer;
  15. //创建监听对象
  16. tf2_ros::TransformListener listener(buffer);
  17. //创建坐标点对象
  18. geometry_msgs::PointStamped ps;
  19. //参考的坐标系
  20. ps.header.frame_id = "turtle1";
  21. //时间戳
  22. ps.header.stamp = ros::Time(0.0); //注意,发布动态消息时不可以发布实时的时间戳(不然会报错)
  23. ps.point.x=2.0;
  24. ps.point.y=3.0;
  25. ps.point.z=5.0;
  26. //添加休眠:以便坐标点被订阅方订阅(这个不加会报错!)
  27. ros::Duration(2).sleep();
  28. //转换算法,需要调用TF内置实现
  29. ros::Rate rate(10); //发布频率10Hz
  30. while(ros::ok())
  31. {
  32. //将ps转换为base(pub中)的坐标点
  33. geometry_msgs::PointStamped ps_out; //输出的坐标
  34. /*buffer.transform()
  35. 第一个参数为被转化的坐标,
  36. 第二个参数为视为参考点的名称
  37. 返回值是转化后的坐标
  38. */
  39. ps_out=buffer.transform(ps,"world");
  40. ROS_INFO("参考坐标系为:%s 转化后的坐标值:%.2f, %.2f, %.2f",
  41. ps_out.header.frame_id.c_str(),
  42. ps_out.point.x,
  43. ps_out.point.y,
  44. ps_out.point.z);
  45. rate.sleep();
  46. ros::spinOnce();
  47. }
  48. return 0;
  49. }
  50. //调用时必须包含头文件 tf2_geometry_msgs/tf2_geometry_msgs.h

4.可能会遇到的问题

(1)时间戳不可以实时发布

三、多坐标变换

1.概述

父级坐标world下,有两个子级坐标,已知两个子级坐标相对于world的相对坐标,求一个子级相对于另一个子级的坐标

2.发布方

我们采用.launch文件的形式,直接发布对象

  1. <launch>
  2. <!--发布son1相对于world 以及 son2相对于world的坐标关系-->
  3. <node pkg="tf2_ros" type="static_transform_publisher" name="son1" args="5 0 0 0 0 0 /world /son1" output="screen"/>
  4. <node pkg="tf2_ros" type="static_transform_publisher" name="son2" args="3 0 0 0 0 0 /world /son2" output="screen"/>
  5. </launch>

该情况下,我们发布的

son1坐标为(5,0,0),欧拉角为(0,0,0)

son2坐标为(3,0,0),欧拉角为(0,0,0)

使用时,直接启动laun文件即可

3.订阅方

  1. #include "ros/ros.h"
  2. #include "tf2_ros/transform_listener.h"
  3. #include "tf2_ros/buffer.h"
  4. #include "geometry_msgs/PoseStamped.h"
  5. #include "tf2_geometry_msgs/tf2_geometry_msgs.h"
  6. #include "geometry_msgs/TransformStamped.h"
  7. /*
  8. 1.计算son1与son2的相对关系 2.计算son1的某个坐标点在son2中的坐标值
  9. */
  10. int main(int argc, char *argv[])
  11. {
  12. /* code */
  13. setlocale(LC_ALL,"");
  14. ros::init(argc, argv, "turtles");
  15. ros::NodeHandle nh;
  16. //创建订阅对象
  17. tf2_ros::Buffer buffer;
  18. tf2_ros::TransformListener sub(buffer);
  19. //编写解析逻辑
  20. ros::Rate rate(10);
  21. //创建相对于son1的坐标点
  22. geometry_msgs::PointStamped psson1;
  23. psson1.header.stamp =ros::Time::now();
  24. psson1.header.frame_id="son1";
  25. psson1.point.x=1.0;
  26. psson1.point.y=2.0;
  27. psson1.point.z=3.0;
  28. while(ros::ok())
  29. {
  30. try //可以避免因为发布订阅不同步造成的报错
  31. {
  32. //计算son1与son2的相对关系
  33. /*buffer.lookupTransform()
  34. 参数1:根据该坐标系进行转化(父级)
  35. 参数2:原坐标系(子级)
  36. 参数3:何时的相对坐标
  37. 返回值:geometry_msgs::TransformStamped 子级相当于父级的相对坐标关系
  38. */
  39. geometry_msgs::TransformStamped son1toson2 = buffer.lookupTransform("son2","son1",ros::Time(0));
  40. ROS_INFO("son1 相对于 son2 的信息:父级%s 子级%s // 偏移量:%.2f, %.2f, %.2f",
  41. son1toson2.header.frame_id.c_str(),
  42. son1toson2.child_frame_id.c_str(),
  43. son1toson2.transform.translation.x,
  44. son1toson2.transform.translation.y,
  45. son1toson2.transform.translation.z);
  46. //计算son1中的某个坐标点在son2中的坐标值
  47. //创建相对于son2坐标点,然后将son1的转化为相对于son2的
  48. geometry_msgs::PointStamped psson2 = buffer.transform(psson1,"son2");
  49. ROS_INFO("son1坐标点在son2中的值: %.2f, %.2f, %.2f",
  50. psson2.point.x, psson2.point.y, psson2.point.z);
  51. }
  52. catch(const std::exception& e)
  53. {
  54. ROS_INFO("错误提示:%s",e.what());
  55. }
  56. rate.sleep();
  57. ros::spinOnce();
  58. }
  59. return 0;
  60. }

四、坐标系关系查看

1.rviz图像化显示

可以得到如下界面

 2.tf2_tools 功能包

(1)安装:

roslin@roslin-VirtualBox:~$ sudo apt install ros-noetic-tf2-tools

(2)启动完程序后生成pdf文件:

roslin@roslin-VirtualBox:~$ rosrun tf2_tools view_frames.py

(3)终端中打开pdf文件:

 roslin@roslin-VirtualBox:~$ evince frames.pdf

 得到如下界面:

 

 五、TF坐标变换实操

1.概述:

实时将动态的相对坐标信息发送给观察者,观察者根据实时更新的坐标信息,匹配到相对应的速度信息,进而实现有效跟随。

2.生成一只新的乌龟:

  1. #include "ros/ros.h"
  2. #include "turtlesim/Spawn.h"
  3. int main(int argc, char *argv[])
  4. {
  5. /* code */
  6. ros::init(argc,argv,"newturtle");
  7. ros::NodeHandle nh;
  8. ros::service::waitForService("/spawn"); //等待发现/spawn服务
  9. ros::ServiceClient add_turtle = nh.serviceClient<turtlesim::Spawn>("/spawn");
  10. turtlesim::Spawn srv;
  11. srv.request.x=2.0;
  12. srv.request.y=2.0;
  13. srv.request.name="turtle2";
  14. //请求服务调用
  15. ROS_INFO("Call service to spawn turtle[x:%0.6f, y:%0.6f, name:%s]",
  16. srv.request.x, srv.request.y, srv.request.name);
  17. add_turtle.call(srv);
  18. //显示服务调用结果
  19. ROS_INFO("Spawn turtle successfully [name:%s]",srv.response.name.c_str());
  20. return 0;
  21. }

3.发布方(发布两只乌龟的坐标信息)

  1. #include "ros/ros.h"
  2. #include "turtlesim/Pose.h"
  3. #include "tf2_ros/transform_broadcaster.h" //用于发布动态坐标消息
  4. #include "geometry_msgs/TransformStamped.h"
  5. #include "tf2/LinearMath/Quaternion.h" //欧拉角
  6. /*
  7. 订阅乌龟的位姿信息,转换成相对窗体的坐标
  8. 准备
  9. 话题:/turtle1/pose
  10. 消息:/turtlesim/Pose
  11. */
  12. //声明变量,接收传递的参数
  13. std::string turtle_name;
  14. void PoseCallback(const turtlesim::Pose::ConstPtr& pose)
  15. {
  16. //获取位姿信息,转化为坐标系相对关系并发布
  17. //创建TF发布对象
  18. static tf2_ros::TransformBroadcaster pub; //加static是为了防止每次进入回调函数都要重新定义一次
  19. //创建发布数据:从参数pose中进行转换
  20. geometry_msgs::TransformStamped ts;
  21. ts.header.frame_id="world"; //参考的坐标系(名字是固定的)
  22. ts.header.stamp=ros::Time::now();
  23. //=====turtle1和turtle2是动态传入的====
  24. ts.child_frame_id=turtle_name; //相对的坐标系(名字是固定的)
  25. //坐标系平移量的设置
  26. ts.transform.translation.x = pose->x; //这些值都可以通过rosmsg info turtlesim/Pose来查看的
  27. ts.transform.translation.y = pose->y;
  28. ts.transform.translation.z = 0;
  29. //坐标系四元数
  30. //乌龟是2D的,因此没有翻滚和俯仰角,因此乌龟的欧拉角为0,0,theta
  31. tf2::Quaternion qtn;
  32. qtn.setRPY(0,0,pose->theta);
  33. //设置四元数
  34. ts.transform.rotation.x=qtn.getX();
  35. ts.transform.rotation.y=qtn.getY();
  36. ts.transform.rotation.z=qtn.getZ();
  37. ts.transform.rotation.w=qtn.getW();
  38. //发布
  39. pub.sendTransform(ts);
  40. }
  41. int main(int argc, char *argv[])
  42. {
  43. setlocale(LC_ALL,"");
  44. ros::init(argc, argv, "dynamic_pub");
  45. ros::NodeHandle nh;
  46. /*
  47. 要想动态传入turtle1和turtle2,需要解析launch文件
  48. */
  49. if(argc !=2 )
  50. {
  51. ROS_ERROR("请传入一个参数");
  52. return 1;
  53. }
  54. else
  55. {
  56. turtle_name = argv[1];
  57. }
  58. //创建订阅对象
  59. //====turtle1和turtle2是动态传入的====
  60. //参数一:话题名称 参数二:队列长度 参数三:回调函数
  61. //注意:动态传入话题时,pose前要加/ !!!
  62. ros::Subscriber sub=nh.subscribe(turtle_name+"/pose",100,PoseCallback);
  63. ros::spin();
  64. return 0;
  65. }

4.控制乌龟跟随

  1. #include "ros/ros.h"
  2. #include "tf2_ros/transform_listener.h"
  3. #include "tf2_ros/buffer.h"
  4. #include "geometry_msgs/PoseStamped.h"
  5. #include "tf2_geometry_msgs/tf2_geometry_msgs.h"
  6. #include "geometry_msgs/TransformStamped.h"
  7. #include "geometry_msgs/Twist.h"
  8. /*
  9. 1.换算turtle1相对于turtle2的关系 2.计算角速度和线速度并发布
  10. */
  11. int main(int argc, char *argv[])
  12. {
  13. /* code */
  14. setlocale(LC_ALL,"");
  15. ros::init(argc, argv, "turtles");
  16. ros::NodeHandle nh;
  17. //创建订阅对象
  18. tf2_ros::Buffer buffer;
  19. tf2_ros::TransformListener sub(buffer);
  20. //创建发布对象
  21. ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel",100);
  22. //编写解析逻辑
  23. ros::Rate rate(10);
  24. while(ros::ok())
  25. {
  26. try //可以避免因为发布订阅不同步造成的报错
  27. {
  28. //计算son1与son2的相对关系
  29. /*buffer.lookupTransform()
  30. 参数1:根据该坐标系进行转化(父级)
  31. 参数2:原坐标系(子级)
  32. 参数3:何时的相对坐标
  33. 返回值:geometry_msgs::TransformStamped 子级相当于父级的相对坐标关系
  34. */
  35. geometry_msgs::TransformStamped son1toson2 = buffer.lookupTransform("turtle2","turtle1",ros::Time(0));
  36. ROS_INFO("turtle1 相对于 turtle2 的信息:父级%s 子级%s // 偏移量:%.2f, %.2f, %.2f",
  37. son1toson2.header.frame_id.c_str(),
  38. son1toson2.child_frame_id.c_str(),
  39. son1toson2.transform.translation.x,
  40. son1toson2.transform.translation.y,
  41. son1toson2.transform.translation.z);
  42. //根据相对坐标计算并组织速度消息
  43. geometry_msgs::Twist twist;
  44. /*
  45. twist.linear.x = 系数*开二次方(x^2+y^2)
  46. twist.angular.z = 系数*反正切(对边/邻边)
  47. */
  48. twist.linear.x = 0.5 * sqrt(pow(son1toson2.transform.translation.x,2)+pow(son1toson2.transform.translation.y,2));
  49. twist.angular.z = 4*atan2(son1toson2.transform.translation.y,son1toson2.transform.translation.x);
  50. //发布
  51. pub.publish(twist);
  52. }
  53. catch(const std::exception& e)
  54. {
  55. ROS_INFO("错误提示:%s",e.what());
  56. }
  57. rate.sleep();
  58. ros::spinOnce();
  59. }
  60. return 0;
  61. }

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号