Coordinate Frames and Transformations in ROS2
Transforming coordinates relative to
the sensors, the robot & the world
What is a coordinate frame?
Set of orthogonal axes attached to a body
Why do we need them?
Robotics convention:
Conversion between frames
In ROS, sensor data is always provided relative to the sensor itself
We need to centralize the data in different sensor frames into one shared 'global' frame, for easier processing
May need to convert from global to local or vice-versa, and even between different sensor frames for e.g. LiDAR-camera fusion
But how do we do that?
Rigid transformations
Frame conversions are a combination of translation and rotation
Can be used to convert points from one frame to another
coordinates in
source frame
coordinates in
target frame
source frame
target frame
4x4 Transformation Matrix
inner dot markdown
Rotation representations
Intuitive: Euler angles
applied in that order (RPY)
Practical: Rotation matrix
→ not compact (9 numbers)
Quaternions
Idea: Rotations can be represented by:
Compact representation: 4 numbers
For practical reasons (easy to compose & invert), represented like that
Used by ROS to represent rotations
Transformation operations = matrix operations
Practical application: ROS TF Tree
What is TF2?
A package that handles the coordinate frames present in a system & the transformations between them
Nodes can:
TF2 Static Broadcaster (Python)
...
from geometry_msgs.msg import TransformStamped
from tf2_ros.static_transform_broadcaster import StaticTransformBroadcaster
from tf_transformations import quaternion_from_euler
class StaticFramePublisher(Node):
def __init__(self, transformation):
super().__init__('static_turtle_tf2_broadcaster')
self.tf_static_broadcaster = StaticTransformBroadcaster(self)
# Publish static transforms once at startup
self.make_transforms()
def make_transforms(self):
t = TransformStamped()
t.header.stamp = self.get_clock().now().to_msg()
t.header.frame_id = ‘map’
t.child_frame_id = ‘base_link’
t.transform.translation.x/y/z = ...
t.transform.rotation.x,y,z,w = quaternion_from_euler(r,p,y)
self.tf_static_broadcaster.sendTransform(t)
...
parent in TF tree
child in TF tree
map = parent frame
base_link = child frame
TF2 Broadcaster (Python)
...
from geometry_msgs.msg import TransformStamped
from tf2_ros import TransformBroadcaster
class FramePublisher(Node):
def __init__(self, transformation):
super().__init__('turtle_tf2_frame_broadcaster')
self.tf_broadcaster = TransformBroadcaster(self)
self.timer = self.create_timer(1/30.0, self.timer_cb) # 30Hz tf broadcaster
def timer_cb(self):
t = TransformStamped()
t.header.stamp = self.get_clock().now().to_msg()
t.header.frame_id = ‘map’
t.child_frame_id = ‘base_link’
t.transform.translation.x/y/z = ...
t.transform.rotation.x/y/z/w = ...
self.tf_broadcaster.sendTransform(t)
...
parent in TF tree
child in TF tree
map = parent frame
base_link = child frame
TF2 Listener (Python)
The transform from Source Frame to Target Frame is the transform which when applied to an object in Target Frame will represent it in Source Frame.
...
from tf2_ros import TransformException
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener
class FrameListener(Node):
def __init__(self, transformation):
super().__init__('turtle_tf2_frame_listener')
self.tf_buffer = Buffer()
self.tf_listener = TransformListener(self.tf_buffer, self)
self.timer = self.create_timer(1/30.0, self.timer_cb)
def timer_cb(self):
try:
# map->base_link rigid transformation
self.map_to_base_link_tf = self.tf_buffer.lookup_transform(“map”, ”base_link”, rclpy.time.Time())
except TransformException as ex:
self.get_logger().info(f'Could not transform map to base_link: {ex}')
return
...
map = target frame
base_link = source frame
target frame
source frame
No need to be parent/child (can be anywhere
in the TF tree)
Useful TF2 utilities
Visualize the TF tree, view frames and transform rates (Hz):
ros2 run tf2_tools view_frames
Listen to a transform:
ros2 run tf2_ros tf2_echo [target_frame] [source_frame]
Broadcast a (static) transform:
ros2 run tf2_ros static_transform_publisher [x] [y] [z] [yaw] [pitch] [roll] [parent_frame] [child_frame]
Monitor a transform, or all transforms (including chain, broadcasters, rate, etc.):
ros2 run tf2_ros tf2_monitor ([target_frame] [source_frame])
Resources