🧪 新教程持续更新中 · 从机械臂到传感器,跟着教程从零搭建
跳到主要内容

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 。

在网页里选串口 ​

页面顶部有一排串口工具条:

串口  [COM3 ★ USB-Enhanced-SERIAL CH343 ▾]  [刷新]  [连接]  [断开]  已连接 COM3
  • 下拉框列出本机所有串口,常见 USB 转串口芯片(CH34x / CP210x / FTDI 等)标 ★ 并排在最前
  • 点 刷新 重新扫描(插拔板子后用)
  • 点 连接 连接选中的串口;连上后会自动回中位
  • 点 断开 先松开力矩再断开,舵机不会僵在原位
  • 换串口直接选新的点「连接」即可,旧口会自动关掉

--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)= 屈伸轴  position axis   张开 ↔ 握紧
servo2(偶数 ID)= 侧摆轴  side axis       手指之间并拢 ↔ 张开

界面上就按这两个轴显示。「手指张不开 / 合不拢」就是侧摆轴没动 —— 比如剪刀手那种 V 字,靠的是食指侧摆 +17、中指侧摆 −30(一正一负)。

下发时合成:

servo1 = 屈伸 + 侧摆
servo2 = 屈伸 − 侧摆

范围(统计官方 19 个姿态 + 中位得出):

轴范围说明
屈伸−50 ~ +90张开(负) ↔ 握紧(正)
侧摆−45 ~ +45并拢(负) ↔ 张开(正)

两条 ID 约定(错了会烧舵机或动作反向):

  1. 奇数 ID 是每根手指的第一个舵机,偶数 ID 是第二个。 两边都成立 (左手 11/12、13/14… 也是奇偶交替)。第二个舵机是反向安装的,换算时要取负。
  2. 左手 ID 不是 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 个值,顺序 [无名指, 中指, 食指, 拇指],每根手指 2 个值 —— 与官方 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
中位 [-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° 偏移)

一次只控制一只手。两只手都接在同一根总线上,用顶部「控制对象」切换; 切换不影响另一只手 —— 它保持当前姿态停在原地。


摄像头互动遥控 ​

浏览器本地跑 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 = 屈伸 − 侧摆 把偶数 ID 舵机带着反向掉头 (奇数 ID 拿到的是 屈伸 + 侧摆,同向,只是变快,看不出异常)。 现象就是「握拳握到一半,12 和 16 号舵机猛地反转」—— 12 是左手无名指侧摆舵机、 16 是左手食指侧摆舵机,正好是侧摆目标非零的那两根。

加上衰减后,握紧时侧摆恒为 0,跟测量值多离谱无关。而且握拳本来就该是并拢的。

五、骨骼配色 ​

手指颜色
大拇指橙
食指红
中指蓝
无名指黄
小拇指暗灰
掌骨中性灰

小拇指不参与手势映射,但它的根部 MCP(17 号点)用来定右手的手掌坐标系,所以不能删。

调参速查 ​

想改什么改哪儿
各轴目标值config.json 的 camera 段
左右手侧摆方向相反camera.side_sign 里那只手改成 -1(1 = 原样)
平滑程度tracking.js 的 SMOOTH(默认 0.35;越小越稳但越迟钝)
手指弯曲的判定快慢重新采一次三个姿势

side_sign 是按**「控制对象」选的那只手**生效的,改完刷新页面即可,不用重启服务:

jsonc
"side_sign": { "right": 1, "left": -1 }   // 左手把整根侧摆轴取反

屈伸(flexion)不受 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 资源(本地打包,离线可用,约 42MB)

前后端协议:

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° 不下发,避免总线被噪声占满
写入限流最小间隔 20ms
急停界面红色按钮,一次松开全部舵机
退出保护Ctrl+C 时自动关闭力矩
单手调试--hand left/right

常见问题 ​

动作方向反了 → 检查接线是否按「奇数 ID 在前」;左手还要确认 angle_offset_deg = -35.16。

左手握拳幅度不够 / 舵机叫 → 限位可能还是 ±90,检查 config.json 里是否已放到 ±120。

手指不动 / 报「需要 N 个电机检测到 M 个」 → 跑 tools/scan_servos.py, 左手 ID 最容易接错。

中位不对 → 中位值 -17.578125 由官方 raw 451/571 换算而来, 要按自己的机械装配标定就改 config.json 的 middle.positions。

摄像头识别不准 → 光线均匀、手掌正对镜头。tracking.js 里 SMOOTH(默认 0.35) 越大越平滑但越迟钝。

摄像头手势不对、某根手指不跟手 → 见上面「摄像头互动遥控」一节。先自查三件事:

  1. 三个姿势都采了没 —— 状态要变绿;采完之前一律不下发,手会停在中位不动
  2. 方向反了就是那个姿势采反了 —— 重采那一个按钮即可,不用改代码
  3. 大拇指不该左右摆 —— 它只做屈伸,见上面第三节

握拳握到一半,某个舵机猛地反转 → 该舵机应该是偶数 ID(侧摆轴,比如左手 12 / 16)。正常情况侧摆会随弯曲衰减到 0、不会跳;出现这个现象说明 tracking.js 里 side *= (1 - curl) 那行没生效,或者 curl 没算出来。