VnRobo
Về chúng tôiBảng giáBlogLiên hệ
🇺🇸ENĐăng nhậpDùng thử miễn phí
🇺🇸EN
VnRobo logo

Hạ tầng AI cho robot công nghiệp thế hệ mới.

Sản phẩm

  • Tính năng
  • Bảng giá
  • Kiến thức
  • Dịch vụ

Công ty

  • Về chúng tôi
  • Blog
  • Liên hệ

Pháp lý

  • Chính sách bảo mật
  • Điều khoản sử dụng

© 2026 VnRobo. Bảo lưu mọi quyền.

Được tạo với♥tại Việt Nam
VnRobo
Về chúng tôiBảng giáBlogLiên hệ
🇺🇸ENĐăng nhậpDùng thử miễn phí
🇺🇸EN
  1. Trang chủ
  2. Blog
  3. ROS 2 từ A đến Z (P4): TF2, URDF và RViz2
navigationros2tutorialamrtf2urdfrviz2cpppython

ROS 2 từ A đến Z (P4): TF2, URDF và RViz2

Mô tả hình dạng robot bằng URDF, theo dõi quan hệ toạ độ giữa các bộ phận bằng TF2, và nhìn tất cả trong RViz2 — ví dụ C++ và Python (Jazzy, 6/2026).

Nguyễn Anh Tuấn20 tháng 2, 20267 phút đọcCập nhật: 14 thg 6, 2026
ROS 2 từ A đến Z (P4): TF2, URDF và RViz2

Một robot là tập hợp nhiều bộ phận chuyển động: bánh xe, cánh tay, camera, LiDAR. Câu hỏi cốt lõi luôn là: vật mà camera nhìn thấy đang ở đâu so với tâm robot? So với bánh xe? Để trả lời, ROS 2 cần biết quan hệ toạ độ giữa mọi bộ phận. Đó là ba công cụ của bài này: URDF (mô tả hình dạng), TF2 (theo dõi quan hệ toạ độ), và RViz2 (nhìn thấy tất cả).

Bài này nối tiếp Phần 3: Parameters, Launch, Lifecycle.

Phiên bản (6/2026): ROS 2 Jazzy (Ubuntu 24.04). Ví dụ có cả C++ và Python. RViz2 là công cụ visualize chính. Trên Humble đổi jazzy → humble.

TF2 demo: turtle2 follow turtle1 — cây transform frames trong hành động
TF2 demo: turtle2 follow turtle1 — cây transform frames trong hành động
nguồn: docs.ros.org/en/jazzy

1. TF2 — cây quan hệ toạ độ

Mỗi bộ phận robot có một frame (hệ toạ độ riêng): base_link (tâm robot), laser (LiDAR), camera_link, left_wheel… TF2 duy trì một cây transform mô tả vị trí + hướng (translation + rotation) của frame con so với frame cha, theo thời gian thực.

            map
             │
            odom
             │
         base_link
        ┌────┼─────────┐
     laser  camera   left_wheel

Nhờ cây này, bạn hỏi "điểm trong frame laser nằm ở đâu trong frame map?" và TF2 tự tính chuỗi phép biến đổi — bạn không phải nhân ma trận bằng tay.

Static transform — frame gắn cứng

LiDAR bắt vít cố định lên thân robot → quan hệ base_link → laser không đổi. Đó là static transform.

# Cách nhanh nhất: phát static TF bằng CLI
# args: x y z yaw pitch roll  frame_cha  frame_con
ros2 run tf2_ros static_transform_publisher \
  0.1 0 0.2 0 0 0 base_link laser

Trong launch file (cách thực tế):

from launch_ros.actions import Node

Node(
    package='tf2_ros',
    executable='static_transform_publisher',
    arguments=['0.1', '0', '0.2', '0', '0', '0', 'base_link', 'laser'],
)

Dynamic transform — frame chuyển động (code)

Quan hệ odom → base_link thay đổi liên tục khi robot di chuyển → cần broadcast transform trong code.

Python:

import rclpy
from rclpy.node import Node
from tf2_ros import TransformBroadcaster
from geometry_msgs.msg import TransformStamped
import math


class OdomBroadcaster(Node):
    def __init__(self):
        super().__init__('odom_broadcaster')
        self.br = TransformBroadcaster(self)
        self.timer = self.create_timer(0.05, self.tick)  # 20 Hz
        self.x = 0.0

    def tick(self):
        self.x += 0.01  # giả lập robot tiến lên
        t = TransformStamped()
        t.header.stamp = self.get_clock().now().to_msg()
        t.header.frame_id = 'odom'
        t.child_frame_id = 'base_link'
        t.transform.translation.x = self.x
        # Quaternion cho yaw = 0
        t.transform.rotation.w = 1.0
        self.br.sendTransform(t)

C++:

#include "rclcpp/rclcpp.hpp"
#include "tf2_ros/transform_broadcaster.h"
#include "geometry_msgs/msg/transform_stamped.hpp"

class OdomBroadcaster : public rclcpp::Node
{
public:
  OdomBroadcaster() : Node("odom_broadcaster")
  {
    br_ = std::make_unique<tf2_ros::TransformBroadcaster>(*this);
    timer_ = create_wall_timer(std::chrono::milliseconds(50),
                               std::bind(&OdomBroadcaster::tick, this));
  }
private:
  void tick()
  {
    x_ += 0.01;
    geometry_msgs::msg::TransformStamped t;
    t.header.stamp = now();
    t.header.frame_id = "odom";
    t.child_frame_id = "base_link";
    t.transform.translation.x = x_;
    t.transform.rotation.w = 1.0;
    br_->sendTransform(t);
  }
  std::unique_ptr<tf2_ros::TransformBroadcaster> br_;
  rclcpp::TimerBase::SharedPtr timer_;
  double x_ = 0.0;
};

Tra cứu transform — frame A so với frame B

Python (lookup):

from tf2_ros import TransformException
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener

class FrameLookup(Node):
    def __init__(self):
        super().__init__('frame_lookup')
        self.buffer = Buffer()
        self.listener = TransformListener(self.buffer, self)
        self.create_timer(1.0, self.lookup)

    def lookup(self):
        try:
            tf = self.buffer.lookup_transform('map', 'laser', rclpy.time.Time())
            self.get_logger().info(
                f'laser trong map: x={tf.transform.translation.x:.2f}')
        except TransformException as e:
            self.get_logger().warn(f'Chưa có transform: {e}')

Công cụ debug TF2

# Vẽ cây frame ra file PDF
ros2 run tf2_tools view_frames

# In ra transform giữa 2 frame liên tục
ros2 run tf2_ros tf2_echo base_link laser

Lỗi kinh điển: "lookup would require extrapolation into the future". Nghĩa là bạn hỏi transform ở thời điểm chưa có dữ liệu. Dùng rclpy.time.Time() (rỗng = "mới nhất") thay vì timestamp cụ thể khi mới học.

2. URDF — mô tả hình dạng robot

URDF (Unified Robot Description Format) là file XML mô tả robot gồm links (bộ phận cứng) nối với nhau qua joints (khớp).

<!-- robot.urdf -->
<?xml version="1.0"?>
<robot name="my_robot">

  <!-- Thân robot -->
  <link name="base_link">
    <visual>
      <geometry><box size="0.3 0.2 0.1"/></geometry>
      <material name="blue"><color rgba="0 0 0.8 1"/></material>
    </visual>
  </link>

  <!-- LiDAR -->
  <link name="laser">
    <visual>
      <geometry><cylinder radius="0.03" length="0.04"/></geometry>
    </visual>
  </link>

  <!-- Khớp gắn LiDAR lên thân (cố định) -->
  <joint name="laser_joint" type="fixed">
    <parent link="base_link"/>
    <child link="laser"/>
    <origin xyz="0.1 0 0.1" rpy="0 0 0"/>
  </joint>

</robot>

Các loại joint quan trọng:

Type Ý nghĩa
fixed Gắn cứng, không cử động (LiDAR, camera)
continuous Quay vô hạn (bánh xe)
revolute Quay có giới hạn góc (khớp cánh tay)
prismatic Trượt thẳng (gripper, thang nâng)

Xacro — URDF gọn hơn

URDF thật rất dài và lặp. Xacro thêm biến và macro:

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="my_robot">
  <xacro:property name="wheel_radius" value="0.05"/>

  <xacro:macro name="wheel" params="prefix y_pos">
    <link name="${prefix}_wheel">
      <visual><geometry>
        <cylinder radius="${wheel_radius}" length="0.04"/>
      </geometry></visual>
    </link>
    <joint name="${prefix}_wheel_joint" type="continuous">
      <parent link="base_link"/>
      <child link="${prefix}_wheel"/>
      <origin xyz="0 ${y_pos} 0" rpy="${-pi/2} 0 0"/>
      <axis xyz="0 0 1"/>
    </joint>
  </xacro:macro>

  <xacro:wheel prefix="left"  y_pos="0.12"/>
  <xacro:wheel prefix="right" y_pos="-0.12"/>
</robot>

robot_state_publisher — biến URDF thành TF2

Đây là cầu nối quan trọng: robot_state_publisher đọc URDF + trạng thái khớp (/joint_states) rồi tự phát toàn bộ TF2 cho mọi link. Bạn không phải viết broadcaster cho từng frame.

# launch/display.launch.py
import os
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import xacro

def generate_launch_description():
    pkg = get_package_share_directory('my_robot_desc')
    urdf = os.path.join(pkg, 'urdf', 'robot.urdf.xacro')
    robot_desc = xacro.process_file(urdf).toxml()

    return LaunchDescription([
        Node(
            package='robot_state_publisher',
            executable='robot_state_publisher',
            parameters=[{'robot_description': robot_desc}],
        ),
        # GUI kéo thanh trượt để quay khớp (chỉ để test)
        Node(
            package='joint_state_publisher_gui',
            executable='joint_state_publisher_gui',
        ),
        Node(package='rviz2', executable='rviz2'),
    ])

3. RViz2 — nhìn thấy robot và dữ liệu

RViz2 là công cụ visualize 3D của ROS 2. Sau khi chạy launch trên:

ros2 launch my_robot_desc display.launch.py

Trong RViz2:

  1. Đặt Fixed Frame = base_link (góc trên trái).
  2. Bấm Add → thêm display RobotModel → thấy robot dựng từ URDF.
  3. Add → TF → thấy các trục toạ độ của mọi frame.
  4. Kéo thanh trượt trong joint_state_publisher_gui → bánh xe quay trong RViz2.

Các display hữu ích khác: LaserScan (/scan từ LiDAR), PointCloud2, Image (camera), Path (đường đi Nav2). Đây là cách bạn "nhìn thấy" robot nghĩ gì — cực kỳ quan trọng khi debug.

Mẹo: Lưu cấu hình RViz2 (File → Save Config As) thành file .rviz, rồi nạp tự động trong launch: arguments=['-d', rviz_config_path]. Đỡ phải add lại display mỗi lần.

Pitfalls thường gặp

  1. RobotModel không hiện — Fixed Frame sai, hoặc robot_state_publisher chưa chạy, hoặc URDF có link không nối vào cây joint.
  2. TF "không liền" — thiếu một transform trong chuỗi (vd thiếu odom → base_link). Chạy ros2 run tf2_tools view_frames để thấy cây bị đứt ở đâu.
  3. Xacro lỗi parse — quên xmlns:xacro, hoặc dùng ${} mà không khai báo property.
  4. Bánh xe quay sai trục — sai axis hoặc rpy trong joint. Dựng từng link trong RViz2 để kiểm tra trực quan.

Tổng kết

  • URDF/Xacro mô tả hình dạng robot bằng links + joints.
  • robot_state_publisher biến URDF thành cây TF2 tự động.
  • TF2 trả lời mọi câu hỏi "frame A ở đâu so với frame B".
  • RViz2 cho bạn nhìn thấy robot, các frame, và dữ liệu sensor.

Có URDF và TF2 rồi, ta sẵn sàng điều khiển phần cứng thật. Phần 5: ros2_control sẽ dùng chính URDF này để khai báo hardware interface và lái motor.


Bài viết liên quan

  • ROS 2 từ A đến Z (P3): Parameters, Launch, Lifecycle — Khởi động hệ thống display
  • ROS 2 từ A đến Z (P5): ros2_control và Hardware — Dùng URDF để điều khiển motor
  • ROS 2 từ A đến Z (P6): Nav2 — Robot tự hành — TF2 là nền tảng của navigation
NT

Nguyễn Anh Tuấn

Robotics & AI Engineer. Building VnRobo — sharing knowledge about robot learning, VLA models, and automation.

Khám phá VnRobo

Fleet MonitoringROS 2 IntegrationAMR Solutions
ros2-a-to-z — Phần 4/7
← ROS 2 từ A đến Z (P3): Parameters, Launch, LifecycleROS 2 từ A đến Z (P5): ros2_control và Hardware →

Bài viết liên quan

ROS 2 từ A đến Z (P3): Parameters, Launch, Lifecycle
ros2tutorialamrPhần 3
navigation

ROS 2 từ A đến Z (P3): Parameters, Launch, Lifecycle

Cấu hình node linh hoạt bằng Parameters, khởi động cả hệ thống bằng Launch files, và quản lý vòng đời node với Lifecycle — đầy đủ ví dụ C++ và Python (Jazzy, 6/2026).

6/2/20267 phút đọc
NT
ROS 2 từ A đến Z (P6): Nav2 — Robot tự hành
ros2tutorialamrPhần 6
navigation

ROS 2 từ A đến Z (P6): Nav2 — Robot tự hành

Cấu hình Nav2 stack để robot tự lập bản đồ SLAM và di chuyển tự động — từ simulation đến thực tế (Jazzy, cập nhật 6/2026).

20/3/202611 phút đọc
NT
ROS 2 từ A đến Z (Phần 2): Topics, Services và Actions
ros2tutorialamrPhần 2
navigation

ROS 2 từ A đến Z (Phần 2): Topics, Services và Actions

Hiểu rõ 3 primitives giao tiếp trong ROS 2 — khi nào dùng Topic, Service hay Action cho robot của bạn.

21/1/202610 phút đọc
NT
VnRobo logo

Hạ tầng AI cho robot công nghiệp thế hệ mới.

Sản phẩm

  • Tính năng
  • Bảng giá
  • Kiến thức
  • Dịch vụ

Công ty

  • Về chúng tôi
  • Blog
  • Liên hệ

Pháp lý

  • Chính sách bảo mật
  • Điều khoản sử dụng

© 2026 VnRobo. Bảo lưu mọi quyền.

Được tạo với♥tại Việt Nam