Pythonによる物理シミュレーション環境PyBulletの導入

記事
IT・テクノロジー
 本記事では、Pythonによる物理シミュレーション環境PyBulletのインストールから基本的な使い方までを紹介します。

PyBulletのインストール

 Pybulletのインストールは、コマンド操作によって行います。Pythonとpipがすでにインストールされている前提で進めていきます。まだPython及びpipをインストールされていない方は、Python及びpipのインストールから行ってください。
 まずはコマンドプロンプトを開き、以下のコマンドを実行してください。
  python -m pip install --upgrade pip
 pipコマンドとは、Pip Install Packagesの略称であり、pythonに公式に登録されたパッケージをインストールするためのものです。これをアップグレードしておかないと、古いバージョンのPybulletがインストールされる可能性があります。pipコマンドをアップグレードしたら、Pybulletをインストールしましょう。
 (Windowsの場合)
 pip install pybullet
 (Linuxの場合)
 sudo pip install pybullet
 “Installing collected packages: pybullet”と表示されれば、正しくインストールが開始されています。しばらく待ち、“Successfully installed pybullet-x.x.x(バージョン)”と表示されれば、インストール完了になります。
comand_successed.png

環境の設定

 インストールが完了したら、実際にPybulletを利用して物理エンジンに接続していきましょう。Pythonファイルを作成します。本書ではスクリプト名を“pybullet-project.py”とします。注意点として、名前をインポートするライブラリ名と同じにすると、スクリプト実行時にエラーとなるため、作成するスクリプトに“pybullet.py”といった名前を付けることは避けましょう。
 スクリプトを作成したら、さっそくコードを書いていきます。まずはPybulletライブラリを読み込みましょう。
 import pybullet as p
 基礎的な解説になりますが、“as”はライブラリ名を変えて利用できるようにするためのものです。“import A(ライブラリ) as B(変更名)”とすることで、AというライブラリをBという名前で使用することができるようになります。今回の場合であれば、pybulletをpという名前で利用できるようにしています。Pybulletライブラリを利用する際、毎回pybulletと打つのは大変なので、pと省略します。
 次に、pybullet_dataをインポートします。
 import pybullet_data
 pybullet_dataには、サンプルのロボットや床など、便利なデータが入っています。
 以上の2つのライブラリをインポートしたら、物理エンジンに接続するコードを書きます。また、シミュレーション時間を計測するためのtimeライブラリもインポートします。
 import time
 では、connect関数によって、物理エンジンに接続します。引数に“p.GUI”と入れることで、GUI (シミュレーション画面表示・キーボード操作)付きの物理エンジンに接続することができます。一方、“p.DIRECT”と入れると、GUIなしの物理エンジンに接続することができます。”p.DIRECT”は、パソコンの計算負担を減らすため、強化学習を行う際に使用します。まずは、GUI付きの物理エンジンに接続します。
 physicsClient = p.connect(p.GUI)
さきほど、pybullet_dataには様々なモデルが入っていると説明しました。しかしそのままでは、pybullet_data内のモデル使用することができません。パスを通すことで、pybullet_dataのを利用するためのコードを書きます。
 p.setAdditionalSearchPath(pybullet_data.getDataPath())
 パスを通したら、さっそくpybullet_dataから床のデータを利用しましょう。床のデータはURDFファイルの形式で記述されています。URDFファイルとは、ロボットなどの構造を記述するために使われるマークアップ言語(タグで囲むことで構造を表現)のことです。URDFファイルを読み込むためには、loadURDF関数を利用します。引数に読み込むファイル名を入れます。床のデータは“plane.urdf”です。
 planeID = p.loadURDF(“plane.urdf”)
 環境設定の仕上げに、重力をセットしましょう。setGravity関数を利用します。引数は3つであり、それぞれx、y、z方向に対応しています。重力加速度はz方向に-9.8m/s2なので、1、2つ目の引数に0、3つ目の引数に-9.8と入れます。
 p.setGravity(0, 0, -9.8)
 以上で基本的な環境の設定は完了になります。では実際に、while文の中でstepSimulation関数を実行することで、シミュレーションを動かしてみましょう。またこのとき、while文の上でtを宣言し、timeライブラリのsleep関数を利用してシミュレーション時間を計測しておきます。
  t= 0
  while True:
     p.stepSimulation()
     time.sleep(0.01)
   t += 0.01
 次の画面が表示されれば成功です。
pybullet.png


基本操作

yBullet上における基本操作を列挙します。実際に、Pybullet画面上でそれぞれの操作を試してみてください。
 マウスホイール
  視点のズームイン/アウト
 CTRL(またはAlt) + 左クリック + ドラッグ
  視点を回転させる
 CTRL(またはAlt) + ホイールボタン + ドラッグ
  視点を平行移動
 左クリック + ドラッグ
  物体のグリップ操作
G key
  UIを非表示
 W key
  フレーム表示
 S key
  光効果を消す
 V key
  各リンクを箱として粗野化

モデルの読み込み

重力の設定など、基本的なシミュレーション環境を整えたら、モデルを読み込んで見ましょう。ここでは、pybullet_dataに入っているサンプルの四輪走行モデル「racecar」を読み込みます。racecarは“racecar”という名前のフォルダに“racecar.urdf”という名前で入っています。loadURDF関数を使ってracecarという変数にモデルを読み込みましょう。
  racecar = p.loadURDF(“racecar/racecar.urdf”)
 以上のコードを実行し、下図のモデルが表示されれば成功です。

racecar.png

モデルの情報を取得

 まずはracecarのジョイントの数を調べましょう。そのためにgetNumJoints関数を利用します。この関数は、引数に調べたいモデルを入れることで、ジョイントの数を出力する関数になります。
print(p.getNumJoints(racecar))
 以上のコードを実行するとracecarのジョイント数の10が出力されます。
 次に、各ジョイントの詳細について、getJointInfo関数を利用して調べます。引数は2つあり、1番目に調べたいモデル、2番目に調べたいジョイントの番号を入れます。ここでは全てのジョイントの情報について知りたいため、for文を用いて次のように書きます。
for i in range(p.getNumJoints(racecar)):
     print(p.getJointInfo(racecar, i))
 すると、ターミナルに次のような各ジョイントの情報が出力されます。
jointInfo.png
たくさんの出力が返ってきます。すべての項目を確認するのは難しいので、抑えて見るべき箇所を絞りましょう。まずは簡単に、ここで見ていただきたいのは、引数の1、2、3、13番目の6つの数字です。それぞれの意味は、以下のようになっています。


 引数1:ジョイントインデックス
 引数2:ジョイント名
 引数3:ジョイントタイプ
  [0:REVOLUTE, 1:PRISMATIC, 2:SPHERICAL, 3:PLANAR, 4:FIXED]
 引数13:リンク名

 引数3のジョイントタイプの種類について簡単に説明します。REVOLUTE(0)は制限範囲内で軸方向に回転するジョイントです。PRISMATIC(1)は軸方向にスライドするジョイントです。SPHERICAL(2)は6自由度を持つ球面ジョイントです。PLANAR(3)は制限なく軸方向に回転するジョイントです。FIXED(4)は固定されたジョイントです。
 以上を踏まえ、1行目のジョイントにおいてそれぞれの情報を読み取ってみると、次のようになります。

〇1行目のジョイント情報
 ジョイントインデックス:0
 ジョイント名     :base_link_joint(ベースリンクジョイント)
 ジョイントタイプ   :4 (FIXED, 固定ジョイント)
 リンク名       :chassis (シャーシ)

 以上の5つの情報について、適宜それぞれのジョイントにおいて調べることで、モデル全体の構造や動きを掴むことができます。モデルの操作として重要な車輪ジョイントはインデックスの2、3、5、7に当たり、それぞれleft_rear_wheel_joint、right_rear_wheel_joint、left_front_wheel_joint、right_front_wheel_jointという名前が付けられています。また、ステアリングジョイントはインデックスの4、6に当たり、それぞれleft_steering_hinge_joint、right_steering_hinge_jointという名前が付けられています。

モデルの操作

 実際にモデルを操作してみましょう。setMotorControl2関数を利用してジョイントを動かします。引数には必須として1つ目に操作するモデル、2つ目に操作するジョイントのインデックス、3つ目にコントロールモードを渡します。コントロールモードにはPOSITION_CONTROL、VELOCITY_CONTOROL、TORQUE_CONTROLがあります。それぞれ位置、速度もしくはトルクによる制御方法を選ぶことができますが、基本となるのは位置制御のPOSITION_CONTROLになります。位置制御はその名の通り位置を指定して制御する方法になります。
 以上の3つの引数を渡すだけでは、エラーは起きませんがモデルは何も動きません。オプションとして他の引数を渡す必要があります。一つ目は、変化量を指定することによるモデルの操作です。steeringAngleという変数を宣言し、0.785を代入します。角度を指定するときの単位は[rad]です。0.785[rad]は約45度になります。
  steeringAngle = 0.785
 次に、racecarのステアリングジョイントである4,6のインデックスをsteeringLinksという変数にまとめます。
  steeringLinks = [4, 6]
 以上の変数を用いて、setJointMotorControl2関数により前輪二つを45度回転させます。以下のコードは、while文の中に書いてください。
  for steer in steeringLinks:
       p.setJointMotorControl2(racecar,
                                           steer,
                                           controlMode=p.POSITION_CONTROL,
                                           targetPosition = steeringAngle)
 以上のプログラムを実行すると、車輪ロボットracecarの左前車輪と右後車輪がそれぞれ約45度回転するのが確認できます。
 次に、車輪を回転させてracecarを動かしていきます。シャーシにおいては、角度を制御するためにcontorlModeにPOSITION_CONTROLを引数で与え、位置を指定することで制御しましたが、車輪においては回転させることで制御するため、controlModeにVELOCITY_CONTROLを引数で与え、速度を指定することで制御していきます。
まずはタイヤの回転速度を60[m/s]と指定して、targetVelocityという変数に代入します。
  targetVelocity = 60
 次に車輪のジョイントインデックスをひとつの変数にまとめましょう。車輪のジョイントインデックスは2、3、5、7です。これらをwheelLinksという変数に代入します。
  wheelLinks = [2, 3, 5, 7]
 ステアリングを制御したときと同様に、for文を用いて次のように記述します。
  for wheel in wheelLinks:
       p.setJointMotorControl2(racecar,
                                           wheel,
                                           controlMode=p.VELOCITY_CONTROL,
                                           targetVelocity= targetVelocity)
 以上のコードを実行すると、racecarが動き始めるのが確認できます。
racecar_test-drive.png


コード全文

import pybullet as p
import pybullet_data
import time

physicsClient = p.connect(p.GUI)  # 物理エンジンに接続
p.setAdditionalSearchPath(pybullet_data.getDataPath())  # パスを追加
p.setGravity(0, 0, -9.8)  # 重力の設定

planeID = p.loadURDF("plane.urdf") # 床の読み込み
racecar = p.loadURDF("racecar/racecar.urdf") # 車モデルの読み込み

# 制御方法
steeringLinks = [4, 6]       # ステアリングリンク
wheelLinks = [2, 3, 5, 7]  # ウィールリンク
steeringAngle = 0.785      # 目標角度(45[deg])
targetVelocity = 60           # 目標速度(60[rad/s])

t = 0  # シミュレーション時間

while True:
  p.stepSimulation()
  t += 0.01
  time.sleep(0.01)

  for steer in steeringLinks:
    p.setJointMotorControl2(racecar,
                                        steer,
                                        controlMode=p.POSITION_CONTROL,
                                        targetPosition = steeringAngle)

  for wheel in wheelLinks:
      p.setJointMotorControl2(racecar,
                                          wheel,
                                          controlMode=p.VELOCITY_CONTROL,
                                          targetVelocity= targetVelocity)

PyBulletで行う車モデルの強化学習

 今回用いた車モデルを使用し、PyBullet上で強化学習を行うコードを販売しています。

PyBulletで行うアームロボットの制御

 アームロボットを使用し、PyBullet上で逆運動学による制御を行うコードを販売しています。

Pythonのコーディング

 Pythonコードのバグ修正・開発を承っています。

サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す