ROS2入门:常用工具
一、坐标变换TF
在机器人系统中,坐标变换是必不可少的,比如把相机坐标系下的点转换到机器人底盘坐标系下。ROS2 提供了tf2
和tf2_ros
用于管理和广播坐标变换。
1. 基本概念
Frame
(坐标系):如 base_link
(机器人底盘)、camera_link
(相机)、odom
(里程计)。
Transform
(变换):描述两个坐标系之间的位置和平移关系,包括平移(x, y, z)
和旋转(roll, pitch, yaw)
或四元数 (x, y, z, w)
。
2.命令行使用TF 坐标变换工具
示例:已知机器人基坐标系 base_link 和 雷达坐标系 base_laser 之间的坐标关系,求 base_laser 系下的障碍,在 base_link 系的坐标。
发布静态坐标变换,使用 static_transform_publisher
可以快速创建一个固定的坐标变换:
ros2 run tf2_ros static_transform_publisher \
--x 0.1 --y 0.0 --z 0.2 \
--roll 0.0 --pitch 0.0 --yaw 0.0 \
--frame-id base_link \
--child-frame-id base_laser
如下
说明:
--x/--y/--z
:平移向量,子坐标系相对于父坐标系的位置(单位:米)。--roll/--pitch/--yaw
:旋转角度(欧拉角,单位:弧度)。--frame-id
:父坐标系名称。--child-frame-id
:子坐标系名称。
例子中:base_laser 相对于 base_link 有 (0.1, 0.0, 0.2) 的平移,没有旋转。
发布多个静态变换,如果系统中有多个传感器,可以连续启动多个 static_transform_publisher
:
# 发布 base_laser 到 wall_point 的变换
ros2 run tf2_ros static_transform_publisher \
--x 0.3 --y 0.0 --z 0.0 \
--roll 0.0 --pitch 0.0 --yaw 0.0 \
--frame-id base_laser \
--child-frame-id wall_point
如下:
这样,系统中就有以下层级关系:
base_link → base_laser → wall_point
使用tf2_echo
可以实时查看任意两个坐标系间的平移和旋转:
ros2 run tf2_ros tf2_echo base_link wall_point
- 第一个参数是父坐标系(frame_id)。
- 第二个参数是子坐标系(child_frame_id)。
使用 view_frames
可以生成系统中所有坐标系的拓扑图:
3.使用 MRPT 可视化 TF2 坐标系
除了 view_frames 生成的静态 PDF 图,MRPT(Mobile Robot Programming Toolkit)提供了动态、交互式的 3D 坐标系可视化工具,非常适合调试多传感器机器人系统。
安装 MRPT ROS2:
sudo apt install mrpt-apps
启动可视化,可以简单体验坐标选择
3d-rotation-converter
4、实践:手眼坐标转换(python)
1.机械臂底座到相机(静态TF发布)
在 ROS2 中,我们通常通过发布 tf2 的静态坐标变换,描述相机在机械臂坐标系中的位置和姿态,为后续手眼标定、目标识别和机械臂运动规划提供参考。
安装依赖包:
sudo apt install ros-${ROS_DISTRO}-tf-transformations
sudo pip3 install transforms3d
tf-transformations
和transforms3d
用于坐标变换的计算和调试。
在节点中创建静态 TF 广播器:
先创建 ROS2 工作区(以自己的实际目录为准):
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
在 src 下创建一个 Python 功能包,比如叫 handeye_tf:
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python handeye_tf --dependencies rclpy geometry_msgs tf2_ros
新建静态TF节点 handeye_tf/handeye_tf/static_tf_broadcaster.py
:
import math
import rclpy
from rclpy.node import Node
from tf2_ros import StaticTransformBroadcaster
from geometry_msgs.msg import TransformStamped
from tf_transformations import quaternion_from_euler
class StaticTFBroadcaster(Node):
def __init__(self):
super().__init__('static_tf2_broadcaster')
self.static_broadcaster_ = StaticTransformBroadcaster(self)
self.publish_static_tf()
def publish_static_tf(self):
transform = TransformStamped()
transform.header.stamp = self.get_clock().now().to_msg()
transform.header.frame_id = "base_link"
transform.child_frame_id = "camera_link"
transform.transform.translation.x = 0.5
transform.transform.translation.y = 0.3
transform.transform.translation.z = 0.6
# 欧拉角转四元数
rotation_quat = quaternion_from_euler(math.radians(180), 0, 0)
transform.transform.rotation.x = rotation_quat[0]
transform.transform.rotation.y = rotation_quat[1]
transform.transform.rotation.z = rotation_quat[2]
transform.transform.rotation.w = rotation_quat[3]
# 发布静态坐标变换
self.static_broadcaster_.sendTransform(transform)
self.get_logger().info(f"发布 TF:{transform}")
def main():
rclpy.init()
static_tf_broadcaster = StaticTFBroadcaster()
rclpy.spin(static_tf_broadcaster)
rclpy.shutdown()
修改 setup.py
,添加节点入口:
entry_points={
'console_scripts': [
'static_tf_broadcaster = handeye_tf.static_tf_broadcaster:main',
],
},
编译 & 运行
colcon build
source install/setup.bash
ros2 run handeye_tf static_tf_broadcaster
如下:
查看坐标话题
ros2 topic echo /tf_static
2.动态发布目标相对相机的坐标
在实际场景中,目标(如瓶子、零件)相对于相机的位置会随着机械臂运动或相机视角的变化不断更新,所以我们需要动态发布 TF,让 TF 树实时反映目标的位置和姿态。
新建动态 TF 节点,在 handeye_tf/handeye_tf/
下新建 dynamic_tf_broadcaster.py
文件:
import rclpy
from rclpy.node import Node
from tf2_ros import TransformBroadcaster
from geometry_msgs.msg import TransformStamped
from tf_transformations import quaternion_from_euler
class DynamicTFBroadcaster(Node):
def __init__(self):
super().__init__("tf_node")
self.tf_broadcaster_ = TransformBroadcaster(self)
# 动态TF需要持续发布,这里发布频率设置为 100HZ
self.timer_ = self.create_timer(0.01, self.publish_transform)
def publish_transform(self):
transform = TransformStamped()
transform.header.stamp = self.get_clock().now().to_msg()
transform.header.frame_id = "camera_link"
transform.child_frame_id = "bottle_link"
transform.transform.translation.x = 0.2
transform.transform.translation.y = 0.0
transform.transform.translation.z = 0.5
rotation_quat = quaternion_from_euler(0, 0, 0)
transform.transform.rotation.x = rotation_quat[0]
transform.transform.rotation.y = rotation_quat[1]
transform.transform.rotation.z = rotation_quat[2]
transform.transform.rotation.w = rotation_quat[3]
self.tf_broadcaster_.sendTransform(transform)
def main():
rclpy.init()
tf_node = DynamicTFBroadcaster()
rclpy.spin(tf_node)
rclpy.shutdown()
修改 setup.py
entry_points={
'console_scripts': [
'static_tf_broadcaster = handeye_tf.static_tf_broadcaster:main',
'dynamic_tf_broadcaster = handeye_tf.dynamic_tf_broadcaster:main',
],
},
编译并运行
colcon build
source install/setup.bash
先运行静态TF节点
ros2 run handeye_tf static_tf_broadcaster
再开一个终端运行动态TF节点
ros2 run handeye_tf dynamic_tf_broadcaster
3. 查看坐标间的变换关系
可以使用命令查看:
ros2 run tf2_ros tf2_echo base_link bottle_link
下面使用python来查询TF关系:
新建 TF 监听节点,在 handeye_tf/handeye_tf/
下新建 tf_listener.py
文件:
import rclpy
from rclpy.node import Node
from tf2_ros import TransformListener, Buffer
from tf_transformations import euler_from_quaternion
class TFListener(Node):
def __init__(self):
super().__init__("tf2_listener")
self.buffer_ = Buffer()
self.listener_ = TransformListener(self.buffer_, self)
self.timer_ = self.create_timer(1, self.get_transform)
def get_transform(self):
try:
result = self.buffer_.lookup_transform("base_link", "bottle_link", rclpy.time.Time(seconds=0), rclpy.time.Duration(seconds=1))
transform = result.transform
rotation_euler = euler_from_quaternion([
transform.rotation.x,
transform.rotation.y,
transform.rotation.z,
transform.rotation.w
])
self.get_logger().info(f"平移:{transform.translation},旋转四元数:{transform.rotation}:旋转欧拉角:{rotation_euler}")
except Exception as e:
self.get_logger().warn(f"不能够获取坐标变换,原因: {str(e)}")
def main():
rclpy.init()
node = TFListener()
rclpy.spin(node)
rclpy.shutdown()
修改 setup.py
entry_points={
'console_scripts': [
'static_tf_broadcaster = handeye_tf.static_tf_broadcaster:main',
'dynamic_tf_broadcaster = handeye_tf.dynamic_tf_broadcaster:main',
'tf_listener = handeye_tf.tf_listener:main',
],
},
编译并运行
colcon build
source install/setup.bash
ros2 run handeye_tf tf_listener
如下:
5、实践:地图坐标系变换(C++)
1. C++发布静态TF
待补充: https://www.bilibili.com/video/BV1h8xnePEMe
2. C++发布动态TF
待补充: https://www.bilibili.com/video/BV1h8xnePEMe
3. C++查询TF关系
待补充: https://www.bilibili.com/video/BV1h8xnePEMe
二、可视化工具rqt
rqt
是 ROS 提供的一个基于 Qt 的图形化调试工具,可以用插件方式查看话题、参数、节点关系图等,非常适合开发调试。
安装:
sudo apt install ros-${ROS_DISTRO}-rqt ros-${ROS_DISTRO}-rqt-common-plugins
启动:
rqt
常用插件:
- Node Graph:查看节点之间的话题关系图,直观展示 TF 节点的发布关系。
- Topic Monitor:订阅并查看某个话题的实时消息内容,比如 /tf_static 和 /tf。
- TF Tree:可视化 TF 树,展示各坐标系之间的父子关系。
三、数据可视化工具Rviz
Rviz
是 ROS 中最常用的 3D 可视化工具,适合展示机器人模型、传感器数据和 TF 树。
安装:
sudo apt install ros-${ROS_DISTRO}-rviz2
启动:
rviz2
操作步骤:
- 打开 Rviz2,添加 TF 显示项,可以看到发布的 base_link、camera_link、bottle_link 坐标系。
- 可以添加 RobotModel 查看机械臂模型,结合 URDF 文件更直观。
- 可以添加点云、图像等可视化显示项,辅助调试手眼标定和感知功能。
比如说使用TF工具,查看1.4 实例里面的坐标关系:
四、数据记录工具bag
rosbag 是 ROS 提供的数据录制和回放工具,方便调试时记录话题消息,之后离线回放分析。
安装:
sudo apt install ros-${ROS_DISTRO}-ros2bag ros-${ROS_DISTRO}-rosbag2-storage-default-plugins
录制:
ros2 bag record /tf /tf_static
这会将 /tf 和 /tf_static 两个话题数据保存到一个 bag 文件夹中。
回放:
ros2 bag play <bag文件夹>
查看 bag 文件:
ros2 bag info <bag文件夹>
典型用途:
保存机器人运行时的数据,便于分析。离线调试算法(比如视觉识别、路径规划)时无需真实硬件。