欢迎光临
我们一直在努力

【计算机视觉入门】基于 MediaPipe 与传统机器学习的手势识别实战

文章目录

  • 一、项目整体流程
  • 二、环境准备
  • 三、原理:MediaPipe Hands 与 21 个手部关键点
    • 3.1 什么是 MediaPipe Hands
    • 3.2 21 个关键点的坐标含义
    • 3.3 为什么用"关键点坐标"而不是原始图像
  • 四、第一步:手势数据采集
    • 4.1 完整代码(`1_hand_gesture_collector.py`)
    • 4.2 使用方法
    • 4.3 采集建议
  • 五、第二步:模型训练与评估
    • 5.1 完整代码(`2_gesture_recognizer.py`)
    • 5.2 特征构造:63 维向量
    • 5.3 训练集/测试集划分
    • 5.4 StandardScaler 标准化
    • 5.5 四个传统机器学习模型对比
    • 5.6 模型评估指标
  • 六、第三步:实时手势识别
    • 6.1 运行方式
    • 6.2 实时识别流程
  • 七、总结

一、项目整体流程

整个项目分为三个阶段,对应两个脚本:

摄像头画面


[脚本1] MediaPipe 检测手部 21 个关键点
│ 按数字键 0~4 采集

JSON 文件(gesture_data/<类别>/xxx.json)


[脚本2] 读取 JSON → 展平为 63 维特征向量
│ 划分训练/测试集 → StandardScaler 标准化

训练 KNN / SVM / 决策树 / 随机森林
│ 选准确率最高的模型

best_gesture_model.pkl + scaler.pkl + confusion_matrix.png


[脚本2 –recognize] 摄像头实时识别并显示手势与置信度

共定义 5 个手势类别:

编号英文标签含义
0 fist 拳头
1 open_hand 张开的手
2 point 食指指向
3 peace 剪刀手(V 字)
4 ok OK 手势

二、环境准备

主要依赖如下:

库名用途安装命令
opencv-python 摄像头读取、图像显示、绘制文字 pip install opencv-python
mediapipe 手部 21 关键点检测 pip install mediapipe
numpy 数值计算、时间戳 pip install numpy
scikit-learn 数据集划分、标准化、四种分类模型、评估指标 pip install scikit-learn
joblib 模型与标准化器的保存/加载 随 scikit-learn 自动安装
matplotlib 绘制混淆矩阵 pip install matplotlib
seaborn 混淆矩阵热力图美化 pip install seaborn

三、原理:MediaPipe Hands 与 21 个手部关键点

3.1 什么是 MediaPipe Hands

MediaPipe 是 Google 开源的跨平台多媒体机器学习 pipeline 框架。其中的 Hands 模块可以实时检测并追踪单只或双手的 21 个三维关键点,速度快,对普通摄像头也能流畅运行。

它内部其实由两个模型协同工作:

  • 手掌检测模型(Palm Detection):在整幅图像上先找到手的大致位置;
  • 关键点追踪模型(Hand Landmark):在检测到的手掌区域内回归出 21 个关键点的坐标,并在后续帧直接追踪,避免重复检测。
  • 代码中这样初始化:

    with mp_hands.Hands(
    static_image_mode=False, # 视频流模式,开启追踪
    max_num_hands=1, # 最多检测 1 只手
    min_detection_confidence=0.7, # 检测置信度阈值
    min_tracking_confidence=0.7) as hands:

    • static_image_mode=False:表示输入是视频流,会启用追踪,更快;如果处理静态图片应设为 True。
    • max_num_hands=1:本项目只识别单手手势。
    • 两个 min_*_confidence=0.7:置信度低于 0.7 就认为没有检测到。

    3.2 21 个关键点的坐标含义

    MediaPipe Hands 返回的 21 个关键点按固定编号排列:

    在这里插入图片描述

    • 0:手腕(Wrist)
    • 1~4:拇指(从指根到指尖:CMC、MCP、IP、TIP)
    • 5~8:食指(MCP、PIP、DIP、TIP)
    • 9~12:中指
    • 13~16:无名指
    • 17~20:小指

    每个关键点包含三个坐标:x、y、z。需要特别注意:

    • x、y 是归一化坐标,除以图像 宽度/高度 后取值在 [0, 1] 之间,因此与图像分辨率无关;
    • z 表示该点相对于手腕的深度,数值越小代表离摄像头越近,它没有固定的归一化范围。

    正因为 x, y 已经是归一化坐标,同一手势在不同距离、不同位置下手部关键点的相对形状基本不变,所以把 21 个点的 (x, y, z) 直接展平就能得到一个 63 维的特征向量,用来表示手势。

    3.3 为什么用"关键点坐标"而不是原始图像

    相比直接把整帧图像喂给 CNN,关键点坐标有几个明显优势:

  • 维度低:63 维 vs 原图几万维,传统机器学习模型就能处理;
  • 与光照、背景、肤色无关:只关心手部骨架形状;
  • 与位置、距离基本无关:因为 x, y 已归一化;
  • 可解释性强:能清楚看到是哪些手指弯曲导致的分类结果。
  • 便可以做到"不训练神经网络、只用传统机器学习"也能达到不错效果。


    四、第一步:手势数据采集

    4.1 完整代码(1_hand_gesture_collector.py)

    import cv2
    import mediapipe as mp
    import numpy as np
    import os
    import json

    # 初始化 MediaPipe 手部检测
    mp_hands = mp.solutions.hands
    mp_drawing = mp.solutions.drawing_utils

    # 手势类别和保存路径
    GESTURE_CLASSES = {
    0: "fist", # 拳头
    1: "open_hand", # 张开的手
    2: "point", # 指向
    3: "peace", # 剪刀手
    4: "ok" # OK 手势
    }

    DATA_DIR = "gesture_data"

    # 创建数据目录
    for cls in GESTURE_CLASSES.values():
    os.makedirs(os.path.join(DATA_DIR, cls), exist_ok=True)

    def collect_gesture_data():
    """采集手势数据,提取 21 个关键点的三维坐标"""
    cap = cv2.VideoCapture(0) # 使用默认摄像头

    # 显示手势类别说明
    print("手势类别:")
    for key, value in GESTURE_CLASSES.items():
    print(f"{key}: {value}")
    print("按对应的数字键收集数据,按 q 退出")

    # 初始化计数器,记录每个类别的样本数量
    counters = {cls: 0 for cls in GESTURE_CLASSES.values()}

    with mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.7,
    min_tracking_confidence=0.7) as hands:

    while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
    print("无法获取摄像头画面")
    break

    # 转换为 RGB 并处理(MediaPipe 要求 RGB 输入)
    image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = hands.process(image)

    # 转换回 BGR 用于显示
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

    # 检测到手部
    if results.multi_hand_landmarks:
    for hand_landmarks in results.multi_hand_landmarks:
    # 绘制手部关键点与连线
    mp_drawing.draw_landmarks(
    image, hand_landmarks, mp_hands.HAND_CONNECTIONS)

    # 提取 21 个关键点的三维坐标
    landmarks = []
    for lm in hand_landmarks.landmark:
    landmarks.append([lm.x, lm.y, lm.z])

    # 显示当前各类别样本数量
    info_text = " | ".join(
    [f"{cls}: {count}" for cls, count in counters.items()])
    cv2.putText(image, info_text, (10, 30),
    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

    # 显示画面
    cv2.imshow('Gesture Collection (Press number key to save, q to exit)', image)

    # 键盘事件处理
    key = cv2.waitKey(5) & 0xFF
    if key == ord('q'):
    break
    # 按数字键保存对应类别的数据(chr: 将整数转为对应字符)
    elif chr(key) in [str(k) for k in GESTURE_CLASSES.keys()]:
    cls_idx = int(chr(key))
    cls_name = GESTURE_CLASSES[cls_idx]

    if results.multi_hand_landmarks and len(results.multi_hand_landmarks) > 0:
    # 保存关键点数据
    counters[cls_name] += 1
    data = {
    "class": cls_name,
    "class_index": cls_idx,
    "landmarks": landmarks,
    "timestamp": str(np.datetime64('now'))
    }

    # 保存为 JSON 文件
    filename = f"{cls_name}_{counters[cls_name]}.json"
    filepath = os.path.join(DATA_DIR, cls_name, filename)
    with open(filepath, 'w') as f:
    json.dump(data, f, indent=2)

    print(f"已保存 {cls_name} 样本 #{counters[cls_name]}")
    else:
    print("未检测到手部,请将手放在摄像头前")

    cap.release()
    cv2.destroyAllWindows()

    # 打印最终收集的样本数量
    print("\\n数据收集完成!")
    print("样本数量统计:")
    for cls, count in counters.items():
    print(f"{cls}: {count}个样本")

    if __name__ == "__main__":
    collect_gesture_data()

    4.2 使用方法

  • 运行脚本:python 1_hand_gesture_collector.py;
  • 在摄像头前比出对应手势,按住手不动;
  • 按数字键 0 ~ 4 即可把当前帧的 21 个关键点保存到 gesture_data/<类别名>/ 目录下;
  • 按 q 退出。
  • 每个 JSON 文件结构大致如下:

    {
    "class": "fist",
    "class_index": 0,
    "landmarks": [[0.51, 0.32, 0.01], ],
    "timestamp": "2026-09-21T17:30:12.345678"
    }

    其中 landmarks 是一个长度为 21 的列表,每个元素是 [x, y, z]。

    4.3 采集建议

    数据质量直接决定模型上限,采集时建议:

    • 每类至少50个样本,样本太少模型容易过拟合;
    • 多角度、多距离、多光照下采集,不要只在一个姿势下拍;
    • 手在画面中左、中、右、上、下不同位置都要有样本;
    • 保持五类样本数量大致均衡,避免某类特别多导致模型偏向多数类;
    • 采集过程中如果关键点连线飘了,不要按数字键,等稳定再按。

    五、第二步:模型训练与评估

    5.1 完整代码(2_gesture_recognizer.py)

    import os
    import json
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.svm import SVC
    from sklearn.tree import DecisionTreeClassifier
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
    import joblib
    import seaborn as sns

    # 数据目录和手势类别(需与采集程序一致)
    DATA_DIR = "gesture_data"
    GESTURE_CLASSES = {
    0: "fist",
    1: "open_hand",
    2: "point",
    3: "peace",
    4: "ok"
    }

    def load_gesture_data():
    """加载手势数据并转换为特征向量"""
    X = [] # 特征向量
    y = [] # 标签

    # 遍历每个手势类别目录
    for cls_idx, cls_name in GESTURE_CLASSES.items():
    cls_dir = os.path.join(DATA_DIR, cls_name)
    if not os.path.exists(cls_dir):
    print(f"警告: 类别 {cls_name} 的数据目录不存在")
    continue

    # 加载该类别的所有样本
    for filename in os.listdir(cls_dir):
    if filename.endswith(".json"):
    filepath = os.path.join(cls_dir, filename)
    with open(filepath, 'r') as f:
    data = json.load(f)

    # 将 21 个关键点的 3D 坐标展平为一维向量 (21*3=63 维)
    landmarks = data["landmarks"]
    feature_vector = []
    for lm in landmarks:
    feature_vector.extend(lm) # 添加 x, y, z 坐标

    X.append(feature_vector)
    y.append(cls_idx)

    print(f"加载完成! 共 {len(X)} 个样本,每个样本 {len(X[0]) if X else 0} 维特征")
    return np.array(X), np.array(y)

    def train_and_evaluate_models():
    """训练多种传统机器学习模型并评估性能"""
    # 加载数据
    X, y = load_gesture_data()
    if len(X) == 0:
    print("没有数据可用于训练,请先采集数据")
    return

    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y)

    # 特征标准化
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)

    # 保存标准化器
    joblib.dump(scaler, "scaler.pkl")

    # 定义要训练的模型
    models = {
    "K近邻分类器": KNeighborsClassifier(n_neighbors=5),
    "支持向量机": SVC(kernel='rbf', gamma='scale'),
    "决策树": DecisionTreeClassifier(max_depth=10),
    "随机森林": RandomForestClassifier(n_estimators=100)
    }

    # 训练并评估每个模型
    best_accuracy = 0
    best_model = None
    best_model_name = ""

    print("\\n模型训练和评估结果:")
    for name, model in models.items():
    # 训练模型
    model.fit(X_train_scaled, y_train)

    # 预测
    y_pred = model.predict(X_test_scaled)

    # 评估
    accuracy = accuracy_score(y_test, y_pred)
    print(f"\\n{name} 准确率: {accuracy:.4f}")
    print("分类报告:")
    print(classification_report(
    y_test, y_pred,
    target_names=GESTURE_CLASSES.values()
    ))

    # 保存表现最好的模型
    if accuracy > best_accuracy:
    best_accuracy = accuracy
    best_model = model
    best_model_name = name

    # 保存最佳模型
    joblib.dump(best_model, "best_gesture_model.pkl")
    print(f"\\n最佳模型是: {best_model_name},准确率: {best_accuracy:.4f}")
    print("最佳模型已保存为 best_gesture_model.pkl")
    print("特征标准化器已保存为 scaler.pkl")

    # 绘制最佳模型的混淆矩阵
    y_pred_best = best_model.predict(X_test_scaled)
    cm = confusion_matrix(y_test, y_pred_best)

    plt.figure(figsize=(10, 8))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
    xticklabels=GESTURE_CLASSES.values(),
    yticklabels=GESTURE_CLASSES.values())
    plt.xlabel('预测标签')
    plt.ylabel('真实标签')
    plt.title(f'{best_model_name} 混淆矩阵 (准确率: {best_accuracy:.4f})')
    plt.savefig('confusion_matrix.png')
    print("混淆矩阵已保存为 confusion_matrix.png")
    plt.close()

    def real_time_recognition():
    """实时手势识别"""
    import cv2
    import mediapipe as mp

    # 加载模型和标准化器
    try:
    model = joblib.load("best_gesture_model.pkl")
    scaler = joblib.load("scaler.pkl")
    except FileNotFoundError:
    print("未找到模型文件,请先训练模型")
    return

    # 初始化 MediaPipe 手部检测
    mp_hands = mp.solutions.hands
    mp_drawing = mp.solutions.drawing_utils

    cap = cv2.VideoCapture(0)

    with mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=1,
    min_detection_confidence=0.7,
    min_tracking_confidence=0.7) as hands:

    print("实时手势识别开始,按 q 退出")
    while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
    print("无法获取摄像头画面")
    break

    # 转换为 RGB 并处理
    image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    results = hands.process(image)

    # 转换回 BGR 用于显示
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

    # 检测到手部
    if results.multi_hand_landmarks:
    for hand_landmarks in results.multi_hand_landmarks:
    # 绘制手部关键点
    mp_drawing.draw_landmarks(
    image, hand_landmarks, mp_hands.HAND_CONNECTIONS)

    # 提取 21 个关键点的三维坐标并转换为特征向量
    landmarks = []
    for lm in hand_landmarks.landmark:
    landmarks.extend([lm.x, lm.y, lm.z])

    # 标准化特征(必须使用训练时保存的 scaler)
    landmarks_scaled = scaler.transform([landmarks])

    # 预测手势
    prediction = model.predict(landmarks_scaled)
    predicted_class = GESTURE_CLASSES[prediction[0]]

    # 获取预测概率
    if hasattr(model, 'predict_proba'):
    probabilities = model.predict_proba(landmarks_scaled)[0]
    max_prob = max(probabilities) * 100
    display_text = f"{predicted_class} ({max_prob:.1f}%)"
    else:
    display_text = predicted_class

    # 在画面上显示结果
    cv2.putText(image, display_text, (10, 30),
    cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

    # 显示画面
    cv2.imshow('Real-time Gesture Recognition (q to exit)', image)

    if cv2.waitKey(5) & 0xFF == ord('q'):
    break

    cap.release()
    cv2.destroyAllWindows()
    print("实时识别结束")

    if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description='手势识别程序')
    parser.add_argument('–train', action='store_true', help='训练模型')
    parser.add_argument('–recognize', action='store_true', help='实时识别')

    args = parser.parse_args()

    if args.train:
    train_and_evaluate_models()
    elif args.recognize:
    real_time_recognition()
    else:
    print("请指定操作: –train 训练模型 或 –recognize 实时识别")

    5.2 特征构造:63 维向量

    每个样本的 landmarks 是 21 个 [x, y, z],按顺序拼接:

    [x1, y1, z1, x2, y2, z2, …, x21, y21, z21]

    一共 21 × 3 = 63 维。这就是模型看到的"一张图片"。

    5.3 训练集/测试集划分

    X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y)

    • test_size=0.2:20% 作为测试集,80% 用于训练;
    • random_state=42:固定随机种子,保证每次划分结果一致,便于复现;
    • stratify=y:分层抽样,保证训练集和测试集中每类的比例与原始数据一致,这在多分类、样本不均衡时非常重要。

    5.4 StandardScaler 标准化

    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)

    StandardScaler 会把每一维特征转换为均值 0、方差 1:

    x

    =

    x

    μ

    σ

    x' = \\frac{x – \\mu}{\\sigma}

    x=σxμ

    其中

    μ

    \\mu

    μ

    σ

    \\sigma

    σ 都是在训练集上计算的。测试集和实时识别时只能 transform,不能重新 fit,否则就泄漏了测试集信息、分布也对不上。

    为什么要标准化?

    • KNN 用欧氏距离衡量相似度,如果某一维数值范围特别大,会主导距离计算;
    • SVM(RBF 核) 对特征尺度同样敏感;
    • 决策树、随机森林理论上对单调缩放不敏感,但统一做标准化也不会有副作用,且能保证所有模型输入一致。

    5.5 四个传统机器学习模型对比

    模型关键参数特点
    KNN(K 近邻) n_neighbors=5 最简单,靠最近的 5 个邻居投票;对尺度敏感,预测时计算量大
    SVM(支持向量机) kernel='rbf', gamma='scale' 在小样本高维数据上表现通常较好,能找最大间隔超平面
    决策树 max_depth=10 可解释性强,但单棵树容易过拟合
    随机森林 n_estimators=100 100 棵决策树投票,抗过拟合,通常是四者中最稳的

    代码会依次训练这四个模型,用测试集准确率比较,自动把准确率最高的那个保存为 best_gesture_model.pkl。

    5.6 模型评估指标

    • accuracy_score:整体准确率;
    • classification_report:输出每一类的精确率(precision)、召回率(recall)、F1 分数;
    • confusion_matrix + seaborn.heatmap:绘制混淆矩阵热力图,保存为 confusion_matrix.png。混淆矩阵的行是真实标签、列是预测标签,对角线上数值越大越好;非对角线上的数值能直观看到"哪两个手势容易被搞混"。

    六、第三步:实时手势识别

    6.1 运行方式

  • 第一步:采集数据(右键运行1_hand_gesture_collector.py脚本)
  • 第二步:训练模型(右键2_gesture_recognizer.py脚本,在修改运行配置中,将脚本形参改为–train。然后右键运行)
  • 第三步:实时识别(右键2_gesture_recognizer.py脚本,在修改运行配置中,将脚本形参改为–recognize。然后右键运行)
  • 6.2 实时识别流程

    实时识别部分与采集部分几乎对称:

  • 从摄像头读一帧;
  • 转 RGB 后送入 MediaPipe,得到 21 个关键点;
  • 把关键点展平成 63 维向量;
  • 用训练时保存的 scaler 做标准化;
  • 用最佳模型预测类别;
  • 如果模型支持 predict_proba,同时显示置信度百分比;
  • 在画面上画出关键点与识别结果。
  • 关键点在于:实时推理时的预处理必须与训练时完全一致——同样的 21 个点、同样的顺序、同样的标准化器。


    七、总结

    阶段脚本核心技术
    数据采集 1_hand_gesture_collector.py MediaPipe Hands 21 关键点、JSON 存储
    模型训练 2_gesture_recognizer.py –train 63 维特征、StandardScaler、KNN/SVM/决策树/随机森林、混淆矩阵
    实时识别 2_gesture_recognizer.py –recognize 加载 pkl 模型、摄像头实时推理、置信度显示
    赞(0)
    未经允许不得转载:171主机测评 » 【计算机视觉入门】基于 MediaPipe 与传统机器学习的手势识别实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址