AmazingHand ウェブコントロール
ブラウザで AmazingHand 多指ハンドを操作します。左手/右手の 2 モード、操作方法は 2 通りです:
- 手動制御 —— サーボスライダー 8 本 + プリセットポーズ 19 種 + ワンクリックで中間位置へ復帰
- カメラテレオペ —— ブラウザ内でローカルに MediaPipe が手の動きを認識し、リアルタイムで指にマッピング(映像はマシン外に出ません)
- 中国語/英語の切り替え —— 右上で切り替え。選択はブラウザに記憶され、次回も前回の言語で開きます
本ページは共通のウェブコントロール説明です。SCS0009 専用のマッピング/換算は SCS0009 ウェブコントロール、PWM 専用の配線/書き込み/キャリブレーションは PWM ウェブコントロール を参照してください。
クイックスタート
ターミナルを開き、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 --driver pwm # ESP32-S3 PWM サーボ版を使用
# python server.py --dry-run # シリアルポートを一切使わず、UI のみ検証ブラウザで http://localhost:8000 を開きます。
静的フロントエンドを直接使うこともできます(Web Serial、Python サーバー不要):
- オンライン:https://juxi-technology.github.io/AmazingHand/Amazinghand-web-io/(Chrome / Edge)
- ローカル:リポジトリのルートで
python -m http.server 8099を実行し、http://localhost:8099/Amazinghand-web-io/ を開きます
ページ上でシリアルポートを選ぶ
ページ上部にツールバーがあり、先にドライバーを選び、次にシリアルポートを選びます:
Driver [SCS0009 bus ▾] Port [COM3 ★ USB-Enhanced-SERIAL CH343 ▾] [Refresh] [Connect] [Disconnect] Connected to COM3- ドライバーのドロップダウン:SCS0009 bus(公式バスサーボ、デフォルト)または PWM(ESP32-S3)(PWM ウェブコントロール を参照)
- ドロップダウンには本機のすべてのシリアルポートが並びます。一般的な USB シリアルチップ(CH34x / CP210x / FTDI など)には ★ が付き、先頭に並びます
- Refresh で再スキャン(ボードを挿抜した後に使います)
- Connect で選択中のシリアルポートを開きます。接続後は自動的に中間位置へ戻ります
- Disconnect は先にトルクを解放してから切断するので、サーボがその位置のまま固まりません(PWM 版にはトルクコマンドがありません。後述を参照)
- ポートを切り替えるときは、新しいポートを選んで「Connect」を押すだけです。古いポートは自動的に閉じられます
静的デプロイ(GitHub Pages)には Python サーバーがありません:ブラウザはプライバシー保護のためシリアルポートを無断で列挙できず、許可したデバイスしか見えません。その場合は Add port… をクリックし、ブラウザのダイアログで制御ボードを選びます。上記の「本機のすべてのシリアルポートを列挙する」動作は
server.pyサーバーモードにのみ当てはまります。

--no-connectはこのフローと組み合わせて使います:起動時にサーバーだけを起動し、シリアルポートには一切触れないため、 誤ったポートへ自動接続してハンドが勝手に動くことがありません。起動時に自動接続させたい場合はこの引数を外してください。
シリアルログは server.log に出力されます(.bat から起動するとウィンドウが一瞬で消えるので、このファイルを見てください)。
配線チェック
検出したサーボ ID を一覧表示し、config.json と左手・右手ごとに突き合わせます。
python tools/scan_servos.py --port COM9COM9 は実際のポート番号に置き換えてください。
このツールは SCS0009 バスサーボにのみ適用されます。PWM 版の配線チェックは PWM ウェブコントロール を参照するか、
AmazingHand-PWM/tools/serial_test.pyを使用してください。
プリセットポーズ(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 個だけへ送信(SCS0009 はミラーと −35.16° のオフセットを自動適用。PWM は左手のチャンネルマッピングを使用) |
一度に片方の手だけを制御し、上部の「Target」で切り替えます。切り替えてももう片方の手は影響を受けず、その場のポーズを保持します。SCS0009 では両手を同じバスに接続できます。PWM では左右それぞれを 1 枚の ESP32-S3 が制御します。
カメラテレオペ
ブラウザ内でローカルに MediaPipe の手のランドマークを実行し(アセットは Amazinghand-web/web/mediapipe/ にあり、オフラインで使用可能。ページは Amazinghand-web-io/mediapipe/ も優先的に検出します)、 21 個のランドマークを 4 本の指のポーズ値へ変換して、選択中の手へリアルタイムで送信します。

1. まず 3 つのポーズを取得する
カメラを起動したら、ポーズを作って対応するボタンをクリックし 1 回取得します(約 1 秒間の平均を取ります)。 順序は問いません。取り直すときは同じボタンをもう一度クリックします。3 つすべてを取得して初めてステータスが緑になります。 取得が終わるまでは何も送信されず、ハンドは中間位置のポーズを保ちます。
| ボタン | ポーズ | 提供するもの |
|---|---|---|
| ① 開く | 指をまっすぐ開き、親指を外側へ向ける | 屈伸の「まっすぐ」端 + 開きの「開く」端 |
| ② 揃える | 指をまっすぐ揃え、親指を人差し指に付ける | 開きの「揃う」端 |
| ③ 握る | 握り拳を作り、親指を手のひら側へ入れる | 屈伸の「握る」端 |
2. 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 つのサーボの開き差動量が 0)。親指が必要とする 2 つの基準ポーズは ①(外を指す)と ③(手のひらを指す)で、追加で取得する必要はありません。
4. 開きは曲げに応じて減衰する
最終的な開きは次の式でスケールされます:
side = side_measured × (1 − curl)splayDeg は MCP→PIP 方向を手のひらの横軸へ射影したもので、指の屈曲はまさにこの軸を中心に回転します —— ある程度曲げるとこの射影の符号が反転し、「開き度」が突然反対の端へ飛びます。
減衰がないと、この飛びは servo2 = 屈伸 − 開き を通じて偶数 ID のサーボを逆方向へ回してしまいます (奇数 ID は 屈伸 + 開き を受け取るため変化の向きが逆で、目立ちにくくなります)。 症状は「握り拳の途中で 12 番と 16 番のサーボが急に逆転する」で、12 は左手薬指の 2 つ目のサーボ、16 は左手人差し指の 2 つ目のサーボ —— 開き成分が非ゼロのときに差動で巻き込まれる 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 } // 左手の開き軸全体を反転屈伸(flexion)は side_sign の影響を受けません。
プロジェクト構成
Amazinghand-web-io/ フロントエンド(静的、単独でデプロイ可能)
├── index.html
├── app.js 画面、ドライバー切り替え、ポーズとカメラ UI
├── io.js SCS0009 Web Serial バックエンド
├── pwm.js PWM Web Serial バックエンド
├── common.js 共有設定、ポーズ換算、リミット
├── ws.js WebSocket バックエンド(Python サーバーモード)
├── tracking.js MediaPipe カメラトラッキング
├── i18n.js 中国語/英語の文言
├── config.json ドライバー、サーボマッピング、ポーズライブラリ、カメラパラメータ
└── tools/pwm_protocol_test.mjs
Amazinghand-web/
├── server.py HTTP + WebSocket + シリアルサーバー
├── requirements.txt
├── tools/scan_servos.py SCS0009 バスをスキャンして ID を照合
└── web/mediapipe/ MediaPipe アセット(オフライン、約 42MB)フロントエンド/バックエンド間のプロトコル:
// フロントエンド → バックエンド target: "left" | "right"
{ "cmd": "ports" } // シリアルポートを再スキャン
{ "cmd": "driver", "driver": "pwm" } // scs0009 | pwm
{ "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 } // SCS0009 のみ
{ "cmd": "stop" }
// バックエンド → フロントエンド
{ "type": "state", "hands": {...}, "poses": {...}, "positions": {...},
"axes": {...}, "camera": {...}, "target": "left", "driver": "scs0009",
"capabilities": { "torque": true, "speed": true }, "pwm": {...},
"limits": {...}, "port": { "available": [...], "current": "COM3", "connected": true } }
{ "type": "ports" | "connected" | "disconnected", "ports": [...], ... }
{ "type": "error", "msg": "连接 COM_FAKE 失败:系统找不到指定的文件。" }安全設計
| 項目 | 説明 |
|---|---|
| ポーズリミット | ポーズ値 ±120°(config.json の limits)。19 ポーズすべてが範囲内であることを確認済み |
| 変化の間引き | 0.5° 未満の変化は送信しないため、バスがノイズで溢れない |
| 書き込みスロットル | SCS0009 は最小間隔 20ms、PWM 直接駆動は約 30ms |
| 緊急停止 | SCS0009:すべてのトルクを解放。PWM:0xFF を送信してジェスチャーを中止。サーボは通電したまま位置を保持 |
| 終了時の保護 | SCS0009:Ctrl+C でトルクを解放。PWM:シリアルポートを切断後もサーボは位置を保持 |
| 片手デバッグ | --hand left/right |
よくある質問
カメラの認識が不正確 → 照明を均一にし、手のひらをカメラへ正対させます。tracking.js の SMOOTH(デフォルト 0.35)は大きいほど滑らかですが反応が鈍くなります。
カメラのジェスチャーがおかしい、ある指が追従しない → まず 3 つのポーズをすべて取得したか(ステータスが緑)を確認します。向きが逆なら該当のポーズを取り直します。親指は屈伸のみで、開きには参加しません。
握り拳の途中であるサーボが急に逆転する → 開きが曲げに応じて 0 まで減衰しているか(side *= (1 - curl))、開きの制御量の符号/大きさが異常でないかを確認します。
SCS0009 の向き/ID/中間位置の問題 → SCS0009 ウェブコントロール と SCS0009 サーボ調整とキャリブレーション を参照してください。
PWM の接続/ボーレート/キャリブレーションの問題 → PWM ウェブコントロール を参照してください。
その他の問題 → FAQ。

