ROS实践笔记14
ROS常用组件
在ROS中内置一些比较实用的工具,通过这些工具可以方便快捷的实现某个功能或调试程序,从而提高开发效率。
-
TF坐标变换,实现不同类型的坐标系之间的转换:
注意: ROS使用右手坐标系
tf2常用的功能包有:
- tf2_geometry_msgs:可以将ROS消息转换成tf2消息。
- tf2: 封装了坐标变换的常用消息。
- tf2_ros:为tf2提供了roscpp和rospy绑定,封装了坐标变换常用的API。
-
坐标msg消息
订阅发布模型中数据载体 msg 是一个重要实现,首先需要了解一下,在坐标转换实现中常用的 msg:geometry_msgs/TransformStamped和geometry_msgs/PointStamped
前者用于传输坐标系相关位置信息,后者用于传输某个坐标系内坐标点的信息。在坐标变换中,频繁的需要使用到坐标系的相对关系以及坐标点信息。
-
geometry_msgs/TransformStamped
打开终端输入:
rosmsg info geometry_msgs/TransformStamped
得到:
std_msgs/Header header uint32 seq //序列化号 time stamp //时间戳 string frame_id //主体坐标系名称 string child_frame_id //相对的坐标系 geometry_msgs/Transform transform geometry_msgs/Vector3 translation float64 x //相对坐标系在x,y,z上的偏移量 float64 y //对于主体坐标系而言 float64 z geometry_msgs/Quaternion rotation float64 x //在相对方向角度的偏移 float64 y //及偏移,巡航,俯仰角 float64 z float64 w
-
geometry_msgs/PointStamped
打开终端输入:rosmsg info geometry_msgs/PointStamped
得到:
std_msgs/Header header uint32 seq time stamp //时间戳 string frame_id //属于那个坐标系 geometry_msgs/Point point float64 x float64 y float64 z
-
-
静态坐标变换
-
创建功能包
需要的依赖 :tf2 tf2_ros tf2_geometry_msgs roscpp rospy std_msgs geometry_msgs
-
编码实现
发布方
c++
//实现静态坐标变换的发布方 //发布两个坐标系的相对关系 # 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,""); ros::init(argc,argv,"static_pub_c"); ros::NodeHandle nh; tf2_ros::StaticTransformBroadcaster pub; geometry_msgs::TransformStamped tfs; tfs.header.stamp = ros::Time::now(); tfs.header.frame_id = "car_link"; tfs.child_frame_id = "radar_link"; tfs.transform.translation.x = 0.2; tfs.transform.translation.y = 0.0; tfs.transform.translation.z = 0.5; tf2::Quaternion qtn; //创建四元数 //向该对象设置哦啦较,这个对象可以将哦啦较转换为四元数 qtn.setRPY(0,0,0); //欧拉角的单位是弧度 tfs.transform.rotation.x = qtn.getX(); tfs.transform.rotation.y = qtn.getY(); tfs.transform.rotation.z = qtn.getZ(); tfs.transform.rotation.w = qtn.getW(); pub.sendTransform(tfs); ros::spin(); return 0; }
python
#! /usr/bin/env python import rospy import tf2_ros import tf.transformations from geometry_msgs.msg import TransformStamped if __name__ == "__main__": rospy.init_node("static_pub_p") pub = tf2_ros.StaticTransformBroadcaster() ts = TransformStamped() ts.header.stamp = rospy.Time.now() ts.header.frame_id = "car_link" ts.child_frame_id = "radar_link" ts.transform.translation.x = 2.0 ts.transform.translation.y = 0.0 ts.transform.translation.z = 0.5 qtn = tf.transformations.quaternion_from_euler(0,0,0) ts.transform.rotation.x = qtn[0] ts.transform.rotation.y = qtn[1] ts.transform.rotation.z = qtn[2] ts.transform.rotation.w = qtn[3] pub.sendTransform(ts) rospy.spin()
订阅方
c++
# 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[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"static_sub_c"); ros::NodeHandle nh; tf2_ros::Buffer buffer; tf2_ros::TransformListener listener(buffer); geometry_msgs::PointStamped p; p.header.frame_id = "radar_link"; p.header.stamp = ros::Time::now(); p.point.x = 2.0; p.point.y = 3.0; p.point.z = 5.0; ros::Rate rate(1); while(ros::ok()) { geometry_msgs::PointStamped np; try { np = buffer.transform(p,"car_link"); ROS_INFO("转换后的坐标值:(%.2f %.2f %.2f)",np.point.x,np.point.y,np.point.z); ROS_INFO("相对的坐标系是:%s",np.header.frame_id.c_str()); } catch(const std::exception& e) { std::cerr << e.what() << '\n'; } rate.sleep(); ros::spinOnce(); } return 0; }
python
#! /usr/bin/env python import rospy import tf2_ros from tf2_geometry_msgs import tf2_geometry_msgs if __name__=="__main__": rospy.init_node("static_sub_p") # 创建缓存对象 buffer = tf2_ros.Buffer() # 将缓存对象导入订阅对象 sub = tf2_ros.TransformListener(buffer) p = tf2_geometry_msgs.PointStamped() p.header.stamp = rospy.Time.now() p.header.frame_id = "radar_link" p.point.x = 2.0 p.point.y = 3.0 p.point.z = 5.0 rate = rospy.Rate(1) while not rospy.is_shutdown(): try: np=buffer.transform(p,"car_link") rospy.loginfo("转换后的坐标为(%.2f,%.2f,%.2f)",np.point.x,np.point.y,np.point.z) rospy.loginfo("对应的坐标系为:%s",np.header.frame_id) except Exception as e: rospy.logerr("未找到坐标") rate.sleep()
-
通过命令行命令可以直观看到坐标系相对位置
rviz
-
-
动态坐标变换
启动 turtlesim_node,该节点中窗体有一个世界坐标系(左下角为坐标系原点),乌龟是另一个坐标系,键盘控制乌龟运动,将两个坐标系的相对位置动态发布。
功能包依赖:
tf2 tf2_ros tf2_geometry_msgs roscpp rospy std_msgs geometry_msgs turtlesim
发布方:
c++
# 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) { static tf2_ros::TransformBroadcaster pub; geometry_msgs::TransformStamped ts; ts.header.frame_id = "world"; ts.header.stamp = ros::Time::now(); ts.child_frame_id = "turtle"; ts.transform.translation.x = pose->x; ts.transform.translation.y = pose->y; ts.transform.translation.z = 0.0; /* 由于乌龟不能沿x轴y轴转动,所以可以直接通过乌龟位姿得到欧拉角 0 0 theta */ tf2::Quaternion qtn; qtn.setRPY(0,0,pose->theta); ts.transform.rotation.x=qtn.getX(); ts.transform.rotation.y=qtn.getY(); ts.transform.rotation.z=qtn.getZ(); ts.transform.rotation.w=qtn.getW(); pub.sendTransform(ts); } int main(int argc, char *argv[]) { ros::init(argc,argv,"dynamic_pub_c"); ros::NodeHandle nh; ros::Rate rate(10); ros::Subscriber sub=nh.subscribe("/turtle1/pose",100,DoPose); rate.sleep(); ros::spin(); return 0; }
python
#! /usr/bin/env python import rospy import tf2_ros import tf.transformations from turtlesim.msg import Pose from geometry_msgs.msg import TransformStamped def DoPose(pose): pub = tf2_ros.TransformBroadcaster() ts = TransformStamped() ts.header.stamp = rospy.Time.now() ts.header.frame_id = "world" ts.child_frame_id = "turtle" ts.transform.translation.x = pose.x ts.transform.translation.y = pose.y ts.transform.translation.z = 0 qtn = tf.transformations.quaternion_from_euler(0,0,pose.theta) ts.transform.rotation.x = qtn[0] ts.transform.rotation.y = qtn[1] ts.transform.rotation.z = qtn[2] ts.transform.rotation.w = qtn[3] pub.sendTransform(ts) if __name__ == "__main__" : rospy.init_node("dynamic_pub_p") rate = rospy.Rate(10) sub = rospy.Subscriber("/turtle1/pose",Pose,DoPose,queue_size = 100) rate.sleep() rospy.spin()
订阅方:
c++
# 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[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"dynamic_sub_c"); ros::NodeHandle nh; tf2_ros::Buffer buffer; tf2_ros::TransformListener listener(buffer); geometry_msgs::PointStamped p; p.header.frame_id = "turtle"; //防止时间不同,发生异常,需要时间初始化为0 p.header.stamp = ros::Time(0.0); p.point.x = 0.0; p.point.y = 0.0; p.point.z = 0.0; ros::Rate rate(1); while(ros::ok()) { geometry_msgs::PointStamped np; try { np = buffer.transform(p,"world"); ROS_INFO("转换后的坐标值:(%.2f %.2f %.2f)",np.point.x,np.point.y,np.point.z); ROS_INFO("相对的坐标系是:%s",np.header.frame_id.c_str()); } catch(const std::exception& e) { std::cerr << e.what() << '\n'; } rate.sleep(); ros::spinOnce(); } return 0; }
python
#! /usr/bin/env python import rospy import tf2_ros from tf2_geometry_msgs import tf2_geometry_msgs if __name__=="__main__": rospy.init_node("dynamic_sub_p") # 创建缓存对象 buffer = tf2_ros.Buffer() # 将缓存对象导入订阅对象 sub = tf2_ros.TransformListener(buffer) p = tf2_geometry_msgs.PointStamped() p.header.stamp = rospy.Time() p.header.frame_id = "turtle" p.point.x = 0.0 p.point.y = 0.0 p.point.z = 0.0 rate = rospy.Rate(10) while not rospy.is_shutdown(): try: np=buffer.transform(p,"world") rospy.loginfo("转换后的坐标为(%.2f,%.2f,%.2f)",np.point.x,np.point.y,np.point.z) rospy.loginfo("对应的坐标系为:%s",np.header.frame_id) except Exception as e: rospy.logerr("未找到坐标") rate.sleep()
-
多坐标变换
现有坐标系统,父级坐标系统 world,下有两子级系统 son1,son2,son1 相对于 world,以及 son2 相对于 world 的关系是已知的,求 son1原点在 son2中的坐标,又已知在 son1中一点的坐标,求出该点在 son2 中的坐标。
- 通过launch发布坐标关系
<launch> <node pkg = "tf2_ros" type = "static_transform_publisher" name = "son1" args = "5 0 0 0 0 0 /world /son1" output = "screen"/> <node pkg = "tf2_ros" type = "static_transform_publisher" name = "son2" args = "0 0 7 0 0 0 /world /son2" output = "screen"/> </launch>
-
订阅方编写
c++
# 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" # include"geometry_msgs/TransformStamped.h" int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"many_sub_c"); ros::NodeHandle nh; tf2_ros::Buffer buffer; tf2_ros::TransformListener listener(buffer); geometry_msgs::PointStamped p; p.header.frame_id = "son1"; p.header.stamp = ros::Time::now(); p.point.x = 1.0; p.point.y = 2.0; p.point.z = 5.0; ros::Rate rate(1); while(ros::ok()) { geometry_msgs::PointStamped np; try { //坐标系转换 //time(0)取发布间隔时间最短的两个坐标方位 geometry_msgs::TransformStamped tfs = buffer.lookupTransform("son2","son1",ros::Time(0)); ROS_INFO("主体坐标系:%s",tfs.header.frame_id.c_str()); ROS_INFO("相对坐标系:%s",tfs.child_frame_id.c_str()); ROS_INFO("偏移量:(%.2f,%.2f,%.2f)",tfs.transform.translation.x,tfs.transform.translation.y,tfs.transform.translation.z); //坐标点转换 np = buffer.transform(p,"son2"); ROS_INFO("son1坐标点在son2中的值(%.2f,%.2f,%.2f)",np.point.x,np.point.y,np.point.z); } catch(const std::exception& e) { std::cerr << e.what() << '\n'; } rate.sleep(); } return 0; }
python
#! /usr/bin/env python import rospy import tf2_ros from tf2_geometry_msgs import tf2_geometry_msgs from geometry_msgs.msg import TransformStamped if __name__=="__main__": rospy.init_node("many_sub_p") # 创建缓存对象 buffer = tf2_ros.Buffer() # 将缓存对象导入订阅对象 sub = tf2_ros.TransformListener(buffer) p = tf2_geometry_msgs.PointStamped() p.header.stamp = rospy.Time.now() p.header.frame_id = "son1" p.point.x = 2.0 p.point.y = 3.0 p.point.z = 5.0 rate = rospy.Rate(1) while not rospy.is_shutdown(): try: # 坐标系转换 tfs=buffer.lookup_transform("son2","son1",rospy.Time(0)) rospy.loginfo("主体坐标系:%s",tfs.header.frame_id) rospy.loginfo("相对坐标系:%s",tfs.child_frame_id) rospy.loginfo("偏移量:(%.2f,%.2f,%.2f)",tfs.transform.translation.x,tfs.transform.translation.y,tfs.transform.translation.z) # 坐标点转换 np=buffer.transform(p,"son2") rospy.loginfo("转换后的坐标为(%.2f,%.2f,%.2f)",np.point.x,np.point.y,np.point.z) rospy.loginfo("对应的坐标系为:%s",np.header.frame_id) except Exception as e: rospy.logerr("未找到坐标") rate.sleep()
-
坐标系关系查看
在机器人系统中,涉及的坐标系有多个,为了方便查看,ros 提供了专门的工具,可以用于生成显示坐标系关系的 pdf 文件,该文件包含树形结构的坐标系图谱。
-
准备
首先调用rospack find tf2_tools查看是否包含该功能包,如果没有,请使用如下命令安装:sudo apt install ros-noetic-tf2-tools
-
使用
-
生成 pdf 文件
启动坐标系广播程序之后,运行如下命令:rosrun tf2_tools view_frames.py
-
查看文件
可以直接进入目录打开文件,或者调用命令查看文件:evince frames.pdf
-
-
-
通过命令行发布坐标系关系
语法:rosrun tf2_ros static_transform_publisher x偏移量 y偏移量 z偏移量 z偏航角度 y俯仰角度 x翻滚角度 父级坐标系 子级坐标系
rosrun tf2_ros static_transform_publisher 0.2 0 0.5 0 0 0 /baselink /laser
-
rosbag 用于录制ROS节点的执行过程并可以回放该过程:
录制:把数据储存进磁盘文件
回放:从磁盘文件中读取数据
-
命令行使用
录制
rosbag record -a -o turtle.bag
查看
rosbag info turtle_2022-02-20-16-26-26.bag
回放
rosbag play turtle_2022-02-20-16-26-26.bag
-
编码使用
录制
c++
# include"ros/ros.h" # include"rosbag/bag.h" # include"std_msgs/String.h" std_msgs::String msg; void DoMsg(const std_msgs::String::ConstPtr &Msg) { msg.data = Msg->data; return ; } int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"writing_c"); ros::NodeHandle nh; rosbag::Bag bag; //打开文件流 //Append 追加 Read 读 Write 覆盖 bag.open("turtle.bag",rosbag::BagMode::Write); ros::Subscriber sub = nh.subscribe("topic",10,DoMsg); bag.write("/topic",ros::Time::now(),msg); bag.close(); ros::spinOnce(); return 0; }
python
#! /usr/bin/env python import rospy import rosbag from std_msgs.msg import String if __name__ == "__main__": rospy.init_node("writing_p") bag = rosbag.Bag("turtle2.bag","w") msg = String() msg.data = "hello" bag.write("topic",msg) bag.close()
回放
c++
# include"ros/ros.h" # include"rosbag/bag.h" # include"rosbag/view.h" # include"std_msgs/String.h" int main(int argc, char *argv[]) { setlocale(LC_ALL,""); ros::init(argc,argv,"read_c"); ros::NodeHandle nh; rosbag::Bag bag; bag.open("turtle.bag",rosbag::BagMode::Read); for(auto &&p : rosbag::View(bag)) { //View(bag) 消息的集合 //解析 std::string str = p.getTopic(); ros::Time time = p.getTime(); auto m = p.instantiate<std_msgs::String>(); ROS_INFO("解析的内容,话题:%s,时间戳:%.2f,内容:%s",str.c_str(),time.toSec(),m->data.c_str()); } bag.close(); return 0; }
python
#! /usr/bin/env python import rospy import rosbag from std_msgs.msg import String if __name__ == "__main__": rospy.init_node("read_p") bag = rosbag.Bag("turtle2.bag","r") mags = bag.read_messages("topic") for topic,msg,time in mags: rospy.loginfo("话题 %s 消息 %s 时间 %s",topic,msg.data,time) bag.close()
-
-
rqt 工具箱,集成了多款图形化的调试工具:
之前,在 ROS 中使用了一些实用的工具,比如: ros_bag 用于录制与回放、tf2_tools 可以生成 TF 树 ..... 这些工具大大提高了开发的便利性,但是也存在一些问题: 这些工具的启动和使用过程中涉及到一些命令操作,应用起来不够方便,在ROS中,提供了rqt工具箱,在调用工具时以图形化操作代替了命令操作,应用更便利,提高了操作效率,优化了用户体验。
-
rqt安装启动与基本使用
一般只要你安装的是desktop-full版本就会自带工具箱
如果需要安装可以以如下方式安装
$ sudo apt-get install ros-noetic-rqt $ sudo apt-get install ros-noetic-rqt-common-plugins
启动
两个方式:
rqt rosrun rqt_gui rqt_gui
基本使用
启动 rqt 之后,可以通过 plugins 添加所需的插件
-
rqt常用插件:rqt_graph
rqt_graph可视化显示计算图
启动:可以在 rqt 的 plugins 中添加,或者使用rqt_graph启动
圆形是节点,方形是话题
-
rqt常用插件:rqt_console
rqt_console 是 ROS 中用于显示和过滤日志的图形化插件
可以在 rqt 的 plugins 中添加,或者使用rqt_console启动
-
rqt常用插件:rqt_plot
图形绘制插件,可以以 2D 绘图的方式绘制发布在 topic 上的数据
可以在 rqt 的 plugins 中添加,或者使用rqt_plot启动
-
rqt常用插件:rqt_bag
录制和重放 bag 文件的图形化插件
可以在 rqt 的 plugins 中添加,或者使用rqt_bag启动
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)