EP:Engineering and Project

OpenCV + MediaPipe Hands 手掌动作识别完整方案详解

定位:轻量化端侧手部关键点识别方案,适用于第一视角 Egocentric 头戴 / 胸前相机、工业示教、手势交互、石头剪刀布、手部动作捕捉;只专注手部(单 / 双手)21 点 3D 关键点输出,不输出手臂骨骼,手臂需要额外搭配姿态模型(如 MediaPipe Holistic Pose),正好匹配你当前项目架构。

一、方案整体概述

1. 核心分工

  • OpenCV:视频采集、图像预处理、色彩空间转换、画面可视化绘制、截图 / 视频保存、键盘交互。不做 AI 识别,只负责图像管线。
  • MediaPipe Hands:Google 自研端侧轻量化 CNN 推理管线,专门做手部检测 + 21 个手部关键点回归。内置两阶段结构:手部检测子网络 + 关键点回归子网络。

MediaPipe Hands 原生支持:单目 RGB 图像、CPU 实时推理,PC / 工控机 / Jetson 嵌入式均可跑;无需 GPU,常规 CPU 可达 25~30FPS。

2. MediaPipe Hands 输出定义(重点)

每检测到一只手,输出21 个归一化 3D 关键点(坐标范围[0,1],以图像左上角为原点)

表格

索引部位说明
0WRIST 手腕基点手部坐标系原点
1~4拇指:CMC/MCP/IP/TIP拇指根、中节、末节、指尖
5~8食指:MCP/PIP/DIP/TIP掌根、近端、远端、指尖
9~12中指:MCP/PIP/DIP/TIP
13~16无名指:MCP/PIP/DIP/TIP
17~20小指:MCP/PIP/DIP/TIP

坐标字段:

  • x:图像水平归一化坐标(左 = 0,右 = 1)
  • y:图像垂直归一化坐标(上 = 0,下 = 1)
  • z:相对深度,以手腕点为基准;数值越小代表该点离相机更近,不是物理毫米距离

⚠️ 重要区别:MediaPipe Hands 关键点没有 visibility 属性,和 Holistic Pose 不一样;有效性依靠检测置信度multi_handedness判断。
multi_handedness:返回左右手分类 + 置信度,判断当前识别到的是左手还是右手。

3. 可实现能力

  1. 实时定位手掌、全部手指关节、指尖位置
  2. 计算各手指关节夹角、手指伸直 / 弯曲状态
  3. 基于几何规则实现手势分类:石头 / 剪刀 / 布、OK、数字手势、指向等
  4. OpenCV 绘制手掌骨架、分色连线、指尖文字标签
  5. 输出时序关键点,做动作轨迹记录、动作时序分析

4. 识别效果和能力边界

识别效果

在这里插入图片描述

其中双手的十个手指都能够得到很好的识别,但是对于上肢的小臂和大臂的识别需要增加新的模块。

能力边界

需要加强点1: MediaPipe Hands 本身不识别小臂、大臂、肩、肘。它的输出只到手腕(关键点 0);手腕向上的小臂 / 肘关节,可以额外使用人体姿态模型(MediaPipe Holistic Pose / Pose)。
需要加强点2: 厚手套、大面积手部遮挡、暗光、严重运动模糊,识别会失效
需要加强点3: z 是相对深度,不是真实物理距离,不能直接测量手掌真实尺寸

二、MediaPipe Hands 内部推理原理(两阶段架构)

  1. 阶段 1:手部检测器(Hand Detection)
    轻量 SSD 类目标检测网络,在整图上搜索手部,输出手部矩形 ROI(感兴趣区域)。

静态模式(static_image_mode=True):每一帧都跑一次全局手部检测,精度更高、速度慢,适合图片。
视频流模式(static_image_mode=False):首帧检测手部 ROI;后续帧不做全局检测,直接在上一帧手部 ROI 小窗口内追踪关键点,大幅提速。只有追踪丢失时,才重新全局检测。视频流必须用static_image_mode=False。

  1. 阶段 2:手部关键点回归网络(Hand Landmark Regression)
    把 ROI 裁剪、归一化送入 CNN,回归 21 个 3D 关键点。模型训练数据包含大量自遮挡、手握物体场景,对第一视角近距离手掌适配性强。
  2. 后处理:左右手分类 + 坐标反投影
    将 ROI 内归一化关键点映射回原图坐标系,输出整图归一化 x/y/z,同时输出左右手置信。

三、完整技术管线(OpenCV + MediaPipe Hands)

摄像头/视频文件 → OpenCV VideoCapture读取BGR帧
→ OpenCV预处理:BGR转RGB(MediaPipe只接收RGB图像),设置图像不可写锁(提升推理速度)
→ MediaPipe Hands推理:手部检测 + 21关键点回归
→ 结果解析:提取multi_hand_landmarks(21点)、multi_handedness(左右手置信)
→ 业务逻辑层:
    ① 过滤低置信手部结果
    ② 计算手指关节向量、弯曲角度
    ③ 手势分类(石头剪刀布等)
→ OpenCV可视化:绘制骨架连线、关键点、中文指尖标签、HUD状态信息
→ 存储:关键点时序数据写入JSONL/CSV/SQLite
→ 循环读取下一帧

四、核心参数详解(适配第一视角 Egocentric 场景)

mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,          # 视频流模式:开启追踪,加速
    max_num_hands=2,                  # 最多检测2只手(双手场景)
    min_detection_confidence=0.6,     # 全局手部检测置信阈值。低于0.6不触发手部检测
    min_tracking_confidence=0.5       # 帧间追踪置信阈值;追踪结果低于该值,触发重新全局检测
)

参数调优说明(第一视角场景):

  1. min_detection_confidence=0.6:第一视角手掌离镜头近、容易误检出背景,阈值不能太低;如果手部经常漏检,可下调至 0.5;容易误识别则上调 0.7。
  2. min_tracking_confidence=0.5:动态手部移动场景,平衡追踪稳定性和重新检测触发频率;手部快速晃动可上调。
  3. max_num_hands=2:双手操作场景固定为 2;单手场景可以改为 1,减少算力。

五、手势识别实现原理(石头剪刀布举例)

拿到 21 个关键点后,不使用深度学习分类,采用几何向量规则判断

  1. 计算向量:每个手指使用两段骨骼向量(如 PIP→DIP、DIP→TIP)
  2. 计算关节夹角:向量点积求关节弯曲角度
    • 角度大:手指伸直;角度小:手指弯曲收拢
  3. 规则判定:
    • ✊石头:五指全部关节大角度弯曲
    • ✌剪刀:食指、中指伸直;无名指、小指、拇指弯曲
    • ✋布:五指全部伸直张开
  4. 时序防抖:单帧识别容易抖动,增加滑动窗口滤波,连续 3~5 帧判定一致,才输出手势结果。

进阶方案:提取 21 点坐标向量作为特征,训练 KNN / 随机森林轻量分类器,比纯几何规则抗干扰更强。

六、可视化实现(OpenCV 绘制手掌骨架,分色 + 中文指尖标签)

visualize.py模块实现逻辑:

  1. 遍历每只手 21 个关键点,将归一化 x,y 转换为图像像素坐标:
    px = int(lm.x * img_width)
    py = int(lm.y * img_height)
  2. 绘制骨架连线:不同手指设置不同颜色(拇指红色、食指绿色、中指蓝色、无名指黄色、小指紫色,分色骨架)
  3. 在 TIP 指尖像素位置绘制中文文字标签:拇指、食指、中指、无名指、小指
  4. HUD 叠加信息:检测到手数量、手势类别、推理 FPS

七、工程优化方案(适配你的项目)

1. EMA 时序平滑(tracking.py)

手部快速运动时,模型输出关键点会有抖动。对 21 点 x/y/z 使用指数移动平均 EMA 滤波:

(P_{smooth} = \alpha\cdot P_{raw} + (1-\alpha)\cdot P_{prev_smooth})
(\alpha):平滑系数(0~1),α 越小越平滑,但延迟变大;一般取 0.2~0.4。

只平滑关键点坐标,不修改原始 raw 数据,原始数据保留入库,方便溯源。

2. 手部置信过滤

multi_handedness[0].classification[0].score,低于阈值(0.5)直接丢弃该手所有关键点,避免无效点送入可视化与手势识别。

3. 手腕点位融合(重点,对接小臂骨架)

MediaPipe Hands 的手腕点(索引 0)精度 > Holistic Pose 手腕点。
工程策略:

  • 手指骨架:采用独立 Hands 输出手腕点 0
  • 小臂 / 大臂:使用 Holistic Pose 的肘、肩点位
  • 做加权融合,消除两套模型手腕坐标错位,保证手指骨架无缝连接小臂。

八、完整最小可运行示例代码(OpenCV+MediaPipe Hands)

import cv2
import mediapipe as mp
import numpy as np

# 初始化mediapipe hands
mp_hands = mp.solutions.hands
mp_drawing = mp.solutions.drawing_utils

# 初始化手部模型(第一视角推荐参数)
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=2,
    min_detection_confidence=0.6,
    min_tracking_confidence=0.5
)

# 摄像头读取
cap = cv2.VideoCapture(0)

# 手指骨架绘制配色
finger_colors = [
    (255,0,0),    # 拇指 红
    (0,255,0),    # 食指 绿
    (0,0,255),    # 中指 蓝
    (255,255,0),  # 无名指 青
    (255,0,255)   # 小指 品红
]
# 指尖中文标签
tip_names = ["拇指","食指","中指","无名指","小指"]

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    h,w = frame.shape[:2]
    # OpenCV BGR -> RGB
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame_rgb.flags.writeable = False
    # 推理
    result = hands.process(frame_rgb)
    frame_rgb.flags.writeable = True
    frame = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR)

    # 解析双手关键点
    if result.multi_hand_landmarks:
        for hand_idx, hand_lms in enumerate(result.multi_hand_landmarks):
            # 绘制手部骨架
            mp_drawing.draw_landmarks(
                frame, hand_lms, mp_hands.HAND_CONNECTIONS,
                mp_drawing.DrawingSpec(color=(100,100,100), thickness=2, circle_radius=3),
                mp_drawing.DrawingSpec(color=(200,200,200), thickness=2)
            )
            # 绘制指尖中文标签
            tip_ids = [4,8,12,16,20] # 5个指尖索引
            for i, tip_id in enumerate(tip_ids):
                lm = hand_lms.landmark[tip_id]
                px, py = int(lm.x*w), int(lm.y*h)
                cv2.putText(frame, tip_names[i], (px+5,py), cv2.FONT_HERSHEY_SIMPLEX, 0.4, finger_colors[i],1)

    cv2.imshow("OpenCV + MediaPipe Hands 手掌识别", frame)
    if cv2.waitKey(5) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
hands.close()

九、方案优缺点总结

1、 优点

  1. 开箱即用,CPU 实时,跨平台(Windows/Linux/ 工控机 / Jetson)
  2. 21 点完整手指关节,对第一视角近距离手掌、手抓物体场景鲁棒性强
  3. 可直接计算手指夹角,实现石头剪刀布、手势交互,无需额外训练深度学习模型
  4. 轻量,依赖少,和你现有代码架构完全兼容
  5. 支持时序追踪,配合 EMA 平滑,抑制关键点抖动

2、 缺点

  1. 仅输出手部,无小臂 / 大臂 / 肩肘,必须搭配姿态模型
  2. z 为相对深度,不能直接得到真实物理尺寸
  3. 没有 visibility 字段,遮挡后只能依靠检测置信过滤
  4. 完全遮挡、厚手套、严重暗光场景识别失效
  5. 快速大幅度运动时,关键点会产生小幅抖动(需要 EMA 平滑)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐