AmazingHand Web コントロール
ブラウザから AmazingHand 多指ハンドを操作 —— 左手 / 右手のどちらでも、操作方法は 2 通り:
- マニュアル —— サーボスライダー 8 本 + プリセットポーズ 19 種 + ワンクリックで中間位置へ復帰
- カメラテレオペ —— MediaPipe がブラウザ内でローカルに動作し、手の動きをリアルタイムで指にマッピング(映像はマシン外に出ません)
- バイリンガル UI —— 右上で中国語と英語を切り替え。選択はブラウザに記憶されます
サーボマップ、ポーズライブラリ、変換式はすべて AmazingHandControl(公式 GUI)と一致しています。
クイックスタート
ターミナルを開き、Amazinghand-web ディレクトリへ移動します:
pip install -r requirements.txt
python server.py --no-connect # 推奨:先にサーバーを起動し、ポートはページ上で選ぶ(何も動かない)
# python server.py # シリアルポートを自動検出して接続
# python server.py --port COM9 # ポートを指定
# python server.py --hand left # 左手のみを有効化
# python server.py --dry-run # シリアルポートを一切使わず UI のみその後、ブラウザで http://localhost:8000 を開きます。
ページ上でシリアルポートを選ぶ
ページ上部にポートバーがあります:
Port [COM3 ★ USB-Enhanced-SERIAL CH343 ▾] [Refresh] [Connect] [Disconnect] Connected to COM3- ドロップダウンには本機のすべてのシリアルポートが並びます。一般的な USB シリアルチップ(CH34x / CP210x / FTDI など)には ★ が付き、先頭に並びます
- Refresh で再スキャン(ボードを挿した後に使います)
- Connect で選択中のポートを開きます。接続直後にハンドは中間位置へ戻ります
- Disconnect は先にトルクを解放してから切断するので、サーボがその位置のまま固まりません
- ポートを切り替えるときは、新しいポートを選んで Connect を押すだけ。古いポートは自動的に閉じられます
--no-connectはこのフローのためのオプションです:サーバーはシリアルポートに一切触れずに起動するため、 誤ったポートをつかんでハンドを急に動かすことがありません。起動時に自動接続させたい場合はこのフラグを外してください。
シリアルログは server.log に出力されます(.bat から起動した場合、ウィンドウは一瞬で消えるので、ファイルを読んでください)。
配線チェック
検出したサーボ ID を一覧表示し、config.json と左手・右手ごとに突き合わせます。
python tools/scan_servos.py --port COM9COM9 は実際のポートに置き換えてください。
サーボマップ
出典:AmazingHandControl/hand_logic.py の DEFAULT_HAND_SERVOS と docs/zh-hans/CONFIG_FORMAT.md。
| 指 | 右手 ID | 左手 ID |
|---|---|---|
| 薬指 | 5, 6 | 11, 12 |
| 中指 | 3, 4 | 13, 14 |
| 人差し指 | 1, 2 | 15, 16 |
| 親指 | 7, 8 | 17, 18 |
1 本の指の 2 つのサーボは役割が違う ⚠️
servo1 (奇数 ID) = 屈曲軸 開く ↔ 閉じる
servo2 (偶数 ID) = 開き軸 指を揃える ↔ 開くUI が表示するのはまさにこの 2 軸です。「指が開かない / 閉じない」というのは開き軸が動いていないということ —— チョキのような V 字は、人差し指の開き +17 と中指の開き −30(一方が正、他方が負)から作られます。
これらは送信時に合成されます:
servo1 = 屈曲 + 開き
servo2 = 屈曲 − 開き範囲(公式の 19 ポーズ + 中間位置から導出):
| 軸 | 範囲 | 意味 |
|---|---|---|
| 屈曲 | −50 ~ +90 | 開く(負)↔ 閉じる(正) |
| 開き | −45 ~ +45 | 揃う(負)↔ 開く(正) |
2 つの ID ルール(間違えるとサーボを焼いたり動作が逆になります):
- 奇数 ID が指の 1 つ目のサーボ、偶数 ID が 2 つ目。 両手とも当てはまります (左手の 11/12、13/14… も同様に奇数・偶数が交互です)。2 つ目のサーボは逆向きに取り付けられているので、変換時に符号を反転させます。
- 左手の ID は 9–16 ではなく 11–18 で、薬指から順に番号が振られています。 1–8 の順に配線するのは常に間違いです。

変換
AmazingHandControl の apply_pose() と同一です(ポーズごとに検証済み):
1) mirror_pose が true のとき、まず各指の 2 つのサーボ値を入れ替える
2) servo_deg = (ID が奇数 ? +1 : -1) × (ポーズ値 − angle_offset_deg)| 手 | angle_offset_deg | mirror_pose |
|---|---|---|
| 右手 | 0 | あり ⚠️ |
| 左手 | -35.16 | あり |
-35.16° は左手のサーボホーンが右手に対して物理的に半スロットずれている分で、公式 config から取得した値です。
mirror_poseについて —— このビルドは公式と異なります公式リファレンスでは左手だけが
trueです。このビルドの右手モジュールもサーボのペアが逆向きに取り付けられているため、 右手もtrueになっています。この交換で反転するのは開きだけで、屈曲は変わりません:
base = (a+b)/2は交換しても変わらず、side = (a−b)/2は符号が変わります。したがって、どちらかの手の開きが鏡像になって出てくる場合、まず確認すべきはこのスイッチです。
ポーズ配列は常に 8 個の値で、順序は [ring, middle, index, thumb]、1 本の指につき 2 個 —— 公式の data/hand_config.yaml とバイト単位で一致するので、そのまま比較できます。
計算例
| ポーズ | 右手 | 左手 |
|---|---|---|
open [-35]*8 | ID5 −35 / ID6 +35 | ID11 +0.16 / ID12 −0.16 |
close [75]*8 | ID5 +75 / ID6 −75 | ID11 +110.16 / ID12 −110.16 |
中間位置 [-17.578]*8 | ID5 −17.58 / ID6 +17.58 | ID11 +17.58 / ID12 −17.58 |
左手の
closeは ±110.16° を必要とすることに注意してください。右手よりずっと大きい値です。そのためconfig.jsonではリミットを ±120° に設定しています(SCS0009 のフルレンジは約 ±150°)。±90 のリミットでは左手の握り拳がクリップされてしまいます。
プリセットポーズ(19 種)
| ポーズ | ポーズ | ||
|---|---|---|---|
open | 開く | wave1 | 手を振る |
close / stone | 握り拳 | wave_r / wave_l | 右へ / 左へスワイプ |
ok | OK | ring_close | 薬指のみ |
greeting | 挨拶 | middle_close | 中指のみ |
victory / two / scissors | ピース / 2 | pointer_close | 人差し指のみ |
paper | パー | thumb_close | 親指のみ |
one / three | 1 / 3 | hifive | ハイタッチ |
ポーズを変更するには、config.json の poses セクションを編集します。キーは公式 YAML の名前と一致しています。
左手と右手で別々の値を使える
ポーズ内の positions は両手で共有です。機械的な組み立ての違いにより片方の手だけ別のセットが必要な場合は、 そのエントリに right または left キーを追加すると positions を上書きします:
"close": {
"label": "握り拳",
"positions": [75, 75, 75, 75, 75, 75, 18, 18], // 両手で共有
"right": [ ... 8 個の値 ... ] // 任意:右手のみ
}2 つのモード
| モード | 動作 |
|---|---|
| 右手 | 右手の 8 サーボにのみ送信 |
| 左手 | 左手の 8 サーボにのみ送信(ミラー + −35.16° のオフセットを自動適用) |
一度に片手のみ。両手は同じバス上にあり、上部の Target で切り替えます。 切り替えてももう片方の手は影響を受けません —— その場のポーズを保持し続けます。
カメラテレオペ
MediaPipe の手のランドマークはブラウザ内でローカルに動作します(アセットは web/mediapipe/ 以下、オフライン対応)。 21 個のランドマークは 4 本の指のポーズ値に変換され、選択中の手へストリーミングされます。
1. まず 3 つのポーズを取得する
カメラを開始し、手でポーズを作ってそのポーズの取得ボタンをクリックします(約 1 秒間の平均を取ります)。 順序は問いません。同じボタンをもう一度クリックすると取り直せます。3 つすべてが揃って初めてステータスが緑になります —— それまでは何も送信されないので、ハンドは中間位置にとどまります。
| ボタン | ポーズ | 提供するもの |
|---|---|---|
| ① 開く | 指をまっすぐ開き、親指を外側へ向ける | 屈曲の「まっすぐ」端 + 開きの「開いた」端 |
| ② 揃える | 指をまっすぐ揃え、親指を人差し指に付ける | 開きの「揃った」端 |
| ③ 握る | 握り拳を作り、親指を手のひら側へ入れる | 屈曲の「閉じた」端 |
2. 各軸は 2 つの実測点を通る直線
屈曲と開きはどちらも2 つの実測端点で直線を定義します:
t = (測定値 − 端点A) / (端点B − 端点A) // 0〜1 にクランプ
目標値 = Aの目標値 + (Bの目標値 − Aの目標値) × t利点は各指の測定値の符号が自動的に吸収されること —— つまり「この指を反転」といったチェックボックスをチェックする必要がありません。 (人差し指と薬指は中心線の反対側にあり、親指は関節の連なりも異なるため、実際に方向が逆になります —— それを 2 つの実測端点が吸収します。)
端点の目標値は config.json の camera セクションにあります:
| 軸 | 端点 | 薬指 | 中指 | 人差し指 |
|---|---|---|---|---|
| 屈曲 | まっすぐ → 握り拳 | −50 → 90 | −50 → 90 | −50 → 90 |
| 開き | 揃える → 開く | −45 → +45 | 0 → 0 | +45 → −45 |
中指は開きの両端とも 0 です(横に振れません)。
3. 親指は屈曲のみ ⚠️
親指の CMC は**鞍関節(対立関節)**で、その屈曲は外転と強く連成しています —— 親指を人差し指に付けた状態ではすでにある開き位置にあり、そこで測った「屈曲ゼロ」は汚染されています。 無理に 2 軸に押し込むと、どちらも正しく求まりません。
そのため親指は屈曲のみで、開きは常に 0 です(2 つのサーボには同じ値が入ります)。親指が必要とする 2 つの基準ポーズは ①(外側を指す)と ③(手のひら側を指す)で、追加で取得するものはありません。
4. 指を曲げるにつれて開きが減衰する
最終的な開きは次のようにスケールされます:
side = side_measured × (1 − curl)splayDeg は MCP→PIP 方向を手のひらの横軸へ射影したもので、指の屈曲はまさにこの軸を中心に回転します —— ある程度曲げると射影の符号が反転し、測定された「開き」が突然反対の端へ飛びます。
減衰がないと、この飛びは servo2 = flexion − splay を通じて偶数 ID のサーボを逆転させてしまいます。 (奇数 ID は flexion + splay を受け取るので同じ向きに、ただ速く動くだけ —— 目には見えません。) 症状は「握り拳の途中でサーボ 12 と 16 が突然逆転する」です —— 12 は左手の薬指の開きサーボ、16 は左手の人差し指のもので、 開きの目標値が非ゼロであるまさにその 2 つです。
減衰があれば、測定値がどれだけ荒れても、完全に曲げたときの開きはちょうど 0 になります。 そもそも握り拳は指を揃えるべきものです。
5. スケルトンの色
| 指 | 色 |
|---|---|
| 親指 | オレンジ |
| 人差し指 | 赤 |
| 中指 | 青 |
| 薬指 | 黄 |
| 小指 | 暗いグレー |
| 手のひらのボーン | ニュートラルグレー |
小指はジェスチャーのマッピングには使いませんが、その付け根の MCP(ランドマーク 17)が右手の手のひらの座標系を定義するため、単純に外すことはできません。
調整チートシート
| 変えたいもの | 変更箇所 |
|---|---|
| 軸の目標値 | config.json の camera セクション |
| 左右の開きが逆方向になる | camera.side_sign でその手を -1 に(1 = 変更なし) |
| 平滑さ | tracking.js の SMOOTH(デフォルト 0.35、小さいほど安定するが遅延が増える) |
| 屈曲の判定速度 | 3 つのポーズを取得し直す |
side_sign は Target で選択されている方の手に適用されます。変更したらページを再読み込みしてください —— サーバーの再起動は不要です:
"side_sign": { "right": 1, "left": -1 } // 左手の開き軸全体を反転屈曲は side_sign の影響を受けません。
プロジェクト構成
Amazinghand-web/
├── config.json サーボマップ / ポーズライブラリ / シーケンス / リミット(公式 YAML から生成)
├── server.py バックエンド:シリアル + 静的ファイル + WebSocket
├── requirements.txt
├── README.md 英語版(原文)
├── tutorials/ de · en · es · fr · it · ja · ko · pt-br · pt-pt · zh-hans · zh-hant
├── tools/
│ └── scan_servos.py バス上のサーボをスキャンして ID を確認
└── web/
├── index.html · app.js · tracking.js · style.css
├── i18n.js UI 文言(中国語 / 英語の辞書 + 右上の切り替え)
└── mediapipe/ MediaPipe アセット(ローカルに同梱、オフライン、約 42 MB)フロントエンド / バックエンド間のプロトコル:
// フロントエンド → バックエンド target: "left" | "right"
{ "cmd": "ports" } // シリアルポートを再スキャン
{ "cmd": "connect", "port": "COM3" }
{ "cmd": "disconnect" }
{ "cmd": "target", "target": "left" }
{ "cmd": "set", "target": "left", "angles": { "pointer": [0, 0], ... } }
{ "cmd": "pose", "target": "left", "name": "close" }
{ "cmd": "middle", "target": "left" }
{ "cmd": "torque", "target": "left", "on": true }
{ "cmd": "stop" }
// バックエンド → フロントエンド
{ "type": "state", "hands": {...}, "poses": {...}, "positions": {...},
"axes": {...}, "camera": {...}, "target": "left",
"limits": {...}, "port": { "available": [...], "current": "COM3", "connected": true } }
{ "type": "ports" | "connected" | "disconnected", "ports": [...], ... }
{ "type": "error", "msg": "COM_FAKE への接続に失敗しました:指定されたファイルが見つかりません。" }安全性
| 項目 | |
|---|---|
| ポーズリミット | ±120°(config.json の limits)。19 ポーズすべてが範囲内に収まることを確認済み |
| 変更の間引き | 0.5° 未満の変化は送信しないため、バスがノイズで溢れない |
| 書き込みスロットル | 書き込み間の最小間隔は 20 ms |
| 緊急停止 | UI の赤いボタンで全サーボを一度に解放 |
| 終了時の保護 | Ctrl+C でトルクが自動的に解放される |
| 片手デバッグ | --hand left/right |
トラブルシューティング
動作が逆になった → 配線が「奇数 ID が先」に従っているか確認してください。左手では angle_offset_deg = -35.16 も確認します。
左手の握り拳が小さすぎる / サーボが唸る → リミットがまだ ±90 の可能性があります。config.json が ±120 になっているか確認してください。
指が動かない / 「expected N motors, found M」 → tools/scan_servos.py を実行してください。左手の ID が最も配線を間違えやすいです。
中間位置がずれている → 中間値 -17.578125 は公式の raw 値 451/571 から変換したものです。 自分の組み立てに合わせて校正するには、config.json の middle.positions を編集してください。
カメラトラッキングが揺れる → 照明を均一にし、手のひらをカメラに向けてください。tracking.js の SMOOTH (デフォルト 0.35)—— 大きいほど滑らかですが遅延が増えます。
カメラのジェスチャーがおかしい / ある指が追従しない → 上記の「カメラテレオペ」セクションを参照してください。まず次の 3 点を確認します:
- 3 つのポーズをすべて取得したか —— ステータスが緑である必要があります。それまでは何も送信されず、ハンドは中間位置のままです
- 方向が逆ならそのポーズを逆に取得している —— そのボタンだけ取り直せばよく、コード変更は不要です
- 親指は開かないはず —— 屈曲のみです。上記セクション 3 を参照
握り拳の途中でサーボが逆に飛ぶ → そのサーボは偶数 ID(開き軸、例えば左手の 12 / 16)のはずです。 通常、開きは屈曲に応じて 0 まで減衰し、跳ぶことはありません。これが起きる場合、tracking.js の side *= (1 - curl) の行が効いていないか、curl が計算されていません。

