ROS2-TF2基础
ROS2-TF2 指南
1. TF2 简介
TF2(Transform Library)是ROS 2中用于管理和转换坐标系的核心库。它帮助机器人应用程序在多个坐标系之间进行变换和同步,确保传感器数据和运动控制指令在正确的坐标系下操作。
1.1 主要功能
- 管理多个坐标系:跟踪和维护系统中所有坐标系之间的关系
- 处理时间戳:支持基于时间的坐标变换查询
- 构建树结构:将坐标系组织成树状结构
- 插值和外推:在时间点之间进行变换插值
- 广播变换:发布坐标系之间的变换关系
- 监听变换:接收和查询坐标系之间的变换关系
1.2 常用工具
tf2_ros.Buffer:存储和管理变换数据tf2_ros.TransformBroadcaster:广播坐标变换tf2_ros.TransformListener:监听坐标变换tf2_ros.StaticTransformBroadcaster:广播静态变换
2. TF2 核心概念
2.1 坐标系(Frame)
坐标系是描述空间位置的参考系统。在机器人系统中,常见的坐标系包括:
world:世界坐标系base_link:机器人本体坐标系camera:相机坐标系laser:激光雷达坐标系
2.2 变换(Transform)
变换描述了两个坐标系之间的相对位置和姿态关系,包括:
- 平移(Translation):x, y, z 三个方向的位置偏移
- 旋转(Rotation):通过四元数或欧拉角表示的姿态变化
2.3 父子关系(Parent-Child Relationship)
在TF2树中,每个坐标系(除了根坐标系)都有一个父坐标系,形成树状结构:
- 父坐标系(Parent Frame):参考坐标系
- 子坐标系(Child Frame):需要变换的坐标系
3. TF2 消息类型
3.1 TransformStamped
这是TF2中最重要的消息类型,用于表示两个坐标系之间的变换关系:
from geometry_msgs.msg import TransformStamped
# TransformStamped 消息结构
# Header header
# string child_frame_id
# geometry_msgs/Transform transform
3.2 Transform
表示实际的变换数据:
from geometry_msgs.msg import Transform
# Transform 消息结构
# geometry_msgs/Vector3 translation
# geometry_msgs/Quaternion rotation
3.3 PointStamped
表示带坐标系信息的点:
from geometry_msgs.msg import PointStamped
# PointStamped 消息结构
# std_msgs/Header header
# geometry_msgs/Point point
4. TF2 树结构
TF2树是表示不同部件之间相对位置和姿态关系的结构,将各种坐标系连接成树状结构,支持在任意两个坐标系之间进行转换。
4.1 工作原理
当广播或监听TF2变换时,这些变换信息被组织成一棵树,每个节点代表一个坐标系,边代表坐标变换。
4.2 作用
- 管理和组织坐标系
- 自动计算变换
- 实时更新和查询
5. TF2 变换广播
5.1 动态变换广播
动态变换广播用于发布随时间变化的坐标变换,例如机器人移动时底盘坐标系的变化。
import rclpy
from rclpy.node import Node
from tf2_ros.transform_broadcaster import TransformBroadcaster
from geometry_msgs.msg import TransformStamped
import tf_transformations
class TransformBroadcasterNode(Node):
def __init__(self):
super().__init__('transform_broadcaster_node')
# 创建变换广播器
self._broadcaster = TransformBroadcaster(self)
# 创建定时器,每秒调用一次回调函数发布变换
self.timer = self.create_timer(1.0, self.broadcast_transform)
def broadcast_transform(self):
# 创建一个TransformStamped消息来存储变换信息
transform_stamped = TransformStamped()
transform_stamped.header.stamp = self.get_clock().now().to_msg()
transform_stamped.header.frame_id = 'base_link'
transform_stamped.child_frame_id = 'laser'
# 设置变换的平移部分,假设激光雷达在底盘前方20厘米,高度为10厘米
transform_stamped.transform.translation.x = 0.2
transform_stamped.transform.translation.y = 0.0
transform_stamped.transform.translation.z = 0.1
# 设置变换的旋转部分,这里假设没有旋转,即四元数(0, 0, 0, 1)
quat = tf_transformations.quaternion_from_euler(0, 0, 0)
transform_stamped.transform.rotation.x = quat[0]
transform_stamped.transform.rotation.y = quat[1]
transform_stamped.transform.rotation.z = quat[2]
transform_stamped.transform.rotation.w = quat[3]
# 发送变换
self._broadcaster.sendTransform(transform_stamped)
def main(args=None):
rclpy.init(args=args)
node = TransformBroadcasterNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
5.2 静态变换广播
静态变换广播用于发布不随时间变化的坐标变换,例如固定安装在机器人上的传感器。
import rclpy
from rclpy.node import Node
from tf2_ros.static_transform_broadcaster import StaticTransformBroadcaster
from geometry_msgs.msg import TransformStamped
import tf_transformations
class StaticTransformPublisher(Node):
def __init__(self):
super().__init__('static_transform_publisher')
# 创建静态变换广播器
self._broadcaster = StaticTransformBroadcaster(self)
# 创建一个TransformStamped消息来存储变换信息
static_transform_stamped = TransformStamped()
static_transform_stamped.header.stamp = self.get_clock().now().to_msg()
static_transform_stamped.header.frame_id = 'base_link'
static_transform_stamped.child_frame_id = 'laser'
# 设置变换的平移部分,假设激光雷达在底盘前方20厘米,高度为10厘米
static_transform_stamped.transform.translation.x = 0.2
static_transform_stamped.transform.translation.y = 0.0
static_transform_stamped.transform.translation.z = 0.1
# 设置变换的旋转部分,这里假设没有旋转,即四元数(0, 0, 0, 1)
quat = tf_transformations.quaternion_from_euler(0, 0, 0)
static_transform_stamped.transform.rotation.x = quat[0]
static_transform_stamped.transform.rotation.y = quat[1]
static_transform_stamped.transform.rotation.z = quat[2]
static_transform_stamped.transform.rotation.w = quat[3]
# 发送静态变换
self._broadcaster.sendTransform(static_transform_stamped)
def main(args=None):
rclpy.init(args=args)
node = StaticTransformPublisher()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
6. TF2 变换监听
6.1 基本监听器
监听器用于查询坐标系之间的变换关系:
import rclpy
from rclpy.node import Node
from tf2_ros import TransformListener, Buffer
from geometry_msgs.msg import PointStamped
import tf2_geometry_msgs
class TransformListenerNode(Node):
def __init__(self):
super().__init__('transform_listener_node')
# 创建一个Buffer对象来存储变换
self.tf_buffer = Buffer()
# 创建TransformListener来监听变换
self.tf_listener = TransformListener(self.tf_buffer, self)
# 模拟一个激光雷达数据点(在激光雷达坐标系下)
self.laser_point = PointStamped()
self.laser_point.header.frame_id = 'laser'
self.laser_point.point.x = 1.0
self.laser_point.point.y = 0.0
self.laser_point.point.z = 0.0
# 创建定时器,每秒调用一次回调函数
self.timer = self.create_timer(1.0, self.on_timer)
def on_timer(self):
try:
# 查询从'laser'到'base_link'的变换
transform = self.tf_buffer.lookup_transform('base_link', 'laser', rclpy.time.Time())
# 将激光雷达坐标系下的点转换到底盘坐标系下
transformed_point = tf2_geometry_msgs.do_transform_point(self.laser_point, transform)
# 打印转换后的点
self.get_logger().info(f'Transformed point: ({transformed_point.point.x}, {transformed_point.point.y}, {transformed_point.point.z})')
except Exception as e:
self.get_logger().warn(f'Could not transform laser point: {e}')
def main(args=None):
rclpy.init(args=args)
node = TransformListenerNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
6.2 点坐标变换
使用do_transform_point函数进行点坐标变换:
from tf2_geometry_msgs import do_transform_point
# point: geometry_msgs.msg.PointStamped
# transform: geometry_msgs.msg.TransformStamped
transformed_point = do_transform_point(point, transform)
7. 最佳实践
7.1 坐标系命名规范
- 使用有意义的名称,如
base_link、camera、laser - 遵循ROS约定,使用小写字母和下划线
- 保持命名一致性
7.2 变换广播最佳实践
-
选择合适的广播类型:
- 静态变换:用于固定关系
- 动态变换:用于变化关系
-
时间戳处理:
- 使用当前时间戳
- 确保时间同步
-
错误处理:
- 添加异常处理
- 日志记录
7.3 变换监听最佳实践
-
超时处理:
try: transform = self.tf_buffer.lookup_transform( target_frame, source_frame, rclpy.time.Time(), rclpy.duration.Duration(seconds=1.0)) except Exception as e: self.get_logger().warn(f'Transform lookup failed: {e}') -
频率控制:
- 避免过于频繁的查询
- 合理使用缓存
8. 常见问题和解决方案
8.1 "Lookup would require extrapolation" 错误
这是由于时间不同步导致的,解决方案:
# 使用最新可用的变换
transform = self.tf_buffer.lookup_transform(
target_frame, source_frame, rclpy.time.Time())
8.2 坐标系未找到错误
确保:
- 变换已正确广播
- 坐标系名称拼写正确
- TF2树已正确构建
8.3 变换延迟问题
解决方案:
- 检查广播频率
- 使用静态变换广播固定关系
- 优化系统性能
10. 总结
TF2是ROS 2中管理坐标变换的核心工具,正确使用TF2对机器人系统开发至关重要。通过掌握:
- TF2的基本概念和数据结构
- 变换广播和监听的方法
- 静态和动态变换的使用场景
- 在实际项目中的应用方式
您将能够构建更加健壮和可维护的机器人系统。

浙公网安备 33010602011771号