ゆるふわフィジカルAI
パソコンで作業中のゆるロボ

RRTをPythonで実装する|ロボットが「狭い隙間」を抜ける経路計画

2026-11-02 ・ 実践

#RRT#経路計画#Python#実践

RRT(Rapidly-exploring Random Tree、高速探索ランダム木) とは、ランダムに打った点へ向けて木を少しずつ伸ばし、障害物を避けながらスタートからゴールまでの経路を探す経路計画アルゴリズムです。A*はマス目(グリッド)の上で最短経路を探しましたが、ロボットアームの関節角度のような連続値の空間では、マス目に区切ること自体が重くなります。この記事ではRRTをPythonでゼロから実装し、実際に経路を見つけるところまで動かします。

この記事で分かること

  • RRTとは、ランダムな点へ向けて木を少しずつ伸ばし、障害物を避けながら経路を探すアルゴリズム。LaValleが1998年に発表した
  • グリッド化が重くなる連続・高次元の空間(ロボットアームの関節角度など)で、A*より扱いやすい
  • 見つかる経路は最短とは限らない。最適性を求める改良版RRT*(Karaman & Frazzoli, 2011)も存在する

対象読者:A*などの経路探索を学んだあと、連続空間での経路計画に進みたい学習者・エンジニア

執筆:ゆるふわフィジカルAI編集部 最終更新日:2026年11月2日

RRTの1ステップ

🎲

点を打つ

空間からランダムに1点

→
📍

最近傍を探す

木の中で一番近いノード

→
🌱

少し伸ばす

そちらへ一定の歩幅だけ進む

→
✅

衝突確認

通れたら木に追加

なぜグリッドではなくRRTなのか

A*は空間をマス目に分割して探索します。2次元の地図ならマス目は現実的な数で済みますが、6自由度のロボットアームを「関節角度6個の空間」として扱うと、1軸あたり100分割しただけで100の6乗(1兆通り)のマスができてしまい、グリッド化が事実上不可能になります。RRTはマス目を作らず、空間からサンプリング(ランダムに点を抜き出す)するだけなので、次元が増えても扱いやすいのが利点です。

💡

トレードオフもある

その代わりRRTが見つける経路は、A*のような最短経路の保証がありません。「通れる経路を素早く見つける」ことを優先した手法です。

準備

ターミナル
pip install numpy

標準ライブラリの math と random も使います。

① 空間と障害物を決める

2次元の10×10の空間に、円形の障害物をいくつか置きます。

Python
import random
import math
import numpy as np
 
random.seed(7)
 
# 障害物: (中心x, 中心y, 半径)
obstacles = [(5, 5, 1.5), (3, 7, 1.0), (7, 3, 1.2), (6, 7.5, 0.8)]
START = (0.5, 0.5)
GOAL = (9.5, 9.5)
BOUNDS = (0, 10)
STEP = 0.5          # 1回に伸ばす歩幅
GOAL_RADIUS = 0.5    # ここまで近づけばゴール扱い
MAX_ITERS = 5000
 
 
def collides(p):
    for cx, cy, r in obstacles:
        if math.hypot(p[0] - cx, p[1] - cy) <= r:
            return True
    return False
 
 
def segment_collides(a, b, samples=10):
    for i in range(samples + 1):
        t = i / samples
        p = (a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t)
        if collides(p):
            return True
    return False

collides は1点が障害物の中にあるかを見るだけの判定です。木を1本伸ばすたびに、その線分が障害物を突き抜けていないかも segment_collides で細かく確認します。点だけ見ると、障害物をすり抜けて反対側に着地してしまうことがあるためです。

② 木を伸ばす(steer)

ランダムな点のほうへ、歩幅 STEP の分だけ進んだ新しい点を返します。ランダム点がすでに近ければ、そのまま使います。

Python
def steer(a, b, step):
    dx, dy = b[0] - a[0], b[1] - a[1]
    dist = math.hypot(dx, dy)
    if dist <= step:
        return b
    return (a[0] + dx / dist * step, a[1] + dy / dist * step)
🌱

一歩ずつしか伸ばさない理由

ランダム点まで一気に直線で結ぶと、途中の障害物を見逃しやすくなります。歩幅を小さく刻んで少しずつ伸ばし、そのたびに衝突確認することで、木全体が安全な経路だけで構成されます。

③ RRT本体

木(ノードの配列と、それぞれの親)を持ち、ループのたびに「ランダム点 → 最近傍 → 伸ばす → 衝突確認 → 木に追加」を繰り返します。5%の確率でゴール自体をランダム点として選ぶゴールバイアスを入れると、収束が早くなります。

Python
nodes = [START]
parents = {0: None}
 
goal_reached_idx = None
for it in range(MAX_ITERS):
    if random.random() < 0.05:
        rnd = GOAL
    else:
        rnd = (random.uniform(*BOUNDS), random.uniform(*BOUNDS))
 
    dists = [math.hypot(rnd[0] - n[0], rnd[1] - n[1]) for n in nodes]
    nearest_idx = int(np.argmin(dists))
    nearest = nodes[nearest_idx]
 
    new_node = steer(nearest, rnd, STEP)
    if collides(new_node) or segment_collides(nearest, new_node):
        continue
 
    nodes.append(new_node)
    new_idx = len(nodes) - 1
    parents[new_idx] = nearest_idx
 
    if math.hypot(new_node[0] - GOAL[0], new_node[1] - GOAL[1]) <= GOAL_RADIUS:
        goal_reached_idx = new_idx
        break

④ 実行して経路を見る

ゴールに着いたノードから親をたどって戻れば、スタートからの経路が復元できます。

Python
print(f"サンプリング回数: {it + 1}")
print(f"木のノード数: {len(nodes)}")
 
if goal_reached_idx is not None:
    path = []
    idx = goal_reached_idx
    while idx is not None:
        path.append(nodes[idx])
        idx = parents[idx]
    path.reverse()
    length = sum(
        math.hypot(path[i + 1][0] - path[i][0], path[i + 1][1] - path[i][1])
        for i in range(len(path) - 1)
    )
    print(f"ゴールまでの経路: ノード数 {len(path)}, 経路長 {length:.2f}")
    print("経路の一部:", [(round(x, 2), round(y, 2)) for x, y in path[:5]])
else:
    print("ゴールに到達できませんでした")
サンプリング回数: 229
木のノード数: 175
ゴールまでの経路: ノード数 32, 経路長 15.50
経路の一部: [(0.5, 0.5), (0.58, 0.99), (1.02, 1.24), (1.51, 1.13), (2.01, 1.14)]

229回のサンプリングで、4つの円形障害物を避けながらスタート(0.5, 0.5)からゴール(9.5, 9.5)までの経路が見つかりました。random.seed を変えたり STEP を小さくしたりすると、ノード数や経路長が変わるのを確認してみてください。

🎲

229回

サンプリング回数

🌳

175個

木のノード数

📏

15.50

見つかった経路の長さ

⚠️

直線距離は約12.7、見つかった経路は15.50

スタートからゴールの直線距離は約12.73なので、15.50はそれより長い回り道です。これがRRTの弱点で、見つかる経路は最短ではありません。最短に近づけたい場合は、この記事の最後で触れるRRT*という改良版を使います。

よくある質問

Q. RRTとA*は何が違いますか?

A. A*はマス目(グリッド)に区切った空間で最短経路を保証して探します。RRTはグリッドを作らずランダムな点をサンプリングして木を伸ばすので、ロボットアームの関節角度のような高次元・連続値の空間でも扱いやすい代わりに、見つかる経路の最短性は保証されません。

Q. RRTで見つかる経路は最短ルートになりますか?

A. いいえ、保証されません。RRTは「通れる経路を素早く見つけること」を優先したアルゴリズムで、スタートからゴールの直線距離より長い回り道になることがよくあります。経路の最適性に近づけたい場合は、Karaman と Frazzoli が2011年に発表したRRT*という改良版が使われます。

Q. RRTは実際のロボットのモーションプランニングで使われていますか?

A. はい。ロボットのモーションプランニングで広く使われるオープンソースライブラリOMPL(Open Motion Planning Library)には、RRTやRRT*を含む複数のサンプリングベース手法が実装として収録されており、ROSのMoveItなどからも利用されています。

まとめ

  • RRTはランダムサンプリングで木を伸ばし、障害物を避けてゴールへの経路を探すアルゴリズム
  • グリッド化が難しい連続・高次元の空間(ロボットアームの関節角度など)でA*より扱いやすい
  • 代わりに見つかる経路は最短とは限らない。最適性を求めるならRRT*などの改良版を使う
  • ゴールバイアス(低確率でゴール自体をサンプリング)を入れると収束が早くなる
📚 もう少し詳しく(背景と理論)気になる人向け・タップで開く

RRTはLaValleが1998年に発表した探索木で1、空間をランダムにサンプリングしながら木を伸ばすことで、明示的な空間分割(グリッド化)なしに探索できます。サンプリング回数が十分に多ければ、存在する経路をほぼ確実に見つけられる**確率的完全性(probabilistic completeness)**を持つことが知られていますが、見つかる経路の長さそのものは最適化されません。

この弱点に対して、Karaman と Frazzoli は2011年にRRT*(アールアールティー・スター)を発表しました2。RRT*は新しいノードを木に追加するたびに近傍のつながり方を見直し(リワイヤリング)、サンプリング回数が無限に近づくほど経路長が最適解に収束する漸近的最適性を持ちます。計算コストは増えますが、通常のRRTと同程度の計算量に収まることが示されています。

ロボットアームの逆運動学で扱った関節角度の空間(コンフィギュレーション空間)は、関節数が増えるほど次元が増え、Aのようなグリッド探索が指数的に重くなります。RRTやRRTはこうした高次元空間での経路計画に向いており、ロボットのモーションプランニングライブラリOMPLにも複数のバリエーションが実装されています3。

次の一歩 🌸

見つけた経路の上をロボットアームにたどらせる計算は逆運動学をPythonで実装する、グリッド上の最短経路はA*をPythonで実装する、センサーで地図を作る側はSLAMの基礎をPythonでへどうぞ。

参考資料・出典

ゆるふわポータルでは、一次情報・公的資料にもとづき、専門用語をできるだけやさしく翻訳することを心がけています。内容に誤りや古い情報がある場合は、お問い合わせからご連絡ください。

執筆:ゆるふわフィジカルAI編集部 最終更新日:2026年11月2日

Footnotes

  1. LaValle, S. M. (1998). "Rapidly-exploring random trees: A new tool for path planning." Technical Report 98-11, Dept. of Computer Science, Iowa State University. ↩

  2. Karaman, S., & Frazzoli, E. (2011). "Sampling-based Algorithms for Optimal Motion Planning." The International Journal of Robotics Research, 30(7), 846–894. 本文中でPRMとRRTという2つの漸近的最適アルゴリズムを提案している。 ↩

  3. OMPL(Open Motion Planning Library、ompl.kavrakilab.org)は、PRM・RRT・EST・SBL・KPIECEなど複数のサンプリングベース経路計画アルゴリズムを実装として収録した、ロボット向けのオープンソースライブラリ。 ↩

この記事をシェア:𝕏 で共有LINEで送る

🔥 この分野の最新トレンドをチェック →

あわせて読みたい

MuJoCoで逆運動学を使う|IKで目標位置へ手先を届かせる動かし方

MuJoCoで逆運動学(IK)を使うとは、手先の目標位置から関節角度を逆算し、その角度をPID制御の目標値として物理エンジン内のアームに与えることです。肘上げ・肘下げの2つの解や、届かない目標を指定した場合の挙動まで、実際に動かして確認したコード付きで解説します。

MuJoCoでロボットアームを制御する|PIDで関節を目標角度に動かす

MuJoCoでロボットアームを制御するとは、関節にアクチュエータを足し、PIDコントローラが計算した力を流し込むことです。2関節アームのMJCF定義から、重力下でPIDが目標姿勢に収束する様子までPythonコードで確認します。

Isaac Lab入門|強化学習フレームワークの導入とサンプル実行

Isaac Labとは、NVIDIAが提供するロボット向け強化学習フレームワークです。インストール手順、必要なGPU・OS要件、環境一覧の確認方法、最初のサンプルの動かし方をエンジニア向けに解説します。

MuJoCoのビューアが開かない・固まるときの原因と直し方

MuJoCoのビューアウィンドウが開かない、エラーで落ちる、真っ暗なまま固まるときの原因と直し方。DISPLAY未設定・macOSのmjpython・sync()忘れの3パターンを、公式ドキュメントと実際の動作確認で整理します。