🧪 新教程持續更新中 · 由機械臂到傳感器,跟隨教程從零搭建
跳到主要內容

AmazingHand 網頁控制 ​

用瀏覽器驅動 AmazingHand 靈巧手 —— 左手或右手皆可,並提供兩種操控方式:

  • 手動控制 —— 8 個伺服馬達滑桿 + 19 個預設姿態 + 一鍵回到中位
  • 攝影機遙控 —— MediaPipe 在瀏覽器本機執行,即時把你的手映射到手指上(影像永遠不會離開本機)
  • 雙語介面 —— 在右上角切換中文與英文;選擇會由瀏覽器記住

伺服馬達映射、姿態庫與換算公式全部對齊 AmazingHandControl(官方 GUI)。


快速開始 ​

開啟終端機,進入到 Amazinghand-web 目錄下:

bash
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      # 完全不開序列埠,只驗證介面

接著在瀏覽器開啟 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 核對。

bash
python tools/scan_servos.py --port COM9

把 COM9 換成你實際使用的埠。


伺服馬達映射 ​

來源:AmazingHandControl/hand_logic.py 中的 DEFAULT_HAND_SERVOS 與 docs/zh-hans/CONFIG_FORMAT.md。

手指右手 ID左手 ID
無名指5, 611, 12
中指3, 413, 14
食指1, 215, 16
拇指7, 817, 18

一根手指的兩個伺服馬達分工不同 ⚠️ ​

servo1(奇數 ID)= 屈伸軸   張開 ↔ 握緊
servo2(偶數 ID)= 側擺軸   手指併攏 ↔ 張開

介面上顯示的正是這兩個軸。「手指張不開/合不攏」就代表側擺軸沒有在動 —— 剪刀手那樣的 V 字,來自食指側擺 +17 與中指側擺 −30(一正一負)。

兩者在匯流排上會合成:

servo1 = 屈伸 + 側擺
servo2 = 屈伸 − 側擺

範圍(由官方 19 個姿態加上中位推導而來):

軸範圍說明
屈伸−50 ~ +90張開(負)↔ 握緊(正)
側擺−45 ~ +45併攏(負)↔ 張開(正)

兩條 ID 規則(弄錯會燒毀伺服馬達或使動作反向):

  1. 奇數 ID 是手指的第一個伺服馬達,偶數 ID 是第二個。 兩隻手都成立 (左手 11/12、13/14… 同樣是奇偶交替)。第二個伺服馬達是反向安裝的,換算時要取負號。
  2. 左手不是 9–16,而是 11–18,且以無名指優先編號。 依序接成 1–8 一定是錯的。

image.png


換算 ​

與 AmazingHandControl 中的 apply_pose() 完全相同(已逐個姿態驗證):

1) mirror_pose 為真時,先交換每根手指兩個伺服馬達的值
2) servo_deg = (ID 奇數 ? +1 : -1) × (姿態值 − angle_offset_deg)
手angle_offset_degmirror_pose
右手0是 ⚠️
左手-35.16是

-35.16° 是左手伺服馬達臂相對右手錯開半格的實體偏移量,取自官方設定。

關於 mirror_pose —— 本版本與官方不同

在官方參考件中只有左手是 true。本版本的右手模組同樣將伺服馬達對反裝, 因此右手也設為 true。

這個交換只會翻轉側擺,絕不會動到屈伸:base = (a+b)/2 交換後不變,而 side = (a−b)/2 會變號。所以如果哪隻手的側擺出現鏡像反轉,這就是第一個該檢查的開關。

姿態陣列固定為 8 個值,順序為 [ring, middle, index, thumb],每根手指兩個值 —— 與官方 data/hand_config.yaml 逐位元組相同,方便你直接比對。

實測範例 ​

姿態右手左手
open [-35]*8ID5 −35 / ID6 +35ID11 +0.16 / ID12 −0.16
close [75]*8ID5 +75 / ID6 −75ID11 +110.16 / ID12 −110.16
middle [-17.578]*8ID5 −17.58 / ID6 +17.58ID11 +17.58 / ID12 −17.58

請注意左手的 close 需要 ±110.16°,遠超過右手。這就是 config.json 把限位設為 ±120° 的原因(SCS0009 的滿量程約 ±150°)。限位設成 ±90 會裁掉左手的握拳。


預設姿態(19) ​

姿態姿態
open張開wave1揮手
close / stone握拳wave_r / wave_l向右/向左揮
okOKring_close僅無名指
greeting打招呼middle_close僅中指
victory / two / scissors剪刀手/二pointer_close僅食指
paper布thumb_close僅拇指
one / three一/三hifive擊掌

要修改某個姿態,請編輯 config.json 的 poses 區段;鍵名與官方 YAML 中的名稱一致。

左右手可以使用不同的值 ​

在一個姿態內,positions 是兩隻手共用的。如果某隻手因機械組裝差異需要不同的一組值, 在該筆項目中新增 right 或 left 鍵,它就會覆寫 positions:

jsonc
"close": {
  "label": "握拳",
  "positions": [75, 75, 75, 75, 75, 75, 18, 18],   // 兩隻手共用
  "right":     [ ... 8 個值 ... ]                   // 可選:只給右手用
}

兩種模式 ​

模式行為
右手只下發到右手的 8 個伺服馬達
左手只下發到左手的 8 個伺服馬達(自動套用鏡像 + −35.16° 偏移)

一次控制一隻手。兩隻手位於同一條匯流排上;用頂端的 Target 切換。 切換不會干擾另一隻手 —— 它會維持在目前的姿態。


攝影機遙控 ​

MediaPipe 手部特徵點在瀏覽器本機執行(資源位於 web/mediapipe/,可離線運作)。 這 21 個特徵點會被轉換成 4 根手指的姿態值,並串流下發給所選的手。

一、先擷取三個姿勢 ​

啟動攝影機後,擺好手部姿勢,再點該姿勢的擷取按鈕(約 1 秒取平均)。 順序不拘;再點同一個按鈕即可重新擷取。狀態要三個都擷取完成才會變綠 —— 在那之前不會下發任何值,所以手會停在中位。

按鈕姿勢提供
① 張開手指伸直並張開,拇指向外屈伸的「伸直」端 + 側擺的「張開」端
② 併攏手指伸直並併攏,拇指貼靠食指側擺的「併攏」端
③ 握拳握拳,拇指收向掌心屈伸的「握緊」端

二、每個軸都是一條通過兩個量測點的直線 ​

屈伸與側擺都使用兩個量測到的端點來定義一條直線:

t    = (量測值 − 端點A) / (端點B − 端點A)        // 鉗制在 0~1
目標 = A的目標值 + (B的目標值 − A的目標值) × t

好處是:每根手指量測值的正負號會被自動吸收 —— 因此不需要勾選 任何「讓這根手指反向」的核取方塊。(食指與無名指分居中線兩側,而拇指的關節鏈 也不同,所以它們的方向確實相反 —— 兩個量測端點就能把它吸收掉。)

端點目標值位於 config.json 的 camera 區段:

軸端點無名指中指食指
屈伸伸直 → 握拳−50 → 90−50 → 90−50 → 90
側擺併攏 → 張開−45 → +450 → 0+45 → −45

中指側擺的兩端都是 0(它不會左右擺動)。

三、拇指只做屈伸 ⚠️ ​

拇指的 CMC 是鞍狀(對掌)關節,它的屈伸與外展緊密耦合 —— 當拇指貼靠食指時,它已經處於側擺位置,因此在該處量到的「屈伸零點」是被污染的。 若硬把它拆成兩個軸,兩者都會算錯。

所以拇指只做屈伸;它的側擺永遠是 0(它的兩個伺服馬達會拿到相同的值)。它需要的兩個 參考姿勢是 ①(向外指)與 ③(指向掌心)—— 不需要額外擷取。

四、側擺會隨手指蜷曲而遞減 ​

最終的側擺會依下式縮放:

side = side_measured × (1 − curl)

splayDeg 是 MCP→PIP 方向在手掌橫軸上的投影,而手指屈伸 正是繞著這根軸旋轉 —— 超過某個蜷曲程度後,這個投影會翻轉正負號,量到的 「張開」會突然跳到另一端。

沒有這個遞減,那個突跳會經由 servo2 = flexion − splay 傳遞,並把偶數 ID 的伺服馬達 拖入反向。(奇數 ID 拿到的是 flexion + splay,所以它會朝同方向移動,只是更快 —— 你看不出來。) 症狀是「握拳握到一半,12 與 16 號伺服馬達突然反向」—— 12 是左手無名指的 側擺伺服馬達、16 是左手食指的,正是側擺目標值非零的那兩個。

有了這個遞減,無論量測值多離譜,在全蜷曲時側擺都會正好是 0。 而且握拳本來就該是手指併攏的。

五、骨架顏色 ​

手指顏色
拇指橘色
食指紅色
中指藍色
無名指黃色
小指暗灰色
掌骨中性灰

小指不用於手勢映射,但它的根部 MCP(第 17 號特徵點)定義了右手的手掌座標系, 所以不能直接捨棄。

調校速查表 ​

你想改什麼在哪裡改
各軸的目標值config.json 的 camera 區段
左手與右手的側擺方向相反在 camera.side_sign 中把該隻手設為 -1(1 = 不變)
平滑度tracking.js 中的 SMOOTH(預設 0.35;數值越低越穩但越遲鈍)
蜷曲多快會被判定重新擷取三個姿勢

side_sign 會套用到 Target 下所選的那隻手。修改後重新載入頁面即可 —— 不需要重啟伺服器:

jsonc
"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          中英文文案(詞典 + 右上角切換)
    └── mediapipe/       MediaPipe 資源(本機打包、可離線使用,約 42 MB)

前端/後端通訊協定:

jsonc
// 前端 → 後端   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
急停介面上的紅色按鈕會一次鬆開所有伺服馬達
退出保護按下 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 是由官方原始值 451/571 換算而來。 要依你自己的組裝進行校正,請編輯 config.json 中的 middle.positions。

攝影機追蹤會抖動 → 光線均勻、手掌朝向攝影機。tracking.js 中的 SMOOTH (預設 0.35)—— 數值越高越平滑但越遲鈍。

攝影機手勢錯誤/某根手指不跟隨 → 見上方「攝影機遙控」一節。先檢查三件事:

  1. 三個姿勢都擷取了嗎? —— 狀態必須變綠;在那之前不會下發任何值,手會停在中位
  2. 方向相反代表那個姿勢被反向擷取了 —— 只要重新擷取那一個按鈕即可,不需修改程式碼
  3. 拇指不應該側擺 —— 它只做屈伸,見上方第三節

握拳握到一半,某個伺服馬達突然反向 → 那個伺服馬達應該是偶數 ID(側擺軸,例如左手 12 / 16)。 正常情況下側擺會隨蜷曲遞減到 0,絕不會跳動;如果你看到這個現象,代表 tracking.js 中的 side *= (1 - curl) 那行沒有生效,或是 curl 沒有被計算出來。