😊

MuJoCoによるロボットシミュレーション例

に公開

はじめに

以前MuJoCoを使用する授業のTAをしているときに、ネット上の実装例が少なく、フリー課題で苦労している学生が多くいました。そこで、他のMuJoCoの学習者にも役立ててもらおうと、いくつかの実装例を紹介したいと思います。

簡単!実践!ロボットシミュレーション内で紹介されているものを中心に、MuJoCoでの実装とその説明、動作例などを示します。ここでは、実装を中心に紹介するため、より詳細な設定や機能などについては公式ドキュメントこちらのQiita記事を参照してください。

環境

  • macOS 15.4.1
  • Python 3.13.3
  • MuJoCo 3.2.7

MuJoCoはバージョンアップによって破壊的な変更が加わることもあります。最新バージョンで動作しない場合は、上記のバージョンを使用してみてください。

基本事項

MuJoCoではモデルをXML形式で記述します。シミュレーションの表示はライブラリで用意されているものを利用したり、画像出力したものを表示したりなど様々な方法があります。今回はインタラクティブな動作や表示のカスタマイズを可能にするため、mujoco_viewerを元にしたビューワーを使用しました。

実装例

共通設定

MuJoCoでは、別のモデルファイルをインポートできます。共通の設定を記述したファイルを作成し、それぞれのモデルファイルでそのファイルをインポートする、という形で使用されることが(見ている中では)多いです。

以下に今回使用した共通の設定ファイルを示します。

https://github.com/maki8maki/MuJoCoExample/blob/main/assets/shared.xml

2〜13、23〜28行目は設定を記述しています。
15〜21行目は各要素のデフォルト設定を記述しています。モデルを定義するときにここでの設定が使用されます。ただ、同じ項目をモデルファイルで設定している場合は上書きされます。また、17行目のようにクラスを定義すると、クラス使用時のみデフォルト設定が有効になります。
30〜34行目はシミュレーション空間上に設置する床と照明の設定を記述しています。

以下には、それぞれで使用するビューワーの元となるクラスの定義を示します。

https://github.com/maki8maki/MuJoCoExample/blob/main/src/viewer.py

mujoco_viewerで定義されているクラスを継承し、新たにモデルに合わせたカメラ位置の変更や表示の変更ができるようにしています。
Viewerをさらに継承し、モデルや制御ごとに_key_callback関数や_callback関数をカスタマイズして使用することを想定しています。

ホッピングロボット

ここではホッピングロボットの実装について紹介します。上下の矢印キーを押すことで下の動画のようにジャンプします。

Hopper

モデルファイルを以下に示します。

https://github.com/maki8maki/MuJoCoExample/blob/main/assets/hopper.xml

2行目で上でも説明した共通の設定ファイルを読み込んでいます。
4〜16行目でシミュレーション空間に設置するホッピングロボットの定義をしています。足を2つの円柱(7,9行目)・頭部を球(12行目)で構成し、足はスライダージョイントで接続されています(10行目)。
19行目でスライダージョイントを動かすためのアクチュエータを定義しています。

詳細な説明

5行目では、全体のボディの基準を定義しています。今回は、空中に浮かせた状態から始めるために z = 0.5 としています。
6行目では、全体のボディと環境が固定されないことを明示しています。
7行目では、下側の足の定義をしています。fromto="x1 y1 z1 x2 y2 z2" とすることで、座標 (x1, y1, z1) と座標 (x2, y2, z2) をつなぐ円柱を作成できます。sizeでは半径を設定しています。posとsizeで起点と高さ・半径を設定する方法もありますが、こちらの方法は向きを考えなくてすむので楽だと思います。
8行目では、上側の足から上のボディの位置を定義しています。MuJoCoでは子の位置姿勢は親からの相対的な位置姿勢で考えます。今回は、下側の足から少し上に設定しています。
9行目では、下側の足と同じように上側の足を定義しています。わかりやすさのために色は変えています。
10行目では、下側と上側の足をつなぐジョイントを定義しています。今回は、スライダージョイントを使用し、移動範囲を制限しています。
11行目では、8行目と同じように頭部のボディの位置を定義しています。
12行目では、頭部を球で作成しています。
19行目では、スライダージョイントを動かすためのアクチュエータを定義しています。ギア比を1000に設定しています。この値は、適当に試行錯誤して決めました。

以下に実行するPythonファイルを示します。このファイルを実行すると、上の動画のように動作します。

https://github.com/maki8maki/MuJoCoExample/blob/main/src/hopper.py

5行目で読み込むMJCFファイルのパス、7〜12行目でカメラ位置の設定を指定しています。
15〜31行目でホッピングロボット用のビューワークラスを定義しています。19〜26行目が主要部分で、_key_callback関数を上書きしています。上下矢印キーが押されるとスライダージョイントのアクチュエータに力を加えます。self.dataMjData 型です。self.data.ctrl はアクチュエータに与える値のリストとなっており、アクチュエータを定義した順番とインデックスが対応します。今回は、アクチュエータが1つのみなので、インデックス0に値を代入します。

2輪差動駆動ロボット

ここでは2輪差動駆動ロボットの実装について紹介します。2輪差動駆動ロボットとは2輪の駆動輪とキャスターなどの補助輪で移動するロボットです。以下の動画が動作例となっています。上下の矢印キーで前後に移動し、左右の矢印キーで回転します。(摩擦の調整前のため、タイヤが空回りしています。)

Wheel

モデルファイルを以下に示します。

https://github.com/maki8maki/MuJoCoExample/blob/main/assets/wheel.xml

4~22行目でロボットの定義をしています。ボディ(7行目)、キャスター(9~12行目)、左右のタイヤ(13〜20行目)で構成されています。8行目にボディを追跡するカメラを定義しており、ロボットの移動に合わせてカメラも動きます。
24〜27行目でアクチュエータの定義をしており、左右のタイヤそれぞれの軸に回転方向の力を加えます。

詳細な説明

5行目は全体の位置の基準を設定しています。タイヤの分だけ浮かせています。
7行目はボディを定義しており、今回は直方体としています。
8行目はカメラを定義しています。mode="trackcom" とし、ボディを追従するように設定しています。
9〜11行目で補助輪であるキャスターを定義しています。形状を球とし、ballジョイントを設定することで、自由回転するようにしています。
13〜16行目で左側の駆動輪を定義しています。形状はシリンダです。タイヤが空回りしないように friction="3.0 と設定しています。MuJoCoで設定できる摩擦のパラメータは滑り摩擦、ねじれ摩擦、転がり摩擦ですが、このように指定すると、滑り摩擦のみが変更されて残りはデフォルト値が使用されます。今回は空回りの防止なので、滑り摩擦のみを大きくしています。実際のロボットの動作を再現する場合には、重量を適切に設定した上で、使用する床やタイヤの材質に合わせた摩擦設定が必要です。ジョイントはhingeジョイントを使いました。
右側の駆動輪は左側と基本的に同じです。
24〜26行目で左右の駆動輪を動かすためのアクチュエータを定義しています。

以下に実行するPythonファイルを示します。

https://github.com/maki8maki/MuJoCoExample/blob/main/src/wheel.py

19〜41行目でキーボードに関する動作を定義しています。
20〜23行目でタイヤの軸に加える力の大きさを定義しています。キーが押されているときは1.0、そうでないときは0.0としています。
以降の部分で矢印キーに応じた力のかけ方を定義しています。上矢印キーでは両輪に正の方向、下矢印キーでは両輪に負の方向の力を与えます。左右矢印キーでは左右のタイヤに反対方向の力を与え、車体が回転するようにしています。今回、両輪の中心を結んだ線と重心が交わらないため、その場で回転するにはより複雑な制御が必要です。

ドローン

ここではドローンの実装について紹介します。以下の動画は動作例です。上下の矢印キーでドローンが上下に移動し、何もしていないときはその場で停止するようになっています。

Drone

モデルファイルを以下に示します。

https://github.com/maki8maki/MuJoCoExample/blob/main/assets/drone.xml

4〜40行目でロボットの定義をしています。ボディ(13行目)、プロペラ(17〜27行目)で構成されています。ボディに追従するカメラ(16行目、上の動画では使用していない)も定義しています。
31〜36行目ではアクチュエータの定義をしています。今回は、簡略化のために、プロペラの中心に上向きの力を直接発生させています。
38〜40行目では速度センサーの定義をしています。MuJoCoではシミュレーションから得られるデータに全ての状態が含まれていますが、実際のロボット制御に少し寄せてセンサーから値を取得する方法を用いています。

詳細な説明

4〜8行目は共通設定のようにデフォルト設定を定義しています。ここでは、アクチュエータの定義を簡単にするための共通部分を定義しています。詳細はアクチュエータの箇所で述べます。
13〜14行目でボディの定義をしています。後々の制御の計算を楽にするため、mass="1" で質量を1kgに固定しています。
15行目はセンサーを取り付ける場所を定義しています。
16行目はボディに追従するカメラを定義しています。
17〜27行目でプロペラの定義をしています。 replicate で繰り返しの設定ができます。cout で繰り返し回数、euler で1回あたりの回転量を指定します。ここでは、z軸周りに-90度ずつ4回回転させることで、プロペラを配置しています。19〜20行目でボディとプロペラを繋ぐシャフト、23〜24行目でプロペラを定義しています。どちらも制御の計算を楽にするため、mass=0 で質量を0に設定しています。また、21行目にはアクチュエータを設置する場所を定義しています。
31〜36行目でアクチュエータの定義をしています。ここではreplicateを使用できないため、1つ1つ定義しています。アクチュエータのデフォルト設定で gear="0 0 1 0 0 0" を設定しており、Z軸正方向(上向き)に力が発生します。ctrlrange="0 5" として下向きに力が発生しないようにもしています。site で力を発生させる場所を指定します。replicateでは中のnameに自動で番号を振るので、site=thrust0 のように指定しています。
38〜40行目では速度センサーの定義をしています。

以下に実行するPythonファイルを示します。

https://github.com/maki8maki/MuJoCoExample/blob/main/src/drone.py

20〜23行目で共通して使用する定数を定義しています。STABLE_FORCEはドローンがその場で留まるために1つのプロペラが出すべき力を表しています。
30行目で制御に使用するPIDコントローラーを定義しています。simple-pidを使用しています。パラメータは適当に決めています。
32〜34行目で力を入力するユーティリティ関数を定義しています。
37〜44行目で上下の矢印キーが押された時の動作を定義しています。上下方向の目標加速度を出すための力にSTABLE_FORCEを足したものを出力すべき力としています。それ以外の場合では self.hovering = true とし、その場で留まるように制御します。これは次で述べます。
49〜57行目でシミュレーションの毎周期で実行される_callback関数を定義しています。self.hovering = true の場合は、センサーから取得したZ方向の速度がゼロになるようにPID制御しています。そうでない場合は、PIDコントローラーの中身をリセットするだけで、上で計算した力をそのまま使用します。

おわりに

この記事では簡易化したモデルでMuJoCoの実装例を紹介しました。より現実に近いロボットを使用したい場合は、公式ドキュメントのモデルギャラリーに掲載されているので参考にしてみてください。

参考

GitHubで編集を提案

Discussion