ロボットを作って仮想空間で動かしてみよう!【ROS2初心者向けガイド】

工具

こんにちは!今回はロボット開発で使われている「ROS2(Robot Operating System 2)」について、超初心者向けに解説しながら、実際にロボットを仮想空間で動かすところまでを一緒にやってみましょう。


そもそもROS2って何?

ROS2は「ロボットを制御するためのオープンソースのフレームワーク」です。難しく聞こえるかもしれませんが、要は「ロボットを作るときの便利な道具箱」みたいなものです。

ROS2を使えば、

  • センサー情報の取得
  • モーターの制御
  • ロボットの位置把握(SLAM)
  • 経路計画

など、ロボット開発に欠かせない機能を簡単に組み合わせて作ることができます。


ROS2でできることのイメージ

ROS2は単体では動きません。Gazebo(またはIgnition/Gazebo)などのシミュレーターと連携することで、仮想空間でロボットを走らせることができます。つまり、現実のロボットを用意しなくても、開発や検証ができるというわけです。自宅や工場のレイアウトやロボットをパソコン上で作成して、シミュレーションができます。

実際に実機を動かすことも可能で、ROS2でリモート操作や監視、他の設備と自動連係といったこともできます。


開発環境

この記事の内容は、下記環境で作成しました。異なる環境でもできるかもしれませんが試していないのではわかりません。
OS:Ubuntu 24.04 64bit
ROS2バージョン:ROS 2 Jazzy Jalisco
Gazeboバーション:Gazebo Sim 8.9.0
Pythonバージョン:Python 3.12.3

まずは「仮想ロボット」を作ってみよう!

今回は「SDF(Simulation Description Format)」という形式を使って、シンプルな差動二輪ロボットを作っていきます。

🔧 SDFファイルとは?

SDFとは、Gazeboなどのシミュレーターにロボットや環境の情報を伝えるためのXML形式のファイルです。ロボットの形や動き、部品のサイズ、質量などを記述できます。



インストールからシミュレーションまでの流れ(GazeboでSDFを動かす)

  1. Gazeboをインストール(Ignition Gazebo推奨) sudo apt install ros-humble-ros-gz-sim
  2. SDFファイルを保存 simple_robot.sdfという名前で保存しましょう。
  3. Gazeboで起動 gz sim simple_robot.sdf
  4. 起動後、画面上にロボットが現れれば成功です!

→ ロボットが前進するはずです!


実際にSDFを書いてみよう!

以下は、差動二輪ロボット(左右2つの車輪+キャスター)の例です。このファイルをGazeboで読み込めば、ロボットが地面に出現します!とりあえずテキストにコピペして、simple_robot.sdfというファイル名で保存してください。各パーツにコメントをつけているので、読みながら構造を理解してみてください。

<?xml version="1.0" ?>
<!-- シンプルな差動二輪ロボットのSDFファイル -->
<sdf version="1.8">
    <world name="simple_world">
        <!-- 物理シミュレーションの設定 -->
        <physics name="1ms" type="ignored">
            <max_step_size>0.001</max_step_size>    <!-- シミュレーションの時間刻み -->
            <real_time_factor>1.0</real_time_factor>    <!-- リアルタイム係数 -->
        </physics>

        <!-- 必要なプラグインの読み込み -->
        <plugin filename="gz-sim-physics-system" name="gz::sim::systems::Physics"/>
        <plugin filename="gz-sim-scene-broadcaster-system" name="gz::sim::systems::SceneBroadcaster"/>
        <plugin filename="gz-sim-user-commands-system" name="gz::sim::systems::UserCommands"/>

        <!-- 太陽光源の設定 -->
        <light type="directional" name="sun">
            <cast_shadows>true</cast_shadows>
            <pose>0 0 10 0 0 0</pose>    <!-- 光源の位置 -->
            <diffuse>0.8 0.8 0.8 1</diffuse>    <!-- 拡散光 -->
            <specular>0.2 0.2 0.2 1</specular>    <!-- 鏡面光 -->
            <attenuation>
                <range>1000</range>
                <constant>0.9</constant>
                <linear>0.01</linear>
                <quadratic>0.001</quadratic>
            </attenuation>
            <direction>-0.5 0.1 -0.9</direction>    <!-- 光の方向 -->
        </light>

        <!-- 地面モデルの設定 -->
        <model name="ground_plane">
            <static>true</static>
            <link name="link">
                <collision name="collision">
                    <geometry>
                        <plane>
                            <normal>0 0 1</normal>
                        </plane>
                    </geometry>
                </collision>
                <visual name="visual">
                    <geometry>
                        <plane>
                            <normal>0 0 1</normal>
                            <size>100 100</size>    <!-- 地面の大きさ -->
                        </plane>
                    </geometry>
                    <material>
                        <ambient>0.8 0.8 0.8 1</ambient>
                        <diffuse>0.8 0.8 0.8 1</diffuse>
                        <specular>0.8 0.8 0.8 1</specular>
                    </material>
                </visual>
            </link>
        </model>

        <!-- ロボット本体の設定 -->
        <model name="simple_robot">
            <pose>0 0 0.1 0 0 0</pose>    <!-- ロボットの初期位置 -->

            <!-- ベースリンク(本体) -->
            <link name="base_link">
                <inertial>
                    <mass>1.0</mass>    <!-- 本体の質量 -->
                    <inertia>    <!-- 慣性モーメント -->
                        <ixx>0.0835</ixx>
                        <ixy>0</ixy>
                        <ixz>0</ixz>
                        <iyy>0.1209</iyy>
                        <iyz>0</iyz>
                        <izz>0.1209</izz>
                    </inertia>
                </inertial>
                <visual name="visual">
                    <geometry>
                        <box>
                            <size>0.5 0.3 0.1</size>    <!-- 本体のサイズ -->
                        </box>
                    </geometry>
                    <material>
                        <ambient>0 0 1 1</ambient>    <!-- 青色 -->
                        <diffuse>0 0 1 1</diffuse>
                        <specular>0 0 1 1</specular>
                    </material>
                </visual>
                <collision name="collision">
                    <geometry>
                        <box>
                            <size>0.5 0.3 0.1</size>
                        </box>
                    </geometry>
                </collision>
            </link>

            <!-- 左車輪 -->
            <link name="left_wheel">
                <pose relative_to="base_link">0 0.175 0 -1.5708 0 0</pose>    <!-- 左車輪の位置 -->
                <inertial>
                    <mass>0.5</mass>    <!-- 車輪の質量 -->
                    <inertia>
                        <ixx>0.00125</ixx>
                        <ixy>0</ixy>
                        <ixz>0</ixz>
                        <iyy>0.00125</iyy>
                        <iyz>0</iyz>
                        <izz>0.002</izz>
                    </inertia>
                </inertial>
                <visual name="visual">
                    <geometry>
                        <cylinder>
                            <radius>0.1</radius>    <!-- 車輪の半径 -->
                            <length>0.05</length>    <!-- 車輪の幅 -->
                        </cylinder>
                    </geometry>
                    <material>
                        <ambient>0 0 0 1</ambient>    <!-- 黒色 -->
                        <diffuse>0 0 0 1</diffuse>
                        <specular>0 0 0 1</specular>
                    </material>
                </visual>
                <collision name="collision">
                    <geometry>
                        <cylinder>
                            <radius>0.1</radius>
                            <length>0.05</length>
                        </cylinder>
                    </geometry>
                </collision>
            </link>

            <!-- 右車輪(左車輪と同様の設定) -->
            <link name="right_wheel">
                <pose relative_to="base_link">0 -0.175 0 -1.5708 0 0</pose>    <!-- 右車輪の位置 -->
                <inertial>
                    <mass>0.5</mass>
                    <inertia>
                        <ixx>0.00125</ixx>
                        <ixy>0</ixy>
                        <ixz>0</ixz>
                        <iyy>0.00125</iyy>
                        <iyz>0</iyz>
                        <izz>0.002</izz>
                    </inertia>
                </inertial>
                <visual name="visual">
                    <geometry>
                        <cylinder>
                            <radius>0.1</radius>
                            <length>0.05</length>
                        </cylinder>
                    </geometry>
                    <material>
                        <ambient>0 0 0 1</ambient>
                        <diffuse>0 0 0 1</diffuse>
                        <specular>0 0 0 1</specular>
                    </material>
                </visual>
                <collision name="collision">
                    <geometry>
                        <cylinder>
                            <radius>0.1</radius>
                            <length>0.05</length>
                        </cylinder>
                    </geometry>
                </collision>
            </link>

            <!-- キャスター(補助輪) -->
            <link name="caster">
                <pose relative_to="base_link">-0.2 0 -0.05 0 0 0</pose>    <!-- キャスターの位置 -->
                <inertial>
                    <mass>0.1</mass>    <!-- キャスターの質量 -->
                    <inertia>
                        <ixx>0.0001</ixx>
                        <ixy>0</ixy>
                        <ixz>0</ixz>
                        <iyy>0.0001</iyy>
                        <iyz>0</iyz>
                        <izz>0.0001</izz>
                    </inertia>
                </inertial>
                <collision name="collision">
                    <geometry>
                        <sphere>
                            <radius>0.05</radius>    <!-- キャスターの半径 -->
                        </sphere>
                    </geometry>
                </collision>
                <visual name="visual">
                    <geometry>
                        <sphere>
                            <radius>0.05</radius>
                        </sphere>
                    </geometry>
                    <material>
                        <ambient>0.8 0.8 0.8 1</ambient>    <!-- グレー色 -->
                        <diffuse>0.8 0.8 0.8 1</diffuse>
                        <specular>0.8 0.8 0.8 1</specular>
                    </material>
                </visual>
            </link>

            <!-- 左車輪のジョイント -->
            <joint name="left_wheel_joint" type="revolute">
                <parent>base_link</parent>
                <child>left_wheel</child>
                <axis>
                    <xyz>0 0 1</xyz>    <!-- 回転軸 -->
                    <limit>
                        <lower>-1.79769e+308</lower>    <!-- 回転制限なし -->
                        <upper>1.79769e+308</upper>
                    </limit>
                </axis>
            </joint>

            <!-- 右車輪のジョイント -->
            <joint name="right_wheel_joint" type="revolute">
                <parent>base_link</parent>
                <child>right_wheel</child>
                <axis>
                    <xyz>0 0 1</xyz>
                    <limit>
                        <lower>-1.79769e+308</lower>
                        <upper>1.79769e+308</upper>
                    </limit>
                </axis>
            </joint>

            <!-- キャスターのジョイント(固定) -->
            <joint name="caster_joint" type="fixed">
                <parent>base_link</parent>
                <child>caster</child>
            </joint>

            <!-- 差動駆動システムのプラグイン -->
            <plugin filename="gz-sim-diff-drive-system" name="gz::sim::systems::DiffDrive">
                <topic>cmd_vel</topic>    <!-- 速度指令のトピック名 -->
                <left_joint>left_wheel_joint</left_joint>    <!-- 左車輪のジョイント名 -->
                <right_joint>right_wheel_joint</right_joint>    <!-- 右車輪のジョイント名 -->
                <wheel_separation>0.35</wheel_separation>    <!-- 車輪間距離 -->
                <wheel_radius>0.1</wheel_radius>    <!-- 車輪半径 -->
                <odom_publish_frequency>50</odom_publish_frequency>    <!-- Odometry公開の頻度 -->
            </plugin>
        </model>
    </world>
</sdf>

ロボットを動かしてみよう!

端末を起動し、gz sim の後にファイルをSDFファイルを指定し起動してください。

gz sim "保管したSDFファイル名"

Gazeboが起動しロボットが表示されます。
これだけでは操作できません。
キーボードの操作と関連付けが必要になります。
次にLanchファイルを作成します。
内部ではpythonで動きますので拡張子はpyになります。
以下のコードをテキストエディタにコピペし、run_robot.pyといったファイル名で保存してください。

from launch import LaunchDescription
from launch.actions import ExecuteProcess

def generate_launch_description():
    return LaunchDescription([
        # Gazeboの起動
        ExecuteProcess(
            cmd=['gz', 'sim', 'simple_robot.sdf'],
            output='log',
            log_cmd=True
        ),

        # ブリッジの起動
        ExecuteProcess(
            cmd=[
                'ros2', 'run', 'ros_gz_bridge', 'parameter_bridge',
                '/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist'
            ],
            output='log',
            log_cmd=True
        ),

        # teleop_twist_keyboardの起動
        ExecuteProcess(
            cmd=['ros2', 'run', 'teleop_twist_keyboard', 'teleop_twist_keyboard'],
            output='screen',
            prefix='xterm -e'
        ),
    ])

新たに端末を追加で起動し、上記ファイルを以下のように指定してください。

ros2 run "Lanchファイル名"

上記の端末をアクティブにした状態で、キーボードのIや<を押してみてください。
最初に起動したGazeboのシミュレーション画面でロボットが前後に動くはずです。
Kを押すと停止します。U、O、M、>は大きく旋回、JやLはその場で回ります。

SDFファイルを修正することで、ロボットのサイズや動作、フィールドの広さなどが変更できます。
また、ロボットの追加やロボット以外の物(壁や棚など)も追加することができます。
まずはSDFファイルの中身を読んで、修正しながら理解していくのが良いと思います。


まとめ

  • ROS2はロボット開発のためのフレームワーク
  • SDFでロボットの形や動きを記述できる
  • Gazeboで仮想的にロボットを動かして試せる
  • cmd_velトピックで速度制御ができる

次回は、このロボットにセンサーをつけたり、自動で動かしたりする方法を紹介していきます!


参考リンク


コメント

タイトルとURLをコピーしました