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

手势识别:四道工序,把一只手窄化成一个标签

课程最后一个实验。手在画面里往往只占一小块,姿势又有十几种,一步到位认不出来。工程上的做法是接力:先找到手、再钉 21 个点、把点压成一个 128 维向量、最后在 8 个手势里选一个。四道工序各管一段,坏哪一段都能单独看见。

难度 ⭐⭐⭐ · 4 学时 4 个模型级联21 点手部骨架 128 维嵌入softmax 8 分类
01 颜色02 轮廓03 二维码04 检测05 分类06 分割07 跟踪08 人脸09 骨骼10 3D11 体感12 手势

01目标:比个耶就报 Victory,张掌就报 Open_Palm

🎯 实验目标 · 学完你将能
  • 说清四道工序各吃什么、吐什么,以及为什么不能用一个模型直接从图里认出 8 种手势
  • 跑通单图与实时两条命令,看到 21 点手部骨架、状态栏 Hands: 1 Fingers: 2 [color] Victory
  • 解释那个看起来很低的分数 Victory 0.20 —— 为什么 0.2 也敢报,报的依据到底是什么
⚠️ 重点与难点 重点:级联的四段与各段的输入尺寸(192 / 224 / 128 维 / 8 类),以及 argmax 与「分数门限」的区别。
难点:直觉是「0.2 分说明没认出来」,实际是8 个类做 softmax,概率加起来必须是 1;只要它是最大的那一个,就照报。想要「不确定就不报」,得自己加一道分数门限 —— 代码里没有。
INPUT · 你给它
images/ye.jpg

一只手(比耶)的照片。也可以换 --ros 的相机画面。

→
OUTPUT · 你拿到
Victory 0.20

右栏 21 点骨架 + 标签与分数;状态栏 Hands: 1 Fingers: 2 [color] Victory。

本实验的目标:一张手的照片进来,一个手势名出去,顺带告诉你伸了几根手指、模型有多确定。

02问题:手这么小、姿势这么多,怎么认

想用「比耶」开灯、「握拳」关灯。听上去就是把图和标签对上,可把手举远一点,整只手在画面里可能只有 40×40 像素 —— 比一个图标还小。

如果拿整张图直接做 8 分类,模型要在几十万个像素里找到那只小手,还要分辨手指怎么摆。一步到位太难,所以分成四步,每一步只解决一个窄问题:手在哪 → 手指在哪 → 这只手长什么样 → 它属于哪一类。

一步到位:整图 → 8 类 手只占 40x40 背景干扰大、手太小,分类器抓不住 四道工序:每步只解决一个窄问题 找手 192 21 点 224 压成向量 128-d 8 选 1 softmax 每一段都可以单独停下来看:框对不对、点对不对、向量像不像 代价:四个模型串行,延迟叠加;好处:每段都能调试 这就是工程里「级联(cascade)」的典型形态
图 2-1|两种思路的差别。级联不是为了「更准」而堆模型,而是为了每一段都能被单独观察和替换。
对比你看到的(黑盒)机器必须做的(白盒)
比耶被认出手一伸,屏幕就显示 Victory? —— 得先把手指的关节位置找到,才谈得上「几根手指伸着」
为什么分四步以为是一个模型干的? —— 手太小、姿势太多,一步到位认不准
0.20 是什么意思以为「20% 准」就是没认出来? —— 8 类概率加起来是 1,0.2 可能是里面最高的

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

① 为什么不直接整图分类,非要串四个模型?

② 21 个点在手势判定里到底怎么用?

③ 分数只有 0.20,为什么还敢输出 Victory?

级联的本质是把一个大问题切成四个窄问题:每段都能单独看、单独换 —— 代价是延迟叠加。

03现象:左框右骨架,状态栏 Victory 0.20

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

你会看到:双栏图。左栏手 + 绿框 + FPS: 0;右栏黑底,21 个红点用绿线连成手部骨架,旁边蓝字标 Victory 0.20;底部状态栏 FPS: 0 Hands: 1 Fingers: 2 [color] Victory。

比耶手势的识别结果:左栏手部绿框,右栏 21 点骨架与 Victory 0.20 标签,状态栏 Hands: 1 Fingers: 2 Victory
① 比耶 → Victory 0.20,Fingers: 2
张掌手势的识别结果:左栏手部绿框,右栏骨架,标签为 Open_Palm
② 张掌 → Open_Palm

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

cd ~/ros2_ws/src/xflz_eai_car1/vision/12_hand_control
export PYTHONPATH=$PWD/car1_hand_control
python -m hand_control_core.demo_image --config config/hand_control_config.yaml --save out.jpg
[hand_control] gesture: Open_Palm ← 换成 images/ye.jpg 再跑一次:gesture: Victory ← 图上的分数会显示出模型有多确定

🔍 看什么:三处,都是后面要用到的证据。

  • 21 个点不是均匀铺开 —— 5 根手指各 4 个点(根/中/末)+ 1 个手腕。点密的地方就是要算角度的地方。
  • 状态栏有四个字段 —— Hands(检出几只手)、Fingers(伸了几根手指)、[color](标记来源)、最后才是手势名。
  • 分数 0.20 也在报 —— 这就是第 02 节第三问。它不表示「只有 20% 把握所以没认出来」,而是「8 个类里它最大,占 20%」。

这就是本实验最终要呈现的现象。第 05 节会让你换图、接相机,再亲手验证「分数低也照报」这条规则。

04原理:四道工序:找手、21 点、128 维、8 分类

CORE IDEA · 一句话原理

四个小模型接力:先找到手(192 见方),再钉 21 个点(224 见方),把 21 个点压成 128 个数,最后在 8 个手势里取概率最大的那个。

打个比方:像海关查证件 —— 先确认「这是本护照」(找手),再核对上面的照片与指纹(21 点),把信息录成一段编码(128 维),最后在签证类型里打一个勾(8 选 1)。 揭个盖:打勾用的是「哪一栏概率最高」,不是「够不够格」。所以哪怕最高那栏只有 20%,印章照样盖下去 —— 想要「不确定就不盖章」,得自己加一道门限。

四道工序回答第 02 节的三问:① 为什么要串四个 → 步骤 1、2;② 21 点怎么用 → 步骤 2、3;③ 0.20 为什么照报 → 步骤 4。

① 手掌在哪 hand_detector 192 给一个手掌框 不分类,只管「手在这」 ② 21 个关节 landmarks 224 手掌框放大后钉 21 点 5 指 x 4 + 腕 1 ③ 压成 128 个数 21 x 3 坐标 → 128 维 embedder:把「形状」变成一串数 手的大小、位置都被抹平 只剩「姿势本身」 ④ 8 选 1 None Closed_Fist Open_Palm Pointing_Up Victory ← 最大 Thumb_Down / Up ILoveYou softmax 后取最大 不给门限:0.2 也照报 四段各自独立:任何一段坏了,症状都不一样(框错 / 点错 / 向量错 / 标签错) 另一条路:不训练分类器,直接按 21 点算「哪几根手指伸直」(fingers.py 就是干这个的) 两条路并存:分类器给「是什么手势」,掰指头给「伸了几根」—— 状态栏两个都显示
图 4-1|四道工序。值得多看一眼第 ③ 段:它把「手在画面哪个位置、多大」这些信息丢掉,只保留姿势本身 —— 所以远近、左右都不影响分类。

1为什么第一步只找手、不认手势?

吃什么:整帧图,缩到 192×192。做什么:手掌检测模型找一个手掌框。吐什么:框的坐标 + 分数。

因为「手在哪」和「手在做什么」是两个难度完全不同的任务。检测手掌只需要认「一团有个大拇指的肉色块」,用一个小模型就能做;分类手势则要看清每根手指的姿势,必须在放大后的手上做。

动它会怎样:检测分数低(手太小、逆光)→ 后面三段全部没有输入。这一段的失败会静默传播成「什么都没认出来」,所以排查永远从这一段的框开始。

221 个点是怎么钉上去的?

和 EXP-08 的人脸关键点是同一套思路:把手掌框裁出来、放大到 224 见方,再让关键点模型在这张裁片里钉 21 个点。

  • 点怎么分布:手腕 1 个 + 5 根手指各 4 个(根部、中间、末端、指尖),共 21 个。
  • 点为什么够用:手势的差别几乎全在「手指弯没弯」—— 关节角度就够了,不需要手的纹理或颜色。
  • 为什么手要放大:同 EXP-08 —— 精度来自「手在输入里占多大」,不是模型多大。

动它会怎样:裁片太松 → 手只占一半,点位抖;太紧 → 指尖被切掉,第 3 段拿到的形状就缺一块。

3128 个数是怎么来的?为什么不直接算角度?

这是第 02 节第二问的答案,而且这里有两条并行的路,理解它们的差别比记住模型名更重要:

路子怎么得到结论优点 / 代价
学出来的(embedder + classifier)21 点 → 128 维向量 → 8 类 softmax能认「比耶 / 竖拇指 / 我爱你」这类语义手势;代价是要模型、要数据,概率还可能很低
算出来的(fingers.py)三点夹角:指尖-中间关节-根部 > 150° 就算这根手指伸直;拇指还要两段都直透明、可解释、不算概率;代价是只能数「几根手指」,认不出语义手势

状态栏里 Fingers: 2 来自第二条路(比耶确实伸了 2 根),Victory 来自第一条路。两条路互为交叉验证,这也是工程上常见的做法。

另外 fingers.py 还算一个「拇指-食指夹角」,把 0~70° 映射成 0~255 的连续值 —— 用来做滑杆式的连续控制(捏合程度)。

动它会怎样:STRAIGHT_ANGLE(150°)调低 → 略微弯曲的手指也算伸直,Fingers 会虚高;调高 → 弯一点点就不算。

4分数只有 0.20,为什么还敢报 Victory?

这是全页最重要的一点,也是第 02 节第三问的答案。

分类器输出 8 个数(logits),过 softmax 变成 8 个概率,它们加起来必须是 1。然后代码只做一件事:取最大的那个(argmax)。

  • 所以 0.20 的意思是:在 8 个候选里它最大,独占 20%,剩下 80% 被其他 7 类分掉了。
  • 代码里没有分数门限 —— 只要有 8 个数进来,就一定有一个标签输出。
  • 想要「不确定就别报」,必须在外面自己加一道门限(例如 if score < 0.5: 视为 None)。

为什么分数会这么低?因为 softmax 是被「相对差距」驱动的:当 8 个 logit 都很接近时,概率就会平摊。真实场景里手部姿态介于两类之间(比如比耶但中指半弯),就会出现这种「谁都不像」的分布。

动它会怎样:加门限 → 输出更保守,会出现「认得出来但界面没反应」;不加门限 → 永远有反馈,但可能报错。

回看第 03 节那张图:Victory 0.20 不是「只有两成把握」,而是「8 个类里它最大、占两成」 —— 报,是因为规则只问排名,不问分数。

先认识 5 个词

级联 cascade

多个模型串行,前一个的输出是后一个的输入。什么时候失效:第一段框错,后面全错,且不会报错。

21 点手部骨架

手腕 + 5 指 × 4 关节,描述手的姿势。什么时候失效:手指交叠、手套、逆光时点位会乱。

嵌入 embedding

把形状压成一串固定长度的数(这里 128 维)。什么时候失效:手型超出训练分布(如戴手套)时向量会漂到没学过的区域。

softmax / argmax

softmax 把 8 个分数变成和为 1 的概率;argmax 取最大的下标。什么时候失效:只做 argmax 不设门限,就永远「有答案」,哪怕全是噪声。

三点夹角

用余弦定理算指尖-中间-根部形成的角度,判断手指直不直。什么时候失效:侧对镜头时手指前后重叠,角度会算歪。

什么时候会翻车:五个真实边界选读 1 分钟
  • 手太小 / 太远 —— 第一段就检测不到,后面全空。表现是「Hands: 0」。
  • 手指交叠 —— 21 点会互相穿模,掰指头的角度全乱,Fingers 数错。
  • 戴手套 / 深色手 —— 手掌检测对肤色有一定依赖,深色手套上分数明显偏低。
  • 镜头侧对 —— 手指前后重叠,夹角退化成接近 180°,明明弯着也算伸直。
  • 连续挥动 —— 本页只做单帧判定;要识别「挥手」这类时序手势,得再加一层时间窗口。
步数据变成什么为什么非它不可 / 少了会怎样谁在干动参数会怎样
1整图 → 手掌框(192 输入)没有框,后面三段没有输入hand_detector检测分数低 → 静默失败
2手掌框 → 21 点(224 输入)不钉点,就只剩「一团肉色」,谈不上姿势hand_landmarks裁片松紧影响点位稳定
321 点 → 128 维把「位置/大小」抹掉,只留姿势gesture_embedder无参数
4128 维 → 8 类概率 → 一个标签只要 argmax 不要门限,就永远有答案canned_classifier加门限则更保守

只看第 2、5 两列:第 2 列是数据形态变化,第 5 列是能拧的旋钮。第 4 行那句是本页的核心结论。

05实操:单图 / 换图 / 实时三条命令

命令与原理的对应关系。

环节函数(文件)吃哪个参数对应原理第几步
手掌检测engine 第一段(hand_detector.rknn)min_tracking_confidence步骤 1
21 点hand.py / 关键点模型(无)步骤 2
嵌入 + 分类gesture.prepare_embedder_inputs / decode_gesture(gesture.py:28,45)(无)步骤 3、4
掰指头fingers.fingers_up / count_fingers(fingers.py:34,44)STRAIGHT_ANGLE步骤 3 的另一条路
状态栏view.put_status(无)步骤 4 的可视化
推流MjpegServerserver.port(8084)链外
  1. 单图跑通,对照两个手势约 3 分钟

    命令见第 03 节。判据:终端打印 gesture: Open_Palm,图上有 21 点骨架与标签。

    [hand_control] gesture: Open_Palm 状态栏:FPS: 0 Hands: 1 Fingers: 5 [color] Open_Palm

    换一张图再跑:把配置里的 rgb_path 改成 images/ye.jpg,终端会变成 gesture: Victory,状态栏 Fingers: 2。

  2. 故意造两个「认错」约 6 分钟

    第一,把手机拍的比耶照片斜过来 45° 再跑 —— 手指前后重叠,21 点开始穿模,Fingers 会数错,手势标签也可能变。

    第二,手离得远一点,让手在画面里只占很小一块 —— 会看到 Hands: 0,也就是第一段就失败了。这两次失败定义了这套方案的边界。

  3. 实时相机:看四段一起跑约 8 分钟

    实时走 MJPEG,端口 8084(hand.mjpg)。先起驱动再跑程序,两个终端 ROS_DOMAIN_ID 一致(N 取 0~101,各设备不同)。

    终端一 · 起相机驱动

    source /opt/ros/humble/setup.bash && source ~/ros2_ws/install/setup.bash
    export ROS_DOMAIN_ID=N
    ros2 launch ascamera hp60c.launch.py
    日志持续滚动,无 ERROR

    终端二 · 跑手势识别

    source /opt/ros/humble/setup.bash && source ~/ros2_ws/install/setup.bash
    export ROS_DOMAIN_ID=N
    cd ~/ros2_ws/src/xflz_eai_car1/vision/12_hand_control
    export PYTHONPATH=$PWD/car1_hand_control
    python -m hand_control_core.camera_demo --config config/hand_control_config.yaml --ros --port 8084
    [camera_demo] MJPEG on http://<板IP>:8084/hand.mjpg ← 浏览器打开,依次比:握拳 / 张掌 / 比耶 / 竖拇指

    四个手势挨个比一遍,记下每个的分数。你会发现分数普遍不高(0.2~0.6) —— 这就是 softmax 摊薄的结果,不是模型坏了。

🔁 现象呼应这三步跑完看到的,就是第 03 节那两张图:21 点骨架 + Victory 0.20 / Open_Palm + 状态栏四个字段。而且你现在能解释那个 0.20。两处对上了,四道工序才算真的走通。

你该看到的画面

正常
骨架贴手

21 点落在关节上,标签与手势一致,Fingers 数与实际伸出数相同。

换手势
标签跟着变

握拳 → Closed_Fist;竖拇指 → Thumb_Up;Fingers 同步变。

手指交叠
点数错乱

21 点穿模,Fingers 数错,标签可能跳到不相干的类。

异常 · Hands: 0
第一段就失败

手太远 / 太暗 / 被挡。后面三段完全没有输入。

踩坑速查

现象原因怎么办
报 ModuleNotFoundError: hand_control_core没加 PYTHONPATHexport PYTHONPATH=$PWD/car1_hand_control,用 -m 跑
Hands: 0,什么都没识别手太小 / 太暗 / 第一段失败靠近镜头、给足光;先确认左栏有绿框
骨架贴错位置21 点在裁片里算的,裁片没对上检查第一段框是否准确;手别超出画面边缘
分数总是很低(0.2 左右)softmax 在 8 类间摊薄,不是故障看 argmax 而不是看绝对值;需要保守就自己加门限
Fingers 数错手指前后重叠,夹角算歪掌心朝向镜头,别侧对;必要时调 STRAIGHT_ANGLE
换手势后标签不变画面里仍是上一只手(缓存/残影)或握手势太快把手移出画面再进来;实时模式下慢一点切换
--ros 报 cannot open ros topic驱动没起 / ROS_DOMAIN_ID 不一致先起 ascamera;两终端 export 同一个 N
进阶 1 · 自己加一道分数门限约 20 分钟

在 decode_gesture 之后加三行:低于阈值就把标签改成 None。你会立刻看到「想比耶但手指没摆好 → 显示 None」,而不是硬报一个错的。

阈值取多少要看场景:演示用 0.3,做控制用 0.6。没有通用值,只有取舍。

进阶 2 · 用拇指-食指夹角做连续控制约 25 分钟

fingers.thumb_index_value 已经把夹角 0~70° 映射成 0~255。把它接到一条滑杆上(比如车的速度档位),就得到「捏得越紧、开得越慢」的连续输入。

这比 8 个离散手势细腻得多 —— 也是「分类」和「回归」两种思路的分界:要选项就用分类,要程度就用回归。

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

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

提示词 1 · 模块职责分析

请阅读这个手势识别项目的模块:hand_control_core/engine.py、hand.py、gesture.py、fingers.py、view.py、demo_image.py。
它们共同完成「找手 → 21 点 → 分类 → 显示」。
请输出一张表,每行一个模块,四列:模块名 / 一句话职责 / 被谁调用 / 从哪个配置项取值。
并把四个 .rknn 模型分别标在对应的模块上。

提示词 2 · 控制流流程图

请为 gesture.decode_gesture 与 fingers.fingers_up 两条支路各画一张流程图,然后说明它们的结果如何汇合到状态栏。
必须包含:8 类 logits 如何过 softmax、argmax 如何取标签、伸指判定里三点夹角如何计算、拇指为什么需要两段都直。

提示词 3 · 时序图

请为实时模式画一张时序图,参与者:ros2_camera 节点、Ros2Camera 回调、手掌检测、关键点模型、embedder、classifier、MjpegServer。
标出四次推理的先后(严格串行)与各自的输入尺寸,并估算哪一段最耗时。

自查三问:四个模型各在哪个文件被调用?softmax 与 argmax 是否分别指出?能说出为什么四次推理必须串行(后一个输入依赖前一个输出)?

三处必然的差异:

差异点AI 版常见写法工程版(本仓)为什么工程这么写
分类输出直接 argmax,不解释分数,或把分数当「准确率」softmax 后取最大并同时保留 8 类分数(gesture.py:45-52)8 类概率和为 1,0.2 也可能是最大;保留全部分数便于后续加门限或调试
手指计数用关键点做简单的「y 坐标比较」三点夹角 > 150°,拇指还要两段都直(fingers.py:13-44)手会旋转,比 y 大小换个角度就失效;夹角与朝向无关
模型串联一个模型直接输出手势四个模型级联,每段输入尺寸固定(192/224/128/8)手太小、姿态太多;分段后每段都能单独验证与替换

改一处、先预测再验证:把 STRAIGHT_ANGLE 从 150 改成 120,先写下 Fingers 会变大还是变小,再跑一次对答案。

06互动:拖两个 logit,看 softmax 谁赢

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

Victory 0.20 这个数最容易误读。这里把两个类的分数交给你:看它怎么被摊薄、又怎么永远有一个赢家。

⚡ 参数实验台 · 8 类 softmax 的胜负
1.4
1.0
Victory 1.4、Open_Palm 1.0、其余 6 类各 0.2 → Victory 概率最高,输出 Victory,分数约 0.29。
互动 6-1|8 个类各有一个 logit,过 softmax 后概率之和恒为 1。输出永远是「最大的那一个」,没有及格线 —— 这就是 0.20 也照报的原因。其余 6 类的 logit 固定为 0.2(示意「都不太像」)。

怎么读出规律:先把两条都拉到最小 —— 8 类几乎平分,胜者只有 0.13 左右,但它照样输出;再把 Victory 拉满、另一条压低 —— 概率迅速接近 1。你会看到:分数是相对的,标签是绝对的。

两条都低
平分,仍然有答案

8 类各约 0.125,最高那个照样被输出 —— 这正是「噪声也有标签」。

Victory 1.4
≈ 0.29

比其它类高出一截,但绝对值不高。第 03 节的 0.20 就是这种形态。

Victory 拉满
接近 1

差距足够大时 softmax 会把概率集中到一类上。

两条一样高
并列

概率相同,argmax 只能选一个(实现上取第一个)—— 这就是「模棱两可仍要下结论」。

softmax 只保证「和为 1」,不保证「够不够格」。要不要加一道分数门限,是你的设计决定,不是模型的能力。

07验证:三道小测

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

回页首对照「实验目标」:① 四道工序能否按顺序说出来 → 试 Q1;② 单图与实时跑通、状态栏四个字段能否解释 → 回第 05 节;③ 0.20 能否解释清楚 → 试 Q3,并回第 06 节拖一遍 softmax。三条全 ✓,这门课的 12 个实验就都走完了。

Q1为什么不用一个模型直接从整图输出 8 种手势之一?
因为 RKNN 不支持 8 分类
因为手在画面里往往很小、姿态又多;先把手找到并放大,再在裁片上取点和分类,每一段都更容易做对、也更容易单独调试
因为四个模型加起来比一个大模型更准
对。级联不是为了堆精度,而是把大问题拆成四个窄问题:找手、定点、压向量、分类。代价是四次串行推理、延迟叠加;好处是每一段的失败都能单独看见(Hands: 0 就说明第一段挂了)。
Q2状态栏里的 Fingers: 2 是怎么算出来的?
分类器的 8 个概率里有 2 个超过 0.5
用 21 点算三点夹角:指尖-中间关节-根部超过 150° 就算这根手指伸直,拇指还要两段都直
数画面里有几个手掌框
对。这是不训练模型的另一条路:纯几何计算,透明可解释。用夹角而不是比 y 坐标,是因为手会旋转 —— 比 y 大小换个角度就失效。它和分类器互为交叉验证:Fingers: 2 与 Victory 应该一致。
Q3标签显示 Victory 0.20,正确理解是?
8 类 softmax 概率里 Victory 最大、占 20%;代码只做 argmax 不设门限,所以照常输出
模型只有 20% 的可能认对,应该丢弃这个结果
这是「第 20 号手势」的编号
对。softmax 的概率之和恒为 1,所以「都不太像」时每类都会很低,但总有一个最大。想要「不确定就不报」,得自己在外面加门限;模型本身不会拒绝回答。这也是所有分类任务通用的一个坑。

08章节总结:四道工序把「手」一路窄化

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

概念它解决了什么和别的关系
手掌检测(192)在整帧里找到手,给一个框级联的第一段;它失败,后面全空
21 点关键点(224)把手势变成关节坐标上游吃手掌框,下游喂给嵌入与掰指头两条路
128 维嵌入抹掉位置与大小,只留姿势让分类不受远近左右影响;是「形状」的数值化
8 类 softmax + argmax在固定词汇表里给出唯一标签只比排名不比分数;0.2 也照报
三点夹角掰指头不训练模型也能数出伸了几根与分类器互为交叉验证;与朝向无关
拇指-食指夹角 → 0~255把捏合程度变成连续量分类给选项,回归给程度
状态栏四字段一眼看到「几只/几指/来源/结论」Hands/Fingers 出错能立刻定位是哪一段的问题
⚠ 三个反直觉的地方 ① 直觉是0.20 表示「没认出来」,实际是 8 类概率里它最大就照报,模型没有「弃权」这个选项;
② 直觉是模型越多越准,实际是级联的目的是可分解、可调试,代价是四次串行推理的延迟;
③ 直觉是数手指就比 y 坐标,实际是用手会旋转,必须用三点夹角 —— 换个角度比 y 就全错。
💡 四条要注意 ① 四个输入尺寸别记混:检测 192、关键点 224、嵌入 128 维、分类 8 类;
② STRAIGHT_ANGLE = 150° 判的是三点夹角,不是「手指的绝对角度」;拇指还要两段都大于 150°,因为手腕处多一个关节;
③ 状态栏的 Fingers 与手势标签来自两条不同的路,不一致时先怀疑 21 点(手指交叠最容易出问题);
④ 端口是 8084(hand.mjpg),与前三个实验的 8080/8081/8082/8083 全错开。

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

类型具体项缺了会怎样 / 怎么补
环境板上 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缺 rknnlite 四个模型都加载不了;缺 mjpeg_streamer 实时启动即退出
模型 / 资源四个 .rknn:hand_detector(2.9MB)、hand_landmarks_detector(5.9MB)、gesture_embedder(448KB)、canned_gesture_classifier(92KB);示例图 hand_demo.jpg / ye.jpg缺任一模型,对应那一段直接失败;换图要保证手够大、掌心朝镜头
配置config/hand_control_config.yaml:min_tracking_confidence: 0.5、source.mode: image、rgb_path、server.port: 8084缺 key 抛 ConfigError;mode: image 没写 rgb_path 直接报错
运行方式项目根 + PYTHONPATH 含 car1_hand_control,用 -m 运行直接跑 .py 会因相对导入失败

这套代码用在哪儿

  • 无接触交互:隔空翻页、比手势开关灯 —— 医疗、洁净车间里不能碰屏幕的场景。
  • 机器人手势指令:握拳停、张掌走、比耶召回,一套 8 个标签足够覆盖常见指令。
  • 连续控制:拇指-食指夹角 → 速度档位,比离散手势细腻,适合调音量、调速这类量。
  • 不该用它的场合:需要毫米级精度时别用手势(用 EXP-10 的深度);复杂指令集(几十种手势)时分类器会迅速饱和,该上自定义训练。
核心记忆点:找手(192)→ 21 点(224)→ 128 维 → 8 选 1。分类只给排名、不给及格线;要「不确定就不报」,得自己加门限。

十二个实验到此走完。回头看这条主线:像素 → 掩膜 → 轮廓 → 检测 → 分类 → 分割 → 跟踪 → 关键点 → 深度 → 动作。每一格都是「把上一格的输出,变成下一格能吃的形态」。这套思路,比任何单个模型都更值得带走。