基于MediaPipe和传统机器学习的手势识别

一、项目介绍

手势识别是一种常见的计算机视觉应用,可以通过摄像头获取手部图像,再根据手部的关键点位置判断当前手势。本次项目使用 OpenCV 获取摄像头画面,使用 MediaPipe 检测手部关键点,然后将关键点的三维坐标转换成机器学习模型可以处理的特征,最后使用传统机器学习算法完成手势分类和实时识别。

整个项目主要分为两个部分:第一部分负责采集手势数据,第二部分负责训练分类模型并进行实时识别。

程序中设置了 6 种手势类别,分别是拳头、张开手、指向、剪刀手、OK 手势。数据采集程序会将每个手势的关键点信息保存为 JSON 文件,为后续模型训练提供数据。

二、MediaPipe手部关键点检测

程序首先导入 OpenCV、MediaPipe、NumPy、os 和 json 等库,其中 OpenCV 主要负责摄像头和图像处理,MediaPipe 用于手部检测,NumPy 用于数据处理,json 用于保存采集的数据。

MediaPipe 手部模型可以检测手部的 21 个关键点。程序设置:

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

其中 max_num_hands=1 表示一次最多检测一只手,min_detection_confidence=0.7 是手部检测置信度阈值,min_tracking_confidence=0.7 是手部跟踪置信度阈值。

摄像头获取的图像默认是 BGR 格式,而 MediaPipe 处理图像时使用 RGB 格式,因此程序先使用:

复制代码
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(image)

完成颜色空间转换和手部检测,之后再转换回 BGR 格式用于 OpenCV 显示。

当检测到手部以后,可以通过 hand_landmarks.landmark 获取 21 个关键点。每一个关键点都包含 xyz 三个坐标值:

复制代码
landmarks = []
for lm in hand_landmarks.landmark:
    landmarks.append([lm.x, lm.y, lm.z])

因此,一只手最终可以得到:

21 × 3 = 63 个特征值。

同时,程序使用 draw_landmarks() 将关键点以及手部连接线绘制到摄像头画面上,方便观察检测效果。

三、手势数据采集

为了训练机器学习模型,需要先建立手势数据集。程序根据不同手势创建对应的文件夹:

复制代码
DATA_DIR = "gesture_data"

for cls in GESTURE_CLASSES.values():
    os.makedirs(os.path.join(DATA_DIR, cls), exist_ok=True)

这样会形成不同的手势类别目录,每个目录中保存对应手势的 JSON 数据。

程序通过键盘数字控制数据采集。例如按下对应数字键后,如果摄像头检测到了手部,就会把当前手势的关键点保存下来。

保存的数据主要包括四部分:

复制代码
data = {
    "class": cls_name,
    "class_index": cls_idx,
    "landmarks": landmarks,
    "timestamp": str(np.datetime64('now'))
}

其中 class 表示手势名称,class_index 表示手势对应的数字编号,landmarks 保存 21 个关键点的三维坐标,timestamp 保存数据采集时间。

使用 JSON 保存数据的好处是结构比较清晰,而且后续读取时比较方便。这样,原始的手部图像最终被转换成了结构化的关键点数据。

四、将关键点转换为机器学习特征

第二个程序首先遍历不同手势类别对应的文件夹,然后读取其中的 JSON 文件:

复制代码
for cls_idx, cls_name in GESTURE_CLASSES.items():
    cls_dir = os.path.join(DATA_DIR, cls_name)

读取 JSON 后,将其中的 21 个关键点展开成一个一维特征向量:

复制代码
landmarks = data["landmarks"]
feature_vector = []

for lm in landmarks:
    feature_vector.extend(lm)

每个关键点包含三个数据,因此最终一个样本具有 63 维特征:

21 个关键点 × 3 个坐标 = 63 维。

所有样本组成特征矩阵 X,对应的手势类别组成标签数组 y

这种方法相比直接使用原始图片进行训练更加简单,因为模型不需要直接处理大量像素,而是直接学习手部关键点之间的空间关系。

五、划分数据集和特征标准化

得到数据之后,程序使用 train_test_split() 将数据划分为训练集和测试集:

复制代码
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 可以让训练集和测试集中各类别的比例保持相对一致。

之后使用 StandardScaler 对特征进行标准化:

复制代码
scaler = StandardScaler()

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

这里需要注意,标准化器只在训练集上执行 fit,然后使用相同的标准化规则处理测试集。这可以保证训练和测试时的数据处理方式一致。

程序还使用 joblib.dump() 将标准化器保存成 scaler.pkl,方便实时识别时使用相同的特征处理方式。

六、多种传统机器学习模型

为了比较不同算法在手势分类任务上的表现,程序同时训练了四种传统机器学习模型:

复制代码
models = {
    "K近邻分类器": KNeighborsClassifier(n_neighbors=5),
    "支持向量机": SVC(kernel='rbf', gamma='scale'),
    "决策树": DecisionTreeClassifier(max_depth=10),
    "随机森林": RandomForestClassifier(n_estimators=100)
}

分别是 K 近邻、支持向量机、决策树和随机森林。

K 近邻分类器通过寻找距离较近的样本进行分类;支持向量机通过寻找合适的分类边界完成分类;决策树通过不断进行条件判断进行分类;随机森林则通过多棵决策树共同完成预测。

程序依次训练这些模型,并通过测试集计算准确率:

复制代码
model.fit(X_train_scaled, y_train)
y_pred = model.predict(X_test_scaled)
accuracy = accuracy_score(y_test, y_pred)

同时使用 classification_report() 输出分类报告,从多个指标观察模型的分类情况。

七、选择并保存模型

程序通过比较每个模型的测试集准确率,记录当前表现较好的模型:

复制代码
if accuracy > best_accuracy:
    best_accuracy = accuracy
    best_model = model
    best_model_name = name

最终使用 joblib.dump() 保存模型:

复制代码
joblib.dump(best_model, "best_gesture_model.pkl")

因此训练完成后会得到两个重要文件:

  • best_gesture_model.pkl:保存训练好的手势分类模型

  • scaler.pkl:保存特征标准化器

这样在之后进行实时识别时,就不需要重新训练模型,直接加载这两个文件即可。

八、使用混淆矩阵分析分类结果

除了准确率和分类报告,程序还使用混淆矩阵对模型进行进一步分析:

复制代码
cm = confusion_matrix(y_test, y_pred_best)

然后通过 Seaborn 绘制热力图:

复制代码
sns.heatmap(
    cm,
    annot=True,
    fmt='d',
    cmap='Blues',
    xticklabels=GESTURE_CLASSES.values(),
    yticklabels=GESTURE_CLASSES.values()
)

混淆矩阵可以观察不同手势之间的预测情况。例如某一种手势经常被预测成另一种手势,就可以从矩阵中比较直观地发现。

最终图像保存为:

复制代码
confusion_matrix.png

因此,模型评估不仅可以看整体准确率,还可以进一步分析具体类别之间的识别情况。

九、实时手势识别

训练完成以后,程序可以进入实时识别模式。首先加载训练好的模型和标准化器:

复制代码
model = joblib.load("best_gesture_model.pkl")
scaler = joblib.load("scaler.pkl")

然后重新启动摄像头和 MediaPipe 手部检测。实时检测到手部后,同样提取 21 个关键点的三维坐标,并转换为 63 维特征向量:

复制代码
landmarks = []

for lm in hand_landmarks.landmark:
    landmarks.extend([lm.x, lm.y, lm.z])

接着使用训练阶段保存的标准化器处理数据:

复制代码
landmarks_scaled = scaler.transform([landmarks])

最后将处理后的特征输入模型:

复制代码
prediction = model.predict(landmarks_scaled)
predicted_class = GESTURE_CLASSES[prediction[0]]

这样就可以得到当前摄像头画面中的手势类别。

如果模型支持 predict_proba(),程序还会获取各类别的预测概率,并把预测结果和概率显示在摄像头画面上:

复制代码
display_text = f"{predicted_class} ({max_prob:.1f}%)"

最终通过 cv2.putText() 将识别结果显示出来,实现实时手势识别。

十、命令行参数

程序最后使用 argparse 实现训练和实时识别两种运行方式:

复制代码
parser.add_argument('--train', action='store_true', help='训练模型')
parser.add_argument('--recognize', action='store_true', help='实时识别')

运行:

复制代码
python 程序.py --train

可以进行模型训练。

运行:

复制代码
python 程序.py --recognize

可以进入实时手势识别模式。

十一、项目流程总结

整个手势识别系统可以概括为以下流程:

摄像头采集图像 → MediaPipe检测手部 → 提取21个关键点 → 获取63维三维坐标特征 → 保存JSON数据 → 划分训练集和测试集 → 特征标准化 → 训练多种机器学习模型 → 测试模型 → 保存模型和标准化器 → 摄像头实时检测 → 提取关键点 → 标准化 → 模型预测 → 显示识别结果。

相关推荐
ZDN_is_beauty1 小时前
綦江烟草部署(在wsl2里部署)
人工智能·python
小小龙学IT1 小时前
astAPI 异步 Web 框架深度解析
python
PiaoKe___3 小时前
云手机原理与 Python 自动化实战:ADB 批量控制、任务调度与落地建议
服务器·arm开发·python·自动化
Gigavision9 小时前
基于BUAA-MIHR数据集的噪声解耦对比学习算法
人工智能·python·深度学习·算法
IPdodo_10 小时前
跨境 API 调用不稳定怎么办:出口、超时重试与链路监控的实践
网络·python·网络协议
Madison-No710 小时前
搭建项目测试环境
linux·运维·服务器·python
计算机编程-吉哥10 小时前
YOLO26 vs YOLO11 vs YOLOv8:深度学习咖啡果实成熟度分割系统【计算机毕业设计选题推荐】
人工智能·python·深度学习·yolo·django·毕业设计
大衛說11 小时前
11 · 异常处理与日志
python
auto_go11 小时前
Python 实战指南(7)——账本动不动就崩?先把“魔法字符串”和“裸报错”干掉
python