Atlas Robotics
Bài 2/6 · khoảng 10 phút

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:

Publish điều bạn biếtqua TransformBroadcaster
Hỏi điều bạn cầnqua Buffer.lookup_transform(), kể cả khi 2 frame không nối trực tiếp

Ví 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.