2026-04-09 (更新: 2026-05-20)
🗺️ 【ROS2入門⑨】TF2座標変換フレームワーク - ロボットの空間関係を理解する
シリーズ この記事は「ROS2入門シリーズ」の一部です。 シリーズ目次を見る
RViz2 やトピック通信に慣れた状態から、TF2 で複数座標系の位置・姿勢を扱う手順を示します。例えば LiDAR の点群を base_link 基準にそろえるとき、毎回三角関数で計算せず lookup_transform に任せられます。正本は Introducing tf2 です。
2026年5月時点、Ubuntu 22.04 + ROS 2 Humble 想定です。
座標フレーム(TF フレーム)とは?
ロボットには base_link、camera_link、lidar_link など複数の座標フレーム(基準点)が存在します。TF2 tutorial の図解も併読してください。
ロボットの座標フレーム構造(world → base_link → camera/lidar)
各フレーム間の変換(位置 + 姿勢)を TF で受け渡しし、ツリーが途切れない範囲でフレーム間の変換を引き出せます。
TFフレームツリー構造
TF2 関連パッケージのインストール手順
sudo apt install ros-humble-tf2-ros \
ros-humble-tf2-tools \
ros-humble-tf-transformations
pip3 install transforms3d 静的変換(Static Transform)を配信する方法
変化しない変換(カメラがロボットに固定されているなど)は静的変換で配信します。Static transform publisher も参照。
# コマンドで静的変換を配信
# ros2 run tf2_ros static_transform_publisher x y z yaw pitch roll parent child
ros2 run tf2_ros static_transform_publisher \
0.1 0.0 0.2 0.0 0.0 0.0 \
base_link camera_frame # Pythonで静的変換を配信
from geometry_msgs.msg import TransformStamped
from tf2_ros import StaticTransformBroadcaster
import math
class CameraFramePublisher(Node):
def __init__(self):
super().__init__('camera_frame_publisher')
broadcaster = StaticTransformBroadcaster(self)
tf = TransformStamped()
tf.header.stamp = self.get_clock().now().to_msg()
tf.header.frame_id = 'base_link'
tf.child_frame_id = 'camera_frame'
# カメラは前方 10cm、上方 20cmの位置
tf.transform.translation.x = 0.1
tf.transform.translation.y = 0.0
tf.transform.translation.z = 0.2
# 回転なし(クォータニオン)
tf.transform.rotation.w = 1.0
broadcaster.sendTransform(tf) 動的変換(Transform)を配信する方法
ロボットが動く場合、動的変換で位置を更新し続けます。
from tf2_ros import TransformBroadcaster
class RobotPosePublisher(Node):
def __init__(self):
super().__init__('robot_pose_publisher')
self.broadcaster = TransformBroadcaster(self)
self.timer = self.create_timer(0.1, self.broadcast_pose)
self.x = 0.0
def broadcast_pose(self):
tf = TransformStamped()
tf.header.stamp = self.get_clock().now().to_msg()
tf.header.frame_id = 'world'
tf.child_frame_id = 'base_link'
# ロボットが移動していると仮定
self.x += 0.01
tf.transform.translation.x = self.x
tf.transform.translation.y = 0.0
tf.transform.translation.z = 0.0
tf.transform.rotation.w = 1.0
self.broadcaster.sendTransform(tf) lookup_transform で座標変換を取得する方法
import rclpy
from tf2_ros import Buffer, TransformListener
from tf2_ros import LookupException, ConnectivityException
class CoordTransformNode(Node):
def __init__(self):
super().__init__('coord_transform')
self.tf_buffer = Buffer()
self.tf_listener = TransformListener(self.tf_buffer, self)
self.timer = self.create_timer(1.0, self.lookup_transform)
def lookup_transform(self):
try:
# lookup_transform(ターゲット座標系, ソース座標系, 時刻)
tf = self.tf_buffer.lookup_transform(
'base_link',
'camera_frame',
rclpy.time.Time())
x = tf.transform.translation.x
y = tf.transform.translation.y
self.get_logger().info(f'Camera pos in base_link: x={x:.2f}, y={y:.2f}')
except Exception as e:
# 代表的には Lookup / Connectivity / Extrapolation(時刻まわり)など
self.get_logger().warn(str(e))
座標変換の概念図
TF ツリーを可視化・確認する方法
# TFツリーをPDFで出力
ros2 run tf2_tools view_frames
# frames.pdf が生成される
# フレーム間の変換をリアルタイム表示
ros2 run tf2_ros tf2_echo base_link camera_frame
# At time 0.0
# Translation: [0.100, 0.000, 0.200]
# Rotation: [0.000, 0.000, 0.000, 1.000] TF2 でつまずきやすい点
- static_transform_publisher の 引数の並び(位置・オイラー角・クォータニオン等)は版で差が出るので、--help と公式メモをセットで見る
- lookup_transform が落ちる典型は フレーム名の typo、親子がまだブロードキャストされていない、時刻が未来/古すぎて外挿できないなど
- view_frames はスナップショット用途。TF を出しているノードが止まると PDF 上のツリーも欠ける
TF2 座標変換のまとめ
- TF2 は フレームを束ねたツリーとして理解し、どの辺が自分のブロードキャストか押さえると追いやすい
- 静的変換は StaticTransformBroadcaster で、固定の親子関係を流し続ける用途向き
- 動的変換は TransformBroadcaster で、位置姿勢が更新されるたびに書き換える
- Buffer と TransformListener を組み合わせて、lookup_transform で目的のペアを引くのが典型的な読み側パターン
- view_frames や tf2_echo で、いまツリーがどう見えているかを確認する
- 次回(シリーズ⑩)では Gazebo 側のシミュレーション入り口を扱う想定です