核心课程 / 视觉入门课 / EXP-11
EXP-11 · 第 6 阶段 · 视觉驱动动作 · 设计性 · 4 学时

体感控制:把 33 个关节翻译成两条速度指令

上一页拿到了 33 个关键点,可机器人听不懂坐标 —— 它只认 cmd_vel 里的两个数:前进速度和转向角速度。这一页做翻译:手举过肩 → 左转,双手举高 → 前进,蹲下 → 后退,都不是 → 停。有意思的是,判定成立时车默认不会动,这是刻意的。

难度 ⭐⭐ · 4 学时 动作分类规则归一化比较 优先级cmd_vel 安全
01 颜色02 轮廓03 二维码04 检测05 分类06 分割07 跟踪08 人脸09 骨骼10 3D11 体感12 手势

01目标:抬左手就左转,松手就停

🎯 实验目标 · 学完你将能
  • 说清五段链路:像素 → 归一化 → 可见度校验 → 三档比较 → 速度指令,每段各由一个函数负责
  • 跑通单图模式,看到状态栏 Action: Turn Left 与终端 action: turn_left (Turn Left),并解释为什么车没动
  • 调出五档动作:改 SQUAT_HIP_Y 与 HAND_UP_DELTA,让同一姿势判出不同结果,并说清优先级
⚠️ 安全须知 · 先读这一条 本实验配置里 publish_cmd_vel: false,判定成立也不会真的发速度。这是刻意的安全约定:先用海龟仿真验证方向,确认规则对了再谈接底盘。课程不鼓励把它改成 true 在实车上试。
⚠️ 重点与难点 重点:三条判定规则(下蹲 / 双手 / 单手)+ 一条优先级链。
难点:直觉是「左手举高就是左转」,实际是先判优先级 —— 蹲下时无论手怎么举都算后退;而「双手举高」还要求两只手都比对应肩膀高出 0.08,只举一只手不算前进。
INPUT · 你给它
33×4 关键点(像素)

来自 EXP-09 的骨架;单图用 images/pose_demo.jpg,实时走相机话题。

→
OUTPUT · 你拿到
Action: Turn Left

一个动作名 + 一组目标速度(默认不发)。五个动作:Stop / Forward / Backward / Turn Left / Turn Right。

本实验的目标:举手 → 一个动作名 → 一组速度。判定看得见、车轮不动 —— 直到你亲手打开那个开关。

02问题:从「你抬左手」到「车左转」,中间有五段

隔空开车听着简单:抬手、车转。可从「画面里一只手举起来了」到「底盘收到角速度」,中间要过五道手:

像素坐标 (412, 168) px 归一化 x/640, y/480 可见度校验 vis < 0.5 丢弃 三档比较 0.72 / 0.08 速度指令 cmd_vel 每一段都可以单独调试:改一个阈值,只有第 ④ 段变,前面三段不受影响 第 ⑤ 段默认是关的(publish_cmd_vel: false)→ 判定成立,车不动 这条链是本实验唯一需要背下来的东西:五段、四个函数、两个阈值
图 2-1|五段链路。前四段决定「系统认为你在做什么」,第五段决定「要不要真的动」—— 两者故意解耦。
对比你看到的(黑盒)机器必须做的(白盒)
抬手为什么车会转手一抬,车就动? —— 得先把「举手」这件事变成一次坐标比较
为什么蹲下时不判手你根本没想过这个情况? —— 三条规则会同时成立,必须定优先级
判出来了为什么不动以为程序坏了? —— 有一个默认关闭的开关,叫 publish_cmd_vel

三个问号拆成三个具体问题:

① 33 个点,凭什么变成「举手」这一个结论?

② 蹲下时也举着手,算前进还是后退?

③ 明明判出 Turn Left,为什么车一动不动?

五段链路里,前四段是「理解」,第五段是「行动」。把这两件事分开,是安全设计,不是 bug。

03现象:同一张图跑出 Action: Turn Left,可车一动不动

🎬 现象速览 · 30 秒先看效果

你会看到:一张双栏图,右栏火柴人举着右手,底部状态栏写 FPS: 0 Action: Turn Left;终端同时打印 [pose_control] action: turn_left (Turn Left)。而底盘没有任何动静 —— 因为发布开关默认关着。

体感控制单图结果:右侧火柴人举起右手,底部状态栏 FPS: 0 Action: Turn Left
① 板上实跑:右手举起 → Action: Turn Left
输入照片:一个人举起右手站立
② 输入 images/pose_demo.jpg:照片里举起的是他的右手

怎么跑出来:进项目根,加 PYTHONPATH,用模块方式跑单图。

cd ~/ros2_ws/src/xflz_eai_car1/vision/11_pose_control
export PYTHONPATH=$PWD/car1_pose_control
python -m pose_control_core.demo_image --config config/pose_control_config.yaml --save out.jpg
[pose_control] action: turn_left (Turn Left) ← 打开 out.jpg:右栏火柴人举手,底部 Action: Turn Left ← 底盘不动:publish_cmd_vel 默认 false

🔍 看什么:三件事,缺一不可。

  • 动作名是英文的 —— 状态栏写 Turn Left 而不是「左转」。因为 cv2.putText 不带中文字形,写中文会变成一串问号。这是工程上真实存在的小坑。
  • 「他的右手」被判成 Turn Left —— 站在人的视角,右手举起是「向右」;但机器看的是画面坐标:这只手在画面左半边,所以算左。左右的定义要先说清,否则整条规则都反。
  • 没有速度输出 —— 状态栏只有动作名,没有任何 m/s。这就是第 ⑤ 段被关掉的样子。

这就是本实验最终要呈现的现象。第 05 节会让你亲手改阈值,把同一个姿势判成不同结果 —— 那才算真懂规则。

04原理:三关:归一化、比大小、定优先级

CORE IDEA · 一句话原理

把像素坐标除以画面宽高变成 0~1 的比例,然后只做三件比较:髋够不够低、腕够不够高、两只手是不是都高 —— 最后按优先级挑出唯一一个动作。

打个比方:像给动作定规矩的裁判 —— 先看「蹲下了吗」(最优先),再看「双手都举起来了吗」,最后才看「举的是哪只手」。 揭个盖:现实里这些状态会同时成立(蹲下时手也可能举着),所以规则必须写成一条有序的判断链,而不是三条并列的 if。顺序一换,结果就变。

三步回答第 02 节的三问:① 点怎么变结论 → 步骤 2;② 蹲下又举手算谁 → 步骤 3 的优先级;③ 为什么车不动 → 步骤 4。

① 先归一化 x_n = x / 640 y_n = y / 480 y_n 越小 = 越靠上 换屏幕/换分辨率都不变 不归一化,阈值就没有意义 ② 三条比较(全部用归一化后的 y) 下蹲:max(hip_l.y, hip_r.y) > 0.72 左手:wrist_l.y < shoulder_l.y - 0.08 右手:wrist_r.y < shoulder_r.y - 0.08 任何一步 vis < 0.5 → 直接判 Stop 只有两个阈值:0.72 与 0.08 ③ 按优先级取一个 1 下蹲 → 后退 2 双手 → 前进 3 左手 → 左转 4 右手 → 右转 否则 → 停 三档不是并列的 if,是有序的判断链:先命中先返回,后面的规则不再看 所以「蹲下 + 双手举高」判后退,「只举一只手」判转向而不是前进 第 ④ 段(发速度)默认关闭:publish_cmd_vel: false 判定的产物是「目标速度」,底盘跟不跟是另一回事
图 4-1|三关。注意第 ② 关里那条红线:vis 不够就直接 Stop —— 髋部看不见时,整套姿态都不可信。

1为什么第一件事是除以 640 和 480?

吃什么:33×4 的像素关键点 + 帧尺寸。做什么:把 x 除以宽、y 除以高,得到 0~1 的归一化坐标。吐什么:同一套关键点,但「大小无关」了。

因为阈值必须和分辨率解耦。hip.y > 0.72 这句话的意思是「髋部到了画面下方 28% 的位置」;如果直接写像素,换 1280×720 的相机,同一个 345 就完全不是那个意思了。

动它会怎样:这一步没有旋钮,但漏了它,后面所有阈值都只对当前分辨率有效 —— 这是「本地能跑、换设备就废」的典型成因。

2三条比较,各自在比什么?

全部用归一化后的 y(越小越靠上),只比大小,不做任何形状判断:

  • 下蹲:两个髋关节里较低的那个,y 是否超过 0.72。用 max 是因为侧身时两点高度不同,取更低的那个更保守。
  • 举手:手腕 y 是否比同侧肩膀的 y 小 0.08 以上。减 0.08 是为了避开「手自然下垂时略微抬高」的抖动区,不然肩膀一动就误判举手。
  • 眼睛不看:注意这套规则完全没用到脸或手型 —— 33 点里它只用 6 个(髋 2、肩 2、腕 2)。

动它会怎样:SQUAT_HIP_Y 调小(0.6)→ 稍微屈膝就判后退;HAND_UP_DELTA 调大(0.2)→ 手要举得更高才算数。两个阈值决定「这套动作好不好做」。

3同时满足好几条,听谁的?

这是第 02 节第二问的答案,也是本页最容易写错的地方:顺序就是语义。

if 髋不够高 believe → return BACKWARD
if 左手 且 右手 都高   → return FORWARD
if 只有左手高         → return TURN_LEFT
if 只有右手高         → return TURN_RIGHT
return STOP

源码里的写法与这五步完全一致(判断链 + 提前 return)。所以:

  • 蹲下 + 双手举高 → Backward(第一条就命中了)。
  • 双手都高 → Forward;只举一只手 → 转向,不是前进。
  • 髋部 vis < 0.5 → 直接 Stop,后面几条都不看。

动它会怎样:把优先级换个顺序(比如先判单手),蹲下时就会变成转圈 —— 因为蹲下的人往往手也抬着。规则顺序是设计出来的,不是随手写的。

4判出来了,为什么车不动?

因为「判定」和「执行」是两个开关。配置里 publish_cmd_vel: false 是默认值,action 会照常输出到状态栏与终端,但不会发布速度话题。

这是有意的安全设计:调规则时人常常做出各种奇怪姿势,如果判定错就真发速度,车会突然冲出去。先在仿真里验方向、再谈接底盘,是这门课反复强调的顺序。

动它会怎样:改成 true 并接上底盘,一次误判就是一次真实位移 —— 这也是为什么页面不给你「一键开启」的按钮。

回看第 03 节那张图:「他的右手」判成 Turn Left,因为机器看的是画面哪半边;而车不动,是因为第 ⑤ 段被关着 —— 两件事都不是错误。

先认识 5 个词

归一化坐标

像素除以宽高得到的 0~1 比例。什么时候失效:画面被裁切/缩放后没同步更新比例,阈值就错位。

优先级链

多条规则有序判断、先命中先返回。什么时候失效:顺序写错,蹲下+举手会判成转圈。

HAND_UP_DELTA

手腕要比肩膀高出多少才算举手,默认 0.08。什么时候失效:设太小,肩膀抖动就误判;设太大,手要举很高才认。

SQUAT_HIP_Y

髋部低于画面哪个位置算下蹲,默认 0.72。什么时候失效:相机机位太低或太高时,站着的髋部就已经接近 0.72。

cmd_vel

ROS 的速度话题,通常两个数:线速度与角速度。什么时候失效:发出去前不做限幅,一次误判就是一次猛冲。

什么时候会翻车:五个真实边界选读 1 分钟
  • 相机机位不对 —— 相机放得低,站着的髋部 y 就已经接近 0.72,一直判后退。机位高度是这条规则的隐含前提。
  • 左右手搞反 —— 镜像画面(自拍视角)会把左右翻过来,转向全反。先确认画面是否被镜像。
  • 只露上半身 —— 髋部不可见 → vis 不够 → 永远 Stop。这是「怎么都不动」的最常见原因。
  • 手举得不够高 —— 离 0.08 只差一点点,判定在举手与不举手之间抖动,动作来回跳。
  • 两人入镜 —— 姿态只给一个人(分最高),旁边的助手会被静默忽略,也可能抢走判定。
步数据变成什么为什么非它不可 / 少了会怎样谁在干动参数会怎样
1像素 → 归一化 (33,4)不归一化,阈值绑死分辨率_norm无参数
2点 → 三条布尔判断 + vis 校验不比大小就无法把姿态变成结论classify_action0.72 / 0.08 / 0.5 三个阈值
3布尔 → 唯一动作名不定优先级,冲突时结果随机判断链换个顺序,蹲下就变转圈
4动作名 → 速度(默认不发)判定与执行分离是安全底线publish_cmd_vel改 true 就真的会动

只看第 2、5 两列:第 2 列是数据形态,第 5 列是你能拧的旋钮。第 4 行请谨慎对待 —— 那是唯一会动到物理世界的旋钮。

05实操:单图看判定 / 海龟验方向 / 改规则

命令与原理的对应关系。

环节函数(文件)吃哪个参数对应原理第几步
关键点姿态引擎(engine.py,复用 EXP-09 模型)min_detection_confidence链上游
归一化_norm(actions.py:43)帧尺寸步骤 1
三档比较classify_action(actions.py:51)SQUAT_HIP_Y HAND_UP_DELTA MIN_VIS步骤 2、3
状态栏view.put_status(view.py:29)(无)步骤 4 的可视化
速度发布节点内 publish_cmd_vel 分支publish_cmd_vel步骤 4
  1. 单图看判定,先信「判定」这一层约 3 分钟

    命令见第 03 节。判据两条:状态栏的动作名,和终端那一行。

    [pose_control] action: turn_left (Turn Left) 状态栏:FPS: 0 Action: Turn Left

    再换一张举手方向相反的照片(或拍一张自己),确认判出来是 Turn Right —— 左右搞反是这条链最常见的坑。

  2. 用海龟验方向,别用车约 6 分钟

    打开 publish_cmd_vel 之前,先用 turtlesim 确认「左转」在速度里到底是哪个符号:

    source /opt/ros/humble/setup.bash
    ros2 run turtlesim turtlesim_node
    弹出海龟窗口(板上桌面或 noVNC)
    ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.0}, angular: {z: 0.6}}"
    海龟原地逆时针转 —— 正 z 是左转

    这一步把「动作名 → 速度符号」的映射钉死。接底盘前必须先过这一关。

  3. 改一个阈值,看判定怎么切约 8 分钟

    在 actions.py 里把 SQUAT_HIP_Y 从 0.72 改成 0.95 后重跑单图:站姿的髋部永远到不了 0.95,于是「下蹲」这一档被彻底关掉。

    再把 HAND_UP_DELTA 从 0.08 改成 0.30 —— 同一张照片里的手举得不够高,动作会从 Turn Left 掉回 Stop。这两次改动让你确认:阈值就是这套系统的「性格」。

🔁 现象呼应这三步跑完看到的,就是第 03 节那张图:Action: Turn Left + 终端 action: turn_left + 底盘不动。而且你现在能说出「为什么是他的右手判成左转」「为什么不发速度」。两处对上了,才叫走通。

你该看到的画面

正常
动作名跟着姿势变

单手举 → 左/右转;双手举 → 前进;蹲下 → 后退;站直 → 停。

永远 Stop
髋部没看见

只露上半身时 vis 不够,第一条校验就返回 Stop。

动作来回跳
卡在阈值附近

手举得刚好在 0.08 边界,判定抖动。可加几帧平滑。

动作对但车不动
发布开关关着

这是默认值,不是故障。安全设计,别急着改。

踩坑速查

现象原因怎么办
一直判 Stop髋部不可见 / 只露上半身 / 机位太低让全身入镜;确认相机高度
左右转向反了画面被镜像,或按「人的左右」理解成「画面左右」确认无镜像;规则用的是画面坐标
蹲下时判成转圈判断链顺序被改过恢复顺序:下蹲 → 双手 → 单手
动作名显示成问号用中文写进了 label保持 ASCII(cv2.putText 无中文字形)
判定对但底盘不动publish_cmd_vel 默认 false先用 turtlesim 验方向,再谨慎考虑开启
报 ModuleNotFoundError: pose_control_core没加 PYTHONPATHexport PYTHONPATH=$PWD/car1_pose_control
换分辨率后判定全乱阈值绑了像素而不是归一化值确认 _norm 生效,别绕过它
进阶 1 · 加一个「双手放下 → 停车」的急停姿势约 20 分钟

现有五档里 Stop 是「都不是」的兜底,不是主动姿势。可以把「双手举起交叉」定义成显式急停,判定优先级排第一 —— 任何时候做出这个姿势都立刻停。

这也是安全设计的一般做法:兜底不等于急停,急停要有独立的、优先级最高的入口。

进阶 2 · 给动作加几帧确认约 25 分钟

现在每帧独立判定,手在阈值附近会来回跳。加一个长度为 5 的滑动窗口,同一动作连续出现 3 帧才切换,抖动立刻消失。

代价是响应变慢几帧(约 0.2 秒)。这就是 EXP-07 里 n_init 的同一个思想:确认次数换稳定性。

🧪 Vibe Coding · 自己把这段代码读明白约 20 分钟

你要产出三样:模块职责表 → 控制流流程图 → 时序图。

提示词 1 · 模块职责分析

请阅读这个体感控制项目的模块:pose_control_core/actions.py、engine.py、pose.py、view.py、demo_image.py、pose_control_node.py。
它们共同完成「姿态关键点 → 动作名 → 速度指令」。
请输出一张表,每行一个模块,四列:模块名 / 一句话职责 / 被谁调用 / 从哪个配置项取值。
关键函数标行号;不确定处请明说。

提示词 2 · 控制流流程图

请为 actions.classify_action 画一张流程图。
必须体现:像素如何归一化;髋部可见度不足时如何提前返回 Stop;三条判断的先后顺序(下蹲 / 双手 / 单手)以及为什么这个顺序重要。
每个判断框写出完整的比较表达式。

提示词 3 · 时序图

请为节点模式画一张时序图,参与者:相机话题、姿态引擎、classify_action、状态栏绘制、cmd_vel 发布(受 publish_cmd_vel 控制)、turtlesim 或底盘。
标出判定结果的发布频率,以及 publish_cmd_vel=false 时链路上哪一段被断开。

自查三问:能指出三个阈值在代码里的位置?流程图的判断顺序与源码一致?时序图里画出了那个「开关」的位置?

三处必然的差异:

差异点AI 版常见写法工程版(本仓)为什么工程这么写
多个条件同时成立三个独立 if,最后覆盖或并列输出有序判断链 + 提前 return(actions.py:51-79)蹲下时手往往也举着;不排序就会来回跳或有歧义
可见度处理只判坐标,不判 vis髋部 vis < 0.5 直接 Stop(actions.py:58-60)点位是猜的还发速度,等于拿噪声开车
显示文字直接写中文动作名ASCII label(Turn Left)cv2.putText 无 CJK 字形,中文会变问号

改一处、先预测再验证:把 HAND_UP_DELTA 改成 0.25,先写下手要举多高才判转,再跑一次对答案。

06互动:拖三个高度,看五档动作怎么切

📌 这一节是「掌握检查」:亲手改一个参数,看现象是否朝你预期的方向变,还要能说出为什么 —— 说得出,才算掌握。

规则只有三条、优先级四层,但「谁先命中」不看一遍永远记不住。这里把三个高度交给你:左腕、右腕、髋部。判定完全照 classify_action 的写法重算。

⚡ 参数实验台 · 三个高度 → 五档动作
0.30
0.70
0.60
左腕 0.30、右腕 0.70、髋 0.60 → 左手举过肩、右手没有 → 判定 Turn Left。
互动 6-1|肩在 y≈0.45、阈值 HAND_UP_DELTA = 0.08(手腕要比肩小 0.08 才算举)、SQUAT_HIP_Y = 0.72。判定顺序照源码:下蹲 → 双手 → 单手 → 停,先命中先返回。

怎么读出规律:先把髋拉到 0.9(蹲下)—— 无论手怎么放都判 Backward,这就是优先级第一的威力;再把髋拉回 0.6、两只手都拉到 0.3 —— 变 Forward;只抬起一只 —— 变成对应方向的转向。

髋 0.90
Backward

蹲下最优先,手举得再高也走不出去。

双手都 0.30
Forward

两只腕都低于肩-0.08,判双手举高。

只抬左手
Turn Left

第 03 节那张图就是这一档(画面左侧的手)。

双手都 0.70
Stop

低于肩线,一档都不命中,走兜底。

判定不是三个并列的 if,是一条有序的链。蹲下 > 双手 > 单手 > 停 —— 顺序一换,蹲着的人就会开始转圈。

07验证:三道小测

✅ 目标对账 · 验证掌握得有多好

回页首对照「实验目标」:① 五段链路能否背出来 → 试 Q1;② 单图跑通、动作名与终端对上、能否解释「车不动」→ 试 Q3;③ 两个阈值改过没有 → 试 Q2,并回第 06 节把髋拉到 0.9 看优先级。三条全 ✓ 就完成。

Q1判断「举手」用的是哪一组数?
鼻尖和髋部的相对位置
手腕的归一化 y 与同侧肩膀的归一化 y 相差超过 0.08(y 越小越靠上)
手腕和脚踝的距离
对。只比 y,而且要先归一化(除以画面高),再减 0.08 做抖动保护。整套规则里其实只用到 6 个点:髋 2 个、肩 2 个、腕 2 个,其余 27 个点都不参与。
Q2一个人蹲下的同时把双手举高,判定结果是什么?
Forward(双手举高优先)
Backward —— 下蹲在判断链的最前面,先命中先返回
Turn Left
对。这就是优先级的作用:squat > both_up > left/right。写成三个并列的 if 就会出现「后写的覆盖先写的」这种玄学行为。顺序是设计出来的,改顺序等于改语义。
Q3状态栏明明写着 Action: Turn Left,底盘却不动。最可能的原因是?
规则写错了,动作名是假的
publish_cmd_vel 默认是 false —— 判定照常输出,但不会真的发布速度
相机帧率太低
对。这是刻意的安全设计:判定与执行解耦,先在 turtlesim 里验方向,再谈接底盘。把开关改成 true 之前,务必确认「正 z 是左转」这类符号约定 —— 一次误判就是一次真实位移。

08章节总结:把 33 个关节翻成两条速度数

这页讲了什么,它们怎么串起来

概念它解决了什么和别的关系
归一化让阈值与分辨率解耦上游来自 33 点像素坐标,下游所有比较都用它
三条比较把姿态变成三个布尔值只用 6 个关节点;两个阈值(0.72 / 0.08)决定灵敏度
可见度校验点位不可信时不动作髋 vis < 0.5 直接 Stop,是链条最前面的闸
优先级链多条规则同时成立时给出唯一结果顺序即语义:蹲下 > 双手 > 单手 > 停
动作名 label给状态栏一个可显示的名字必须 ASCII,因为 cv2.putText 无中文字形
cmd_vel把动作变成线速度与角速度默认不发(publish_cmd_vel: false),安全底线
turtlesim在没有真车的情况下验证方向符号接底盘前的必经一步
⚠ 三个反直觉的地方 ① 直觉是左右按人的左右算,实际是按画面的左右算 —— 照片里他的右手在画面左侧,所以判 Turn Left;
② 直觉是三条规则并列、谁先写谁生效,实际是有序判断链,蹲下会压过一切;
③ 直觉是判出动作就该动,实际是后面还有一个默认关闭的发布开关 —— 判定与执行必须解耦。
💡 四条要注意 ① HAND_UP_DELTA = 0.08 不是「举手高度」,而是手腕比同侧肩膀高出多少;肩膀位置由姿态本身提供,所以人离镜头远近不影响这个判断;
② SQUAT_HIP_Y = 0.72 隐含了相机高度:机位太低时站着的髋部就接近 0.72,会一直判后退;
③ 状态栏里是 Turn Left 不是「左转」:cv2.putText 没有中文字形,写中文会变问号;
④ publish_cmd_vel: false 是默认值,不鼓励改;一定要开,先在 turtlesim 里把方向验完。

前置依赖 · 缺一样就跑不起来

类型具体项缺了会怎样 / 怎么补
环境板上 Python 在 ~/ros2_venv;实时/海龟验证需 ROS 2 Humble 两级 source未激活 venv → python: command not found;未 source ROS → ImportError: librcl_action.so
Python 库numpy、opencv-python、pyyaml、rknnlite、mjpeg_streamer、turtlesim(验方向用)缺 turtlesim 只是没法验方向,单图判定不受影响
模型 / 资源models/pose_landmark_full.rknn(与 EXP-09 同一个)+ images/pose_demo.jpg缺模型没有任何输出;示例图太小会让点位抖动,判定跟着跳
配置config/pose_control_config.yaml:source.mode: image、server.port: 8083、publish_cmd_vel: falsemode: image 未填 rgb_path 会报错;误改 publish_cmd_vel 需自负后果
运行方式项目根 + PYTHONPATH 含 car1_pose_control,用 -m 运行直接跑 .py 会因相对导入失败

这套代码用在哪儿

  • 隔空操控:展厅里的机械臂、AGV 的粗略遥控,人不必接触设备。
  • 安全动作门:车间里「蹲下」= 进入检修姿态,自动降速或停机 —— 这类规则简单可靠,比语义模型更适合安全回路。
  • 康复训练打分:把「蹲到位没有」变成一条百分比曲线,做重复次数与幅度统计。
  • 不该用它的场合:需要精细指令(选菜单、调速度档位)时,五个离散动作远远不够 —— 那是 EXP-12 手势识别的地盘。
核心记忆点:归一化 → 三条比较 → 按优先级取一个 → (可选)发速度。33 个关节最后只用到 6 个,两个阈值决定这套系统的手感。

最后一个实验把镜头收回到手上:33 点判大动作够用,但「比个耶」「竖大拇指」这种精细语言,需要 21 个手部点 + 一套四模型级联。