はじめに
本記事は、ROS2を「使う側」のエンジニア視点で、座標系・オドメトリ・座標変換(TF/TF2)を整理しながら、手を動かして理解を固めるシリーズの続きです。
ROS2はトピックやノードを動かすだけでも一定の成果は出ますが、実務では「座標が読めること(=フレームが説明できること)」が重要になります。
本記事では、見た目(URDF/ロボットモデル)よりも、座標系の関係と変換がどう動いているかに焦点を当てます。
ここまでの流れは次の通りです。
-
- 1回目:エンジニアとしてROS2を使う側としての理解(座標系・TFの位置づけなど)
ROS2で「動かす」ために押さえておきたい考え方第0章 はじめに ― なぜ今、ROS2を「使う側」の視点で整理するのか― ROS2の座標系・オドメトリ・変換をエンジニア視点で整理する ―ROS2は、自律走行、自動運転、空間認識(地図の認識や自己位置推定)といった分野を中心に、長年にわたっ... - 2回目:実際にノードを動かしながら挙動を確認(CLIで数値だけを見る)
ROS2で「動いている感覚」をターミナルでつかむ(前回の続き)はじめに ― 前回からの続きとして今回は、前回のROS2で「動かす」ために押さえておきたい考え方の続きです。前回では、ROS2 における座標系、オドメトリ、変換について、研究的な定義ではなく、エンジニアが「動かす」ための視点で整理しました。...
- 1回目:エンジニアとしてROS2を使う側としての理解(座標系・TFの位置づけなど)
-
-
/cmd_velを入力- 仮想的に自己位置(x, y, yaw)を計算
/odomを publish- ターミナルに表示される数値(ログ)だけで挙動を追う
-
この2回目の動作では数字だけの確認ですが、座標系の計算(ワールド座標と機体座標の関係)は十分に読み取れます。
そして今回は、その続きとして、TF(座標変換)を追加し、RViz2で座標系を可視化します。
環境はこれまで主に Humble で試していましたが、今回は ROS2 Jazzy を前提にします。
(WSLでの Jazzy 構築は別記事にしています。)
なお、Humbleで作ってテストしたノードは、Jazzyでも同様にビルド・実行できます(今回のノードは単純な計算ノードのため)。

今回のゴール
今回のゴールは「ロボットの箱モデルを表示すること」ではありません。
以下を最小構成で達成します。
teleop_twist_keyboardで/cmd_velを入力する- 仮想オドメトリーが
/odomを publish する - TF(
odom→base_link)を publish する - RViz2で 座標系(TF)とオドメトリー(矢印)が連動して動くことを確認する
URDF(RobotModel)は扱いません。座標系の理解に集中します。
以下のように確認できます。

なぜTFが必要か
前回のノードでは /odom を publish していました。
しかし、RViz2で「ロボットの向き」や「フレーム間の関係」を理解するためには、TF(座標変換)が必要になります。
今回追加するのは、以下の関係です。
odom → base_link
odom:ワールド基準(オドメトリ基準)の座標系base_link:ロボット本体(機体)の基準座標系
TFは「フレーム間の位置・姿勢(姿勢=向き)関係」を流す仕組みです。
/odom が「位置と姿勢」を持っていても、RVizが座標系として扱うためには TF が必要になります。
PythonノードへのTF追加
既存の virtual_odometry_node.py に TF broadcast を追加します。
(本記事では差分が分かる形で要点のみ記載します。)
ソース一式は以下で公開しています。
https://github.com/ISYNishida/ros2_virtual_odometry/tree/main
ノードビルド方法は以下の2回目を参照してください。
https://independence-sys.net/main/?p=7373
ここからvirtual_odometry_node.py への TF broadcast 追加方法です。
1) import を追加
from geometry_msgs.msg import TransformStamped
from tf2_ros import TransformBroadcaster
2) Broadcaster を初期化(__init__ に追加)
self.tf_broadcaster = TransformBroadcaster(self)
3) update_odometry() の最後で TF を送信
tf = TransformStamped()
tf.header.stamp = self.get_clock().now().to_msg()
tf.header.frame_id = 'odom'
tf.child_frame_id = 'base_link'
tf.transform.translation.x = self.x
tf.transform.translation.y = self.y
tf.transform.translation.z = 0.0
tf.transform.rotation = yaw_to_quaternion(self.theta)
self.tf_broadcaster.sendTransform(tf)
これで、仮想オドメトリーの計算結果(x, y, yaw)を、TFとしても配信できます。
補足:座標変換を自分で書く必要はあるのか?(tf2の実務的な位置づけ)
今回は学習目的として、オドメトリー計算(積分)を自前で書き、座標系の意味を確認しています。
一方で実務では、座標変換そのもの(フレーム間変換)は tf2 を利用して扱うのが一般的です。
重要なのは三角関数やクォータニオンを暗記することではなく、次の点です。
- どのフレーム(frame_id)からどのフレーム(child_frame_id)へ変換しているか
- そのTFツリーが破綻していないか(親が一意、循環しない)
- RViz/ナビゲーションが期待するフレーム構成になっているか
「tf2をうまく使うこと」が現場エンジニアの実務です。
その前提として、今回のように TFの意味と見え方を理解しておくことが重要だと考えています。
実行手順
ターミナルを3つ使うと分かりやすいです。
ターミナル①:仮想オドメトリーノード起動
ros2 run virtual_odometry virtual_odometry_node
ターミナル②:teleop起動(/cmd_vel入力)
ros2 run teleop_twist_keyboard teleop_twist_keyboard
ターミナル③:RViz2起動
rviz2
RViz2での可視化
1) Fixed Frame を設定
RViz2を起動したら、左上の Global Options で以下を設定します。
- Fixed Frame:
odom
ここがズレると表示が破綻します。まずは odom 固定で進めます。
2) 表示を追加(Add)
次のDisplayを追加します。
- TF(フレーム関係の可視化)
- Axes(ロボットの向きの可視化)
- Reference Frame:
base_link - (任意)Length:0.3 など
- Reference Frame:
- Odometry(位置と向きの可視化)
- Topic:
/odom - Shape:Arrow
- Topic:
- Grid(任意:ワールド平面の基準)
何が見えるか(「単に動く」ではなく、TFの表示として読む)
teleopでキー操作すると、Odometry(矢印)とAxes(base_linkの座標軸)が連動して動きます。
ここでは「動いた」で終わらず、TF表示としてどう読むかを整理します。
1) Axes(base_link)は「ロボット座標そのもの」
Axes を base_link に設定すると、
ロボットが今どちらを向いているかが視覚的に分かります。
回転(angular.z)を入れると、この軸がその場で回転します。
2) Odometry(矢印)は「位置 + 姿勢」
Odometry表示は、/odom に含まれる pose(位置・姿勢)を矢印として表現します。
前進(linear.x)で矢印が移動し、回転で向きが変わります。
3) TF表示は「フレームの関係」を示す
TFの表示で odom と base_link がつながっていることを確認します。
この関係(ツリー)が崩れると、RVizやナビゲーション系は正しく動作しません。
今回の最小構成では、まず odom → base_link が成立していればOKです。
ここまで確認できれば、座標系・オドメトリ・変換の基礎としては十分に前進しています。
まとめ
今回は、前回作成した仮想オドメトリーノードにTFを追加し、RViz2で座標系を可視化しました。
/cmd_vel入力に対し、仮想オドメトリが/odomを publish- 同時に TF(
odom → base_link) を publish - RViz2で TF / Axes / Odometry を追加し、座標系として挙動を読めるようにする
実務では、座標変換そのものは tf2 が担います。
一方で、フレームの設計(どのフレームを作り、どう繋ぐか)と、TFを読めることが重要になります。
本記事は、そのための「基礎の基礎」をRViz2で確認するステップです。
今回はここまでです。またいろいろと書きたいと思います。



