Frame và Transform
Frame (hệ tọa độ) là một gốc tọa độ có tên, gắn với một vật hoặc vị trí cụ thể — thân robot, một cảm biến, một điểm trên bản đồ. Transform là độ lệch giữa hai frame: tịnh tiến (x, y, z) cộng một phép xoay, mô tả "frame con nằm ở đâu, hướng nào, nhìn từ frame cha".
Mọi message có header.frame_id trong ROS 2 (gần như mọi dữ liệu cảm biến, mọi lệnh định vị) đều đang nói: "các con số này đo từ frame có tên này" — một con số tọa độ không kèm frame là vô nghĩa.
Kéo thử: đổi transform, xem frame con di chuyển
Frame cha (bên trái, cố định) không đổi. Frame con di chuyển và xoay theo đúng 3 số bạn chỉnh: x, y, yaw.
Một chiều, không đối xứng
Transform luôn có hướng: cha → con. Biết A→B thì suy ra được B→A (nghịch đảo), nhưng khai báo một transform là khai báo đúng một chiều. Bạn không tự làm phép cộng/nghịch đảo ma trận tay — tf2 lo việc đó. Công việc của bạn chỉ có hai phần:
TransformBroadcasterBuffer.lookup_transform(), kể cả khi 2 frame không nối trực tiếpVí dụ trong dự án: tf_broadcaster.py
Publish một transform động world → moving_frame, tự xoay tròn quanh gốc — đúng công thức dùng trong ô "Tự xoay" ở mô hình trên:
t.header.frame_id = "world" # frame CHA
t.child_frame_id = "moving_frame" # frame CON
t.transform.translation.x = radius * math.cos(angle)
t.transform.translation.y = radius * math.sin(angle)
t.transform.rotation.z = math.sin(angle / 2.0) # quaternion cho xoay quanh trục Z
t.transform.rotation.w = math.cos(angle / 2.0)
self.broadcaster.sendTransform(t)
tf_listener.py hỏi lại transform đó mà không cần biết bên kia publish thế nào:
t = self.buffer.lookup_transform("world", "moving_frame", rclpy.time.Time())
Thử ngay
Cần 2 terminal (đã source install/setup.bash, xem Module 0):
# Terminal 1 — để chạy, đừng tắt
ros2 launch ros2_basics demo_tf.launch.py
# Terminal 2
ros2 run ros2_basics tf_listener
Terminal 2 in ra vị trí moving_frame mỗi giây — đúng số bạn vừa thấy trong mô hình, chỉ là đọc bằng lookup_transform() thay vì kéo thanh trượt.
Ví dụ trong dự án Atlas
atlas_description/urdf/atlas.urdf.xacro (Module 3) — mỗi <joint> giữa hai <link> chính là khai báo một transform tĩnh cha→con (ví dụ base_link → lidar_link, lệch đúng vị trí gắn LiDAR thật trên khung robot). Bạn sẽ không tự gọi TransformBroadcaster cho các frame này — robot_state_publisher tự đọc URDF và publish hết.