AmazingHand 网页控制
用浏览器控制 AmazingHand 灵巧手,左手 / 右手两种模式,两种操作方式:
- 手动控制 —— 8 个舵机滑块 + 19 个预设姿态 + 一键回中位
- 摄像头互动遥控 —— 浏览器本地 MediaPipe 识别手部动作,实时映射到手指(画面不出本机)
- 中英文切换 —— 右上角切换,选择记在浏览器里,下次打开还是上次那门语言
舵机映射、姿态库、换算公式全部对齐 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 # 完全不开串口,只验证界面浏览器打开 http://localhost:8000 。
在网页里选串口
页面顶部有一排串口工具条:
串口 [COM3 ★ USB-Enhanced-SERIAL CH343 ▾] [刷新] [连接] [断开] 已连接 COM3- 下拉框列出本机所有串口,常见 USB 转串口芯片(CH34x / CP210x / FTDI 等)标 ★ 并排在最前
- 点 刷新 重新扫描(插拔板子后用)
- 点 连接 连接选中的串口;连上后会自动回中位
- 点 断开 先松开力矩再断开,舵机不会僵在原位
- 换串口直接选新的点「连接」即可,旧口会自动关掉
--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 |
每根手指的两个舵机分工不同 ⚠️
servo1(奇数 ID)= 屈伸轴 position axis 张开 ↔ 握紧
servo2(偶数 ID)= 侧摆轴 side axis 手指之间并拢 ↔ 张开界面上就按这两个轴显示。「手指张不开 / 合不拢」就是侧摆轴没动 —— 比如剪刀手那种 V 字,靠的是食指侧摆 +17、中指侧摆 −30(一正一负)。
下发时合成:
servo1 = 屈伸 + 侧摆
servo2 = 屈伸 − 侧摆范围(统计官方 19 个姿态 + 中位得出):
| 轴 | 范围 | 说明 |
|---|---|---|
| 屈伸 | −50 ~ +90 | 张开(负) ↔ 握紧(正) |
| 侧摆 | −45 ~ +45 | 并拢(负) ↔ 张开(正) |
两条 ID 约定(错了会烧舵机或动作反向):
- 奇数 ID 是每根手指的第一个舵机,偶数 ID 是第二个。 两边都成立 (左手 11/12、13/14… 也是奇偶交替)。第二个舵机是反向安装的,换算时要取负。
- 左手 ID 不是 9–16,是 11–18 且按「无名指优先」编号。 按 1-8 顺延接一定错。

换算公式
与 AmazingHandControl 的 apply_pose() 完全一致(已逐姿态比对验证):
1) mirror_pose 为真时,先交换每根手指两个舵机的值
2) servo_deg = (ID 奇数 ? +1 : -1) × (姿态值 − angle_offset_deg)| 手 | angle_offset_deg | mirror_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]*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 | 剪刀手 / 二 | pointer_close | 仅食指握 |
paper | 布 | thumb_close | 仅拇指握 |
one / three | 一 / 三 | hifive | 击掌 |
要自行修改姿态的话,编辑 config.json 的 poses 段;键名对应官方 YAML 里的名字。
左右手可以是不同的姿态值
每条姿态里的 positions 是左右手共用的。如果哪只手因为机械装配差异需要另一套值, 在同一条里加一个 right 或 left 键即可,它会覆盖 positions:
"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 → +45 | 0 → 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 是按**「控制对象」选的那只手**生效的,改完刷新页面即可,不用重启服务:
"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)前后端协议:
// 前端 → 后端 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) 越大越平滑但越迟钝。
摄像头手势不对、某根手指不跟手 → 见上面「摄像头互动遥控」一节。先自查三件事:
- 三个姿势都采了没 —— 状态要变绿;采完之前一律不下发,手会停在中位不动
- 方向反了就是那个姿势采反了 —— 重采那一个按钮即可,不用改代码
- 大拇指不该左右摆 —— 它只做屈伸,见上面第三节
握拳握到一半,某个舵机猛地反转 → 该舵机应该是偶数 ID(侧摆轴,比如左手 12 / 16)。正常情况侧摆会随弯曲衰减到 0、不会跳;出现这个现象说明 tracking.js 里 side *= (1 - curl) 那行没生效,或者 curl 没算出来。

