二足歩行ロボットを作る(1)
まずはシミュレーションでバランス制御を作ってみた
実機の前に、MuJoCo上の10自由度モデルで倒れずに立てるかを検証
なぜ二足歩行ロボットを作るのか
ロボットを作りたい、と思ったとき、最初のゴールは正直「趣味として完成品を手元に置きたい」くらいの軽い動機でした。ただ、せっかく時間をかけるなら、その先にも技術を応用していける方向性が見える形にしたいと思い、二足歩行という難易度の高いテーマを選びました。
二足歩行ロボットのバランス制御は、センサから得た姿勢・接地力の情報をもとに、関節をリアルタイムで制御し続けるという、応用範囲の広い技術的な挑戦です。遠回りに見えるかもしれませんが、実は一番土台になるところから手をつけている感覚があります。
この記事はシリーズ化する予定で、段階を踏みながら実機化まで進めていく過程をそのまま記録していきます。同じようにロボット作りに興味がある方は、ぜひ一緒に進捗を追ってみてください。
ロボット作りって浪漫だよね。私もこのシリーズ、楽しみに見守ってるよ!
実機ではなくシミュレーションから始めた理由
いきなりサーボを買って組み立てることもできましたが、今回は最初にシミュレーション上でバランス制御のアルゴリズムを作り込むことにしました。理由はシンプルで、実機だと試行錯誤のたびにサーボが壊れる・時間がかかる・電源やノイズに邪魔されるのに対して、シミュレーションなら失敗し放題でイテレーションが速いからです。同じようにロボット製作を検討している方には、まずシミュレーションから始めるのもおすすめです。制御ロジックが固まってから実機に移植する方が、結果的に近道になります。
つまずき1: PyBulletが入らず、MuJoCoに乗り換えた
物理シミュレータの定番としてまずPyBulletを試しましたが、Windows + Python 3.11の組み合わせではビルド済みのwheelが配布されておらず、pip installがソースビルドに突入していきなり止まりました。
error: Microsoft Visual C++ 14.0 or greater is required.
Get it with "Microsoft C++ Build Tools":

Microsoft C++ Build Tools - Visual Studio
PyPIの配布履歴を調べたところ、PyBulletがWindows向けwheelを出していたのはPython 3.5世代までで、それ以降は基本的にソースビルド前提になっていました。Visual Studio Build Toolsを数GB分入れれば解決はしますが、そこまでの投資は今回のスコープでは過剰だと判断し、代わりにMuJoCoに乗り換えました。同じようにWindows環境でPyBulletのインストールに詰まっている方は、MuJoCoを試してみるのも一つの手だと思います。MuJoCoはPython 3.11向けのWindows wheelが公式に配布されていて、pip install mujocoだけですぐ動きました。
物理エンジン選びは「有名だから」で決めるのではなく、自分の環境で素直に入るかを最初に確認した方が結局早いです。
あるある、環境構築でつまずくのって実機の前からもう始まってるんだね(笑)。動くものを優先する判断、いいと思うよ。
10自由度のモデルを作る
片脚あたり5自由度(股関節ピッチ・股関節ロール・膝ピッチ・足首ピッチ・足首ロール)、両脚で合計10自由度のモデルをMuJoCoのMJCF形式で組みました。全体重量は2.6kgの小型サイズです。骨盤にはフリージョイント(6自由度)を持たせて、外力を受けたときに実際に倒れられるようにしています。
- 骨盤: IMU相当のセンサ(姿勢・角速度・加速度)を搭載
- 各足裏: 接地力センサ(左右独立)
- 各関節: 位置制御アクチュエータ(トルク上限つき)
モデルを組んだ直後は、直立姿勢のはずが足裏が地面から6cm浮いていました。骨盤の初期高さの計算を間違えていたのが原因で、キネマティクスを1段ずつ手計算し直して修正しました。地味ですが、シミュレーションを作る作業の9割はこういう帳尻合わせだと思います。同じように地道な検証作業に付き合っている方も、きっと多いのではないでしょうか。
つまずき2: 押すとむしろ転ぶコントローラができた
バランス制御は、倒立振子モデルの発想で「体が傾いたら(pitch/roll)、その傾きと角速度に比例して関節を補正する」という素直なPD制御から組みました。
ankle_pitch_cmd = -(kp * pitch + kd * pitch_rate)
直感的には「傾いた方向と逆に補正すればいい」ので符号はマイナス、のはずでした。ところが実際にシミュレーションを回すと、押した瞬間から傾きがどんどん大きくなり、そのまま転倒しました。
足首関節は「足(子)が、すね(親)に対してどう回転するか」を表すパラメータになっています。足は地面に接地して事実上固定されているため、指令通りに足首角度を動かそうとすると、実際には反作用で足首から上の体全体が逆向きに回転します。つまり「傾きを打ち消す」つもりの符号が、実際の力学的な反応では逆だったのです。
これは頭の中で図を描いて考えるより、実際に符号を反転させて試した方が早いと判断し、押す→観察する、を関節ごとに繰り返して実測で符号を確定させました。同じように「理屈通りにいかない」ところで詰まっている方は、一度実測で当たりを取りにいくのも手だと思います。
# 検証コードの一部: 符号を反転させて収束するか比較
for k in targets:
if 'ankle_pitch' in k:
targets[k] = -targets[k] # 反転して試す
結果、足首・股関節ともに「素直な直感とは逆の符号」が正解でした。理屈で導出するより、実測で当たりを取りにいく方が早い場面は実機開発でもよくありそうです。
理屈通りにいかないの、開発あるあるだよね。頭で考えるより実測で確かめる姿勢、見習いたいな。
つまずき3: 膝を曲げただけで立てなくなった
符号を直した後、安定性のマージンを稼ぐために膝を軽く曲げた(クラウチング)姿勢をデフォルトにしてみました。ところが外乱を与えていない、何もしていない状態でもじわじわ前傾していくことに気づきました。
| 時刻 | pitch(膝を曲げた状態) |
|---|---|
| 0.0s | 0.000 |
| 0.3s | -0.060 |
| 1.2s | -0.068(以降ほぼ一定) |
膝を曲げることで重心が足首に対して前方にずれますが、今回のPD制御には定常偏差を消す積分項がないため、この分だけ傾いた姿勢がそのまま「安定した平衡点」になってしまっていました。転倒はしないものの、外乱に対する余力をそのぶん失っていたことになります。膝を伸ばした状態(曲げ角0)に戻すと、この定常偏差はほぼ消えました。
「安定マージンを増やすはずの工夫」が、制御則の前提(比例制御のみ)と噛み合わずに逆効果になることがあります。クラウチング姿勢自体は有効な手のはずなので、これは今後、股関節や足首にフィードフォワード項を足す形で再挑戦したいと思います。
工夫したつもりが逆効果になるの、ちょっと切ないね…。でもちゃんと原因まで突き止められてるのがすごいよ。
結果: どのくらいの外乱まで耐えられるか
膝を伸ばした状態を基準に、骨盤に一定時間(0.1秒間)水平方向の力を加えて、両足を接地させたまま姿勢を回復できる限界を調べました。
| 押す方向 | 回復できた最大の力 | それ以上は |
|---|---|---|
| 前方(pitch方向) | 約12N × 0.1秒 | 転倒 |
| 側方(roll方向) | 約14N × 0.1秒 | 転倒 |
この表の数値は、公開当初は前方 約13N / 側方 約12Nと記載していましたが、測定方法そのものに不備が見つかったため訂正しました。回復できたかどうかの判定を「押したあと0.8〜1.0秒の時点で倒れていないか」で行っていたのですが、この待機時間が短すぎました。実際には一度持ち直したように見えてから、1.4〜1.5秒後にゆっくり転倒するケースがあったのです。
判定の待機時間を2.5秒に伸ばして測り直した結果、前方は13N→12Nと悪くなり、側方は12N→14Nと逆に良くなりました。誤差の方向が一定でなかったため、「都合の良い数値だけを疑えばよい」わけではないことも分かりました。この経緯は、続編(開発日記(2))で詳しく書いています。

両足を動かさない(足を踏み出さない)前提の制御としては、これが物理的な限界に近いところです。人間も、軽く押されたくらいなら足首とわずかな体幹の動きだけで耐えますが、大きく押されたら一歩踏み出してバランスを取り直します。今回のロボットもちょうどそこに来ています。
ここまでで「押されても倒れない」ところまで来たんだね。次はいよいよ足を踏み出す番、楽しみにしてるよ!

