1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで差動二輪の左右の車輪速を逆算する(逆運動学)

1
Posted at

「前へ 0.8 m/s、旋回 1.6 rad/s(約 92 度/秒)で走れ」と言われたとき、左右の車輪をそれぞれ何 m/s で回せばいいか。この逆算を逆運動学(inverse kinematics:結果側=車体の動きから、入力側=車輪や関節の動きを求める向きの計算)と呼びます。式は2行です。この記事のコードだけで動きます(前回を読んでいなくて構いません。必要な式と関数は再掲します)。

ただし、2行で終わりません。実装すると3か所で引っかかります。

引っかかる所 何が起きるか この記事で出る数字
① 車輪には上限がある 上限を超えた車輪だけ切り詰めると、曲がる半径が変わる 旋回半径 0.500 m の指令が 0.584 m になり、2秒後に 0.457 m ずれる
② 行き先の姿勢を指定したい 欲しい数字は3つ、回せる車輪は2つ。1本の円弧では届かない (2 m, 1 m, 90°) を狙うと向きは 53.13° にしかならない
③ それでも届かせたい 区間を分ければ届く。ただし分け方は1つに決まらない 3区間なら 4.366 秒、2区間なら 3.541 秒

図1:前回と今回の関係。上の往復は2行の式で閉じますが、右の点線(姿勢から車輪速へ)は素直には閉じません。それがこの記事の後半です

動作環境

Python 3.10.9
numpy  1.26.4

pip install numpy だけで全部動きます。シミュレータは使いません。

記号(前回と同じ)

差動二輪の記号
図2:差動二輪(differential drive:左右の車輪の速度差で向きを変える方式)の記号。位置と向きは車体中心の1点で代表させる

記号 意味 単位
$v_l,\ v_r$ 左右の車輪の周速(車輪の外周が地面をこする速さ) m/s
$L$ トレッド(左右車輪の間隔) m
$v$ 車体の前進速度 m/s
$\omega$ 旋回角速度(オメガ:1秒あたり何ラジアン向きが変わるか。$\pi$ ラジアン=180度) rad/s
$x,\ y,\ \theta$ 車体中心の位置と向き。3つ組を姿勢(pose)と呼ぶ m, m, rad

数値は前回と同じく $L = 0.555$ m(Husky という移動ロボットの実寸)を使います。Husky は4輪ですが、左右で同じ速度を与える点は差動二輪と同じなので、寸法だけ借ります(4輪ゆえに式どおりには回らないことは末尾で触れます)。

① 前進速度と旋回角速度から、左右の車輪速へ:式は2行

前回導いた順運動学は、左右の車輪速から $v$ と $\omega$ を出す2本の式でした。

$$v = \frac{v_r + v_l}{2}, \qquad \omega = \frac{v_r - v_l}{L}$$

平均が前進、差が旋回です。今回はこの2本を $v_l,\ v_r$ について解き直すだけです。2本目から $v_r - v_l = \omega L$、1本目から $v_r + v_l = 2v$。辺々足すと $v_r$ が、引くと $v_l$ が出ます。

$$v_l = v - \frac{\omega L}{2}, \qquad v_r = v + \frac{\omega L}{2}$$

前進ぶんを両輪に等しく配り、旋回ぶんを左右に $\pm\omega L/2$ ずつ振り分ける、それだけです。左に曲がるとき($\omega > 0$)は右車輪が速くなります。

diffdrive_ik.py
import numpy as np

TREAD = 0.555      # 左右車輪の間隔 [m](Husky というロボットの実寸。前回と同じ)
R_WHEEL = 0.1651   # 車輪の半径 [m](同上)
V_MAX = 1.0        # 車輪の周速の上限 [m/s](モータの限界。この記事では 1.0 と仮定)


def wheel_speeds(v: float, omega: float, tread: float = TREAD) -> tuple[float, float]:
    """前進速度 v [m/s] と旋回角速度 omega [rad/s] から、左右の車輪の周速 [m/s] を求める。"""
    return v - omega * tread / 2.0, v + omega * tread / 2.0


def body_twist(v_l: float, v_r: float, tread: float = TREAD) -> tuple[float, float]:
    """前回の順運動学。左右の車輪速から v と omega へ(逆算が合っているかの検算に使う)。"""
    return (v_r + v_l) / 2.0, (v_r - v_l) / tread

合っているかは、前回の例で往復させれば分かります。前回は左 0.6 m/s・右 1.0 m/s から $v = 0.8$、$\omega = 0.7207$ を出しました。それを今回の式に入れて、0.6 と 1.0 に戻れば正解です。

diffdrive_ik.py(続き)
v, omega = body_twist(0.6, 1.0)          # 前回の例:左 0.6 / 右 1.0
v_l, v_r = wheel_speeds(v, omega)         # 今回の式で戻す
print(f"v = {v:.4f} m/s, omega = {omega:.4f} rad/s")
print(f"逆算した車輪速  左 {v_l:.6f}  右 {v_r:.6f} m/s")
print(f"モータへ渡す角速度  左 {v_l / R_WHEEL:.4f}  右 {v_r / R_WHEEL:.4f} rad/s")
v = 0.8000 m/s, omega = 0.7207 rad/s
逆算した車輪速  左 0.600000  右 1.000000 m/s
モータへ渡す角速度  左 3.6342  右 6.0569 rad/s

往復が閉じました。最後の行は、モータに渡す値への換算です。モータの指令は「周速 m/s」ではなく「車輪の角速度 rad/s」なので、周速を車輪半径で割ります。

同じ「逆運動学」という言葉をロボットアームでも使います。

アームの逆運動学は三角関数を解く必要があって解が複数出ますが、差動二輪は一次式なので解は1つに決まります。ここまでは、アームより簡単です。 難しくなるのはここからです。

② 車輪には上限がある:素直にクリップすると旋回半径が変わる

モータには最高回転数があります。この記事では車輪の周速の上限を $v_{\max} = 1.0$ m/s とします。

上の2行の式を見ると、右車輪は $v + \omega L/2$ です。前進と旋回が上限を分け合っていることになります。大きいほうの車輪が上限に収まる条件は、

$$|v| + \frac{|\omega| L}{2} \le v_{\max}$$

です。$L/2 = 0.2775$ m なので、たとえば $\omega = 1.6$ rad/s で回るなら旋回ぶんだけで 0.444 m/s を使い、前進に残るのは 0.556 m/s までです。

ここで「前へ 0.8 m/s・旋回 1.6 rad/s」(旋回半径 $R = v/\omega = 0.5$ m)を要求してみます。

diffdrive_ik.py(続き)
v_cmd, omega_cmd = 0.8, 1.6                          # 旋回半径 0.5 m で回りたい
v_l, v_r = wheel_speeds(v_cmd, omega_cmd)
print(f"要求どおりの車輪速  左 {v_l:.4f}  右 {v_r:.4f} m/s  (右が上限 {V_MAX} を超える)")
要求どおりの車輪速  左 0.3560  右 1.2440 m/s  (右が上限 1.0 を超える)

右車輪が 1.244 m/s で上限を超えました。ここで手が伸びるのが np.clip です。それをやると、曲がる半径が変わります。

結論を先に書くと、左右を同じ比率で縮めてください。 軌跡が変わらず、到着が遅れるだけで済みます。ロボットに限らず、2つの成分を別々に切り詰めるとベクトルの向きが変わる、という話です。旋回半径は $v/\omega$ の比なので、比を守るには両方を同じ倍率で縮めるしかありません。以下はその根拠です。

3つの縮め方を比べる

縮め方 やること 何を守り、何を捨てるか
(a) クリップ 上限を超えた車輪だけ切り詰める 何も守らない。$v$ も $\omega$ も比率も変わる
(b) 比率保持 左右を同じ比率で縮め、大きいほうを上限に合わせる $v/\omega$(=旋回半径)を守る。速さを捨てる
(c) 旋回優先 $\omega$ は要求どおり、$v$ を上限に収まるまで削る $\omega$(=向きの変化)を守る。半径を捨てる

3つともコードにします。検算には前回導いた step_exact(一定入力のあいだ車体は円弧を描く、という閉じた式)を使うので、そのまま再掲します。

diffdrive_ik.py(続き)
def step_exact(pose: np.ndarray, v: float, omega: float, dt: float) -> np.ndarray:
    """前回の順運動学。1ステップぶん、円弧として厳密に進める(検算用に再掲)。"""
    x, y, th = pose
    if abs(omega) < 1e-12:                       # まっすぐのときは 0 割りになるので直線へ
        return np.array([x + v * np.cos(th) * dt, y + v * np.sin(th) * dt, th])
    radius = v / omega
    th_new = th + omega * dt
    return np.array([x + radius * (np.sin(th_new) - np.sin(th)),
                     y - radius * (np.cos(th_new) - np.cos(th)),
                     th_new])


def limit_clip(v_l: float, v_r: float, v_max: float = V_MAX) -> tuple[float, float]:
    """(a) 上限を超えた車輪だけを切り詰める。"""
    return float(np.clip(v_l, -v_max, v_max)), float(np.clip(v_r, -v_max, v_max))


def limit_scale(v_l: float, v_r: float, v_max: float = V_MAX) -> tuple[float, float]:
    """(b) 左右を同じ比率で縮めて、大きいほうを上限に合わせる。"""
    biggest = max(abs(v_l), abs(v_r))
    if biggest <= v_max:
        return v_l, v_r
    scale = v_max / biggest
    return v_l * scale, v_r * scale


def limit_turn_first(v_l: float, v_r: float, v_max: float = V_MAX) -> tuple[float, float]:
    """(c) omega を守り、v だけを上限に収まるまで削る。"""
    v, omega = body_twist(v_l, v_r)
    v_room = v_max - abs(omega) * TREAD / 2.0           # 旋回ぶんを引いた、前進に使える残り
    v = float(np.clip(v, -v_room, v_room))
    return wheel_speeds(v, omega)


target = step_exact(np.zeros(3), v_cmd, omega_cmd, 2.0)   # 上限が無ければ 2 秒後にここ
for name, limiter in [("(a) クリップ", limit_clip), ("(b) 比率保持", limit_scale),
                      ("(c) 旋回優先", limit_turn_first)]:
    l, r = limiter(v_l, v_r)
    v_act, omega_act = body_twist(l, r)                    # 実際に車体が動く v, omega
    pose = step_exact(np.zeros(3), v_act, omega_act, 2.0)
    print(f"{name}  左 {l:.4f} 右 {r:.4f} → v={v_act:.4f} omega={omega_act:.4f}"
          f" 旋回半径={v_act / omega_act:.4f} m")
    print(f"      2秒後のずれ  位置 {np.linalg.norm(pose[:2] - target[:2]):.3f} m"
          f"  向き {np.degrees(pose[2] - target[2]):+.1f}°")
(a) クリップ  左 0.3560 右 1.0000 → v=0.6780 omega=1.1604 旋回半径=0.5843 m
      2秒後のずれ  位置 0.457 m  向き -50.4°
(b) 比率保持  左 0.2862 右 1.0000 → v=0.6431 omega=1.2862 旋回半径=0.5000 m
      2秒後のずれ  位置 0.309 m  向き -36.0°
(c) 旋回優先  左 0.1120 右 1.0000 → v=0.5560 omega=1.6000 旋回半径=0.3475 m
      2秒後のずれ  位置 0.305 m  向き +0.0°

(b) にも 0.309 m のずれが出ていますが、旋回半径の列が 0.5000 のままである点を見てください。これは同じ円の上で遅れているだけのずれで、時間を伸ばせば消えます(すぐ下で確かめます)。

上限を超えたときの3つの縮め方
図3:同じ要求を、上限 1.0 m/s の下で2秒走らせた軌跡。灰色の太線が上限なしの理想。赤の (a) は外側へ膨らんで別の円になり、青の (b) は理想と同じ円の上を遅れて進む。緑の (c) は向きだけ合って半径が縮む

(a) は右車輪だけを削ったので、左右の差が縮みました。差は $\omega$ そのものです。$\omega$ が 1.6 から 1.16 に落ちて $v$ は 0.68 なので、旋回半径は $0.68/1.16 = 0.584$ m。要求した 0.5 m の円とは別の円を走っています。 2秒後には位置が 0.457 m、向きが 50.4 度ずれました。

(b) は左右をどちらも 0.8039 倍にしたので、平均も差も 0.8039 倍です。$v/\omega$ は変わらず、旋回半径は 0.5 m のままです。2秒時点では 0.309 m 手前にいますが、同じ円の上を遅れているだけです。走らせる時間を $2.0 / 0.8039 = 2.488$ 秒に伸ばすと、理想の位置に一致します。

(c) は $\omega = 1.6$ を守ったので向きのずれは 0.0 度ですが、$v$ を 0.556 まで削ったぶん半径が 0.347 m に縮み、要求より内側の小さな円を回っています。

diffdrive_ik.py(続き)
l, r = limit_scale(v_l, v_r)
v_act, omega_act = body_twist(l, r)
scale = v_act / v_cmd                                   # 0.8039
pose = step_exact(np.zeros(3), v_act, omega_act, 2.0 / scale)
print(f"(b) を {2.0 / scale:.4f} 秒走らせる → ずれ {np.linalg.norm(pose[:2] - target[:2]):.2e} m")
(b) を 2.4880 秒走らせる → ずれ 0.00e+00 m

⚠️ (a) の 0.457 m は、上限を超えている間ずっと積もります。 経路をたどる側のプログラム($v$ と $\omega$ の指令を出す側)から見ると「軌跡は同じで遅い」(b) のほうが圧倒的に扱いやすく、指令が速すぎてもここで自動的に減速されます。(c) の旋回優先は、向きを合わせることが最優先の場面(その場旋回・障害物の直前)でだけ使います。

式が2行で済むぶん、上限の処理は後回しにしがちです。ここを clip で済ませたコードは、直進では正しく動き、きつく曲がるときだけ指令と違う円を描くので、症状が出るまで気づきません。

③ 姿勢を指定する:欲しい数字は3つ、回せる車輪は2つ

ここまでは「速度」の逆算です。前回の記事の末尾で「行き先を先に決めて走らせる」と予告したので、それをやります。

行き先は姿勢 $(x,\ y,\ \theta)$ の3つの数字です。回せるのは $v_l,\ v_r$ の2つ。数が合いません。 前回、車輪は横に滑れないという制約を非ホロノミック拘束(nonholonomic constraint:速度に掛かる、位置の式には書き直せない制約)と呼びました。逆運動学の側から見ると、その制約はこの「3対2」の形で現れます。

具体的に見ます。原点・向き 0 から出発して、一定の $v,\ \omega$ で走ると、前回③のとおり軌跡は円弧です。円弧1本で点 $(x,\ y)$ に着いたとき、向き $\theta$ は選べません。図で描くと一目です。

円弧1本では向きが決まってしまう
図4:原点から (2, 1) へ向かう円弧は1本しかない(中心 (0, 2.5)・半径 2.5 m)。その円弧で着いたときの向きは 53.13 度で、欲しい 90 度には 36.87 度足りない。破線が弦で、出発点で弦と接線($x$ 軸)のなす角が中心角 $\phi$ の半分になっている

式にすると、原点から円弧で $(x,\ y)$ に着いたときの向きは

$$\theta_{\text{arc}} = 2,\mathrm{atan2}(y,\ x)$$

です(接弦定理:円の接線と弦のなす角は、その弦に対する中心角の半分、という性質です。出発の接線が $x$ 軸なので、弦の角度 $\mathrm{atan2}(y, x)$ の2倍が中心角=向きの変化になります。忘れていても、図4の矢印の差だけ見れば結論は同じです)。

半径は弦の長さから逆算します。長さ $\sqrt{x^2 + y^2}$ の弦は、半径 $R$・中心角 $\phi$ の円では $2R\sin(\phi/2)$ に等しいので、$R = \sqrt{x^2 + y^2} ,/, 2\sin(\phi/2)$ です。

diffdrive_ik.py(続き)
x_t, y_t = 2.0, 1.0
phi = 2 * np.arctan2(y_t, x_t)                       # 円弧1本で (x_t, y_t) に着いたときの向き
radius = np.hypot(x_t, y_t) / (2 * np.sin(phi / 2))   # 弦の長さ = 2R sin(φ/2) を R について解く
print(f"(2, 1) に円弧1本で着くと、向きは {np.degrees(phi):.2f}°、半径は {radius:.4f} m に決まる")
(2, 1) に円弧1本で着くと、向きは 53.13°、半径は 2.5000 m に決まる

$(x, y)$ を決めた時点で $\theta$ は 53.13 度に決まりました。90 度で着きたければ、一定入力1本では不可能です。これが、前回の順運動学を「逆に解く」だけでは行き先を指定できない理由です。

図5:入力が2つで欲しい数が3つなら、時間方向に自由度を足すしかない

④ 回転→直進→回転に分けると届く

入力を時間で切り替えれば、区間の数だけ自由度が増えます。いちばん素直な分け方は、車庫入れと同じ3手です。

  1. 回転:その場で目標の方向を向く($v = 0$、$\omega$ だけ)
  2. 直進:目標の点まで走る($\omega = 0$、$v$ だけ)
  3. 回転:その場で目標の向きに合わせる

各区間は一定入力なので、車輪速は①の2行で出ます。その場回転は $v = 0$ なので $v_l = -\omega L/2$、$v_r = +\omega L/2$、つまり左右を逆向きに同じ速さで回すことです。

diffdrive_ik.py(続き)
def plan_turn_drive_turn(x_t: float, y_t: float, th_t: float,
                         v_drive: float = 0.8, omega_turn: float = 1.0) -> list[tuple[float, float, float]]:
    """原点・向き 0 から目標姿勢 (x_t, y_t, th_t) へ、回転→直進→回転で届く (v, omega, 秒数) の列。"""
    th_1 = np.arctan2(y_t, x_t)                # まず目標の方向を向く
    dist = np.hypot(x_t, y_t)                  # 直進する距離
    th_2 = th_t - th_1                         # 着いてから目標の向きへ回す角度
    return [(0.0, omega_turn * np.sign(th_1), abs(th_1) / omega_turn),
            (v_drive, 0.0, dist / v_drive),
            (0.0, omega_turn * np.sign(th_2), abs(th_2) / omega_turn)]


plan = plan_turn_drive_turn(2.0, 1.0, np.radians(90))
pose = np.zeros(3)
for i, (v, omega, seconds) in enumerate(plan, 1):
    v_l, v_r = wheel_speeds(v, omega)
    pose = step_exact(pose, v, omega, seconds)           # 前回の順運動学で「本当に着くか」を検算
    print(f"区間{i}: 左 {v_l:+.4f} 右 {v_r:+.4f} m/s を {seconds:.4f} 秒"
          f" → x={pose[0]:.4f} y={pose[1]:.4f} θ={np.degrees(pose[2]):.2f}°")
print(f"合計 {sum(s[2] for s in plan):.4f} 秒")
区間1: 左 -0.2775 右 +0.2775 m/s を 0.4636 秒 → x=0.0000 y=0.0000 θ=26.57°
区間2: 左 +0.8000 右 +0.8000 m/s を 2.7951 秒 → x=2.0000 y=1.0000 θ=26.57°
区間3: 左 -0.2775 右 +0.2775 m/s を 1.1071 秒 → x=2.0000 y=1.0000 θ=90.00°
合計 4.3659 秒

(2, 1, 90°) に誤差ゼロで届きました。検算に使ったのは前回の順運動学です。逆運動学の答えは、順運動学に入れて戻れば正しさが確かめられます。 アームの記事でも同じことをやっていて、この往復が2本の記事を1組にする理由です。

分け方は1つに決まらない

3区間でなくてもよく、たとえば「円弧で点に着いてから、向きだけ回す」2区間でも届きます。③で出した円弧(向き 53.13 度で着く)を使い、残り 36.87 度をその場で回します。

diffdrive_ik.py(続き)
def plan_arc_turn(x_t: float, y_t: float, th_t: float,
                  v_drive: float = 0.8, omega_turn: float = 1.0) -> list[tuple[float, float, float]]:
    """円弧1本で (x_t, y_t) へ着き、最後に向きだけ合わせる 2 区間の列。"""
    phi = 2 * np.arctan2(y_t, x_t)
    radius = np.hypot(x_t, y_t) / (2 * np.sin(phi / 2))
    omega_arc = v_drive / radius
    th_2 = th_t - phi
    return [(v_drive, omega_arc, phi / omega_arc),
            (0.0, omega_turn * np.sign(th_2), abs(th_2) / omega_turn)]


plan2 = plan_arc_turn(2.0, 1.0, np.radians(90))
pose = np.zeros(3)
for i, (v, omega, seconds) in enumerate(plan2, 1):
    v_l, v_r = wheel_speeds(v, omega)
    pose = step_exact(pose, v, omega, seconds)
    print(f"区間{i}: 左 {v_l:+.4f} 右 {v_r:+.4f} m/s を {seconds:.4f} 秒"
          f" → x={pose[0]:.4f} y={pose[1]:.4f} θ={np.degrees(pose[2]):.2f}°")
print(f"合計 {sum(s[2] for s in plan2):.4f} 秒")
区間1: 左 +0.7112 右 +0.8888 m/s を 2.8978 秒 → x=2.0000 y=1.0000 θ=53.13°
区間2: 左 -0.2775 右 +0.2775 m/s を 0.6435 秒 → x=2.0000 y=1.0000 θ=90.00°
合計 3.5413 秒

2つの走らせ方
図6:同じ目標へ届く2つの走らせ方。緑が回転→直進→回転(4.366 秒)、青が円弧→回転(3.541 秒)。矢印は 0.5 秒ごとの姿勢で、緑は最初の 0.46 秒間は原点で回っているだけ

走らせ方 区間数 合計時間 特徴
回転→直進→回転 3 4.366 秒 どんな目標にも届く。止まって回る時間が無駄になる
円弧→回転 2 3.541 秒(19% 短い) 走りながら曲がるので速い。真後ろの目標には使えない(次節)

同じ目標に対して答えが2つ出ました。アームの逆運動学で解が複数出るのと似ていますが、事情が違います。アームは「式の解が複数ある」のに対し、差動二輪は**「区間の切り方が無数にある」**のです。どれを選ぶかは、時間・滑らかさ・障害物といった逆運動学の外側の基準で決まります。それが経路計画の仕事で、次回の主題です。

この記事の式でできないこと

区間の継ぎ目で、車輪速が一瞬で跳びます。 回転→直進→回転の左右車輪速を時間で描くとこうなります。

左右車輪速の時間変化
図7:回転→直進→回転の左右車輪速。0.46 秒の時点で左車輪は -0.28 から +0.80 m/s へ一瞬で変わる。実機のモータはこの階段を追えない

実機のモータには加速の限界があるので、この指令をそのまま送ると継ぎ目で滑るか遅れるかします。加減速を入れて滑らかにつなぐ話は、この記事の式(一定入力の区間をつなぐ)の外です。

円弧→回転は、真後ろの目標で破綻します。 目標を $(-1,\ 0,\ 0°)$ にすると $\theta_{\text{arc}} = 2,\mathrm{atan2}(0, -1) = 360°$、つまり $\sin(\phi/2) = 0$ で半径が無限大になります。真後ろの点を通る円弧は、出発点で $x$ 軸に接するものが存在しません。回転→直進→回転なら 7.533 秒で届きます(後ろを向いて 3.14 秒、走って 1.25 秒、向き直して 3.14 秒)。

円弧→回転は上限を無視しています。 目標 $(0,\ 1,\ 180°)$ なら円弧の半径は 0.5 m で、②と同じ $\omega = 1.6$ が要ります。右車輪の要求は 1.244 m/s で上限超えです。②の比率保持を掛けると 1.963 秒の計画が 2.442 秒に伸びます(plan_arc_turn(0.0, 1.0, np.radians(180)) の区間1の車輪速に limit_scale を通すと出ます)。逆運動学の2行と上限の処理は、区間計画のあとにもう一度通します。

そして前回と同じく、4輪固定で横滑りするロボットには、この式は係数調整では合いません。 前回、PyBullet(物理シミュレータ)上の Husky に左 0.6 / 右 1.0 m/s を与えて測ると、車体の旋回角速度は式の予測の約 1/2.6 でした。寸法を借りた Husky 自身が、この式どおりには回らないロボットです。左右の車輪速を「正しく」逆算しても、車体はその $\omega$ で回りません。

まとめ

  • 差動二輪の逆運動学は2行です。$v_l = v - \omega L/2$、$v_r = v + \omega L/2$。前進ぶんを両輪に等しく配り、旋回ぶんを左右に振り分けます。モータには周速を車輪半径で割った角速度を渡します
  • 車輪の上限を超えたら、左右を同じ比率で縮めます。 上限を超えた側だけクリップすると旋回半径が 0.500 → 0.584 m に変わり、2秒で 0.457 m ずれます。比率を保てば軌跡は同じで、到着が 2.000 → 2.488 秒に遅れるだけです
  • 欲しい姿勢は $(x, y, \theta)$ の3つ、回せる車輪は2つ。1本の円弧では $\theta$ を選べません((2, 1) に着く向きは 53.13 度に決まる)
  • 入力を時間で切り替えれば届きます。回転→直進→回転なら 4.366 秒、円弧→回転なら 3.541 秒。分け方は1つに決まらないので、選ぶ基準は逆運動学の外にあります
  • 逆運動学の答えは、前回の順運動学に入れて戻せば検算できます

本シリーズの全体像は、まとめ記事から辿れます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?