基于opencv与mediapipe手势关键点检测,并使用KNN近邻算法手势识别(石头、剪刀、布)的python代码实现

本文主要利用opencv读取摄像头的手势画面数据,利用mediapipe库提供的方法将手势图像画面转化为手部21个关节点的坐标值,通过坐标值的数理关系进行任意手势的识别。

通过mediapipe拿到21个手势关节点坐标后,对任意手势识别的方法有很多种,常见的方法是通过数学公司推导进行识别,比如计算某两两关节点的距离进行判别特定手势。本文打算另辟蹊径,通过采集大量手势的坐标点后,通过KNN近邻算法进行手势识别分类。这种方法的主要优点是不需要对新增手势进行特定的数学公式推导和代码编程,只需要采集新的手势坐标数据,在程序启动前,由KNN近邻算法重新训练一下即可适用,以下是我的代码实现部分:

1. 读取训练数据

笔者分别采集了石头剪刀布三个手势20余次的数据(如果有条件可以多采集一些),训练出来的准确率已经高达90%以上,完全够用。采集石头、剪刀、布三个手势的坐标值数据,写入csv文件中以供KNN算法训练,采集数据时,在摄像头前不同角度和远近重复三个手势,并标注手势的类别。

数据集及代码如下:

num, x_value, y_value, hand_result
0,0.5252603888511658,1.023892879486084,Cloth
1,0.48582276701927185,1.0048032999038696,Cloth
2,0.45390379428863525,0.9613886475563049,Cloth
...
20,0.6634553074836731,0.8075148463249207,Cloth
import cv2
import mediapipe as mp
import time
import csv
import numpy as np
import pandas as pd
import math
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsClassifier


def readData(path):
    data = pd.read_csv(path,
                       encoding='utf8', engine='python')
    data = np.array(data)
    return data

2. 数据预处理

因为手势判别只与各个关节点的坐标相对位置相关,与在屏幕上的绝对位置无关,故将21个点坐标转换手掌心节点与其余20个点的距离数据,方法很简单,并不会运用到太多的数据公式,通过勾股定理即可计算而得。

def tarinData2Distance(data, train=False):
    if train == True:
        target = data[:, -1]
        for i in np.arange(len(target)):
            if target[i] == "Cloth":
                data[i][-1] = 0
            elif target[i] == "Scissor":
                data[i][-1] = 1
            else:
                data[i][-1] = 2
    data = np.array(data, dtype='float')
    num_x = 10
    num_y = 10
    list1 = []
    for i in np.arange(np.shape(data)[0]):
        list01 = []
        if int(data[i][0]) == 0:
            basex = data[i][1] * num_x
            basey = data[i][2] * num_y
            for j in np.arange(1, 21):
                str01 = math.pow((basex - data[i + j][1] * num_x), 2) + math.pow((basey - data[i + j][2] * num_y),                                                                                  2)
                list01.append(str01)
            if train:
                list01.append(data[i][3])
            list1.append(list01)
    list1 = np.array(list1)
    return list1

识别过程中,采集到的手势数据也需要进行同样的预处理才能放到模型里进行判别

def imgData2Distance(landmark, img):
    list1 = []
    for i, lm in enumerate(landmark):
        list01 = [i, lm.x, lm.y]
        list1.append(list01)
        xPos = int(lm.x * np.shape(img)[1])
        yPos = int(lm.y * np.shape(img)[0])
        print(i, xPos, yPos)
        cv2.putText(img, str(i), (xPos - 25, yPos + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), 2)  # 大小,颜色,粗度
    return np.array(list1)

3. 训练KNN模型

def trainKNNHand():
    # 1.获取数据集
    sorc = tarinData2Distance(readData(r".\\handeData.csv"), train=True)
    data = sorc[:, 0:-1]
    data = np.array(data, dtype='float')

    target = sorc[:, -1]
    target = np.array(target, dtype='float')

    # 2. 数据集切分
    X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=666)

    # 3.建立KNN模型
    param_grid = [
        {
            'weights': ['uniform'],  # 不考虑距离权重的模式
            'n_neighbors': [i for i in range(1, 11)]  # 邻近点的个数
        },
        {
            'weights': ['distance'],  # 考虑距离权重的模式
            'n_neighbors': [i for i in range(1, 11)],
            'p': [i for i in range(1, 6)]  # 明可夫斯基距离参数 p = (0:曼哈顿距离, 1:欧拉距离)
        }
    ]
    knn_clf = KNeighborsClassifier()  # 实例化 KNN 算法
    grid_search = GridSearchCV(knn_clf, param_grid, n_jobs=-1, verbose=2)  # n_jobs=-1 最大并行训练
    grid_search.fit(X_train, y_train)  # 针对训练数据进行最佳超参数搜索
    best_estimator = grid_search.best_estimator_  # 这就是最终得到的最佳参数
    best_score = grid_search.best_score_  # 使用上诉训练到的最佳参数得到的分类得分
    print("模型训练得分{}".format(best_score))
    print("模型参数{}".format(best_estimator))

    # 5.使用并评估KNN模型
    knn_clf = grid_search.best_estimator_
    knn_clf.fit(X_train, y_train)
    test_score_1 = knn_clf.score(X_test, y_test)  # 可以直接通过KNeighborsClassifier内置函数得到分类得分
    print("test_score_1 = {}".format(test_score_1))  # test_score_1 = 1.0
    return knn_clf

4. 手势判别

将上述步骤训练得到的模型运用到实时采集的摄像头画面中,即可对摄像头采集到的手势进行判别,注意opencv的 VideoCapture() 不仅可以读取摄像头画面数据,还可以读取视频文件,图片文件,只需在方法入参中传入相应文件的全路径加文件全名即可,非常方便,本文读取的是笔记本自带摄像头,只需在入参中填入数字0即可,如果是台式机也可加装USB摄像头进行试验。

通过mediapipe库提供的方法对视频图像数据进行手势21个关节点的坐标读取,顺便说一句,mediapipe不仅可以实现手势识别,还能进行人脸检测、人脸识别、姿势识别、眼动识别。

如下所示:

def handIdentify():
    cap = cv2.VideoCapture(0)
    mpHands = mp.solutions.hands
    hands = mpHands.Hands(
        static_image_mode=False,  # 是否为静态图片
        max_num_hands=2,  # 最大识别手的数量
        model_complexity=1,  # 模型置信度
        min_detection_confidence=0.5,  # 侦测置信度
        min_tracking_confidence=0.5)  # 追踪置信度
    mpDraw = mp.solutions.drawing_utils
    handLmsStyle = mpDraw.DrawingSpec(color=(0, 0, 255), thickness=5)
    handConStyle = mpDraw.DrawingSpec(color=(255, 0, 0), thickness=10)
    pTime = 0
    while True:
        ret, img = cap.read()  # ret 代表是否读到图片(Boolean), img表示截取到的一帧的图片数据
        if ret:
            imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            result = hands.process(imgRGB)  # 手势识别的结果数据
            if result.multi_hand_landmarks:
                for handLms in result.multi_hand_landmarks:
                    mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS, handLmsStyle, handConStyle)
                    list1 = imgData2Distance(handLms.landmark, img)
                    x_data = tarinData2Distance(list1, train=False)
                    hand_num = knn_clf.predict(x_data)
                    if hand_num == 0:
                        hand_type = 'Cloth'
                    elif hand_num == 1:
                        hand_type = 'Scissor'
                    else:
                        hand_type = 'Stone'
                    xPos = int(list1[0][1] * np.shape(img)[1])
                    yPos = int(list1[0][2] * np.shape(img)[0])
                    cv2.putText(img, hand_type, (xPos, yPos), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 3)
                    print("此图识别到的手势是{}".format(hand_type))
            cTime = time.time()
            fps = 1 / (cTime - pTime)
            pTime = cTime
            cv2.putText(img, f"FPS:{int(fps)}", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0))
            cv2.imshow('img', img)
        if cv2.waitKey(1) == ord('q'):
            break
    cap.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    knn_clf = trainKNNHand()
    handIdentify()

5. 总结

通过opencv读取图像数据,使用mediapipe识别手势图像21个坐标点,通过求掌心坐标点与其余20个关键坐标点的距离作为20个特征值形成训练数据,使用KNN算法对数据进行分类判别,能够有效识别石头剪刀布三个手势,如果读者感兴趣的话,还可以基于以上代码做一个与机器人对玩石头剪刀布的程序。最终效果如下所示:

 


                
  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
手势识别技术在近年来得到了越来越广泛的应用,常用于人机交互、虚拟现实、智能家居等领域。而基于opencv mediapipe手势识别技术,相较于传统的计算机视觉方案,具有更加精准、实时、可靠的特点。 mediapipe是Google开发的一套跨平台机器学习框架,其中包含了许多强大的算法和模型,其中就包括了手部姿势估计。通过这些算法,我们可以实现对手的关键点位置进行实时预测,并进行手势识别分类,从而实现数字、石头剪刀手势的识别。 在手势识别实现过程中,关键的问题在于对手部关键点的识别和跟踪。在mediapipe中提供了一种基于深度学习的神经网络,用来精准地检测出手部的21个关键点。这个模型还可以在不同背景下进行对比度调整、亮度调整、旋转调整等图像处理操作,从而适应不同环境下的手势识别场景。 在实际应用中,我们可以使用PythonOpenCV库来实现基于mediapipe手势识别。通过摄像头获取到实时视频流后,我们可以先对图像进行预处理,然后对关键点进行检测,最终进行手势分类和识别。在实际场景中,我们可以通过改变背景颜色、增加光照环境等方式来测试手势识别算法的鲁棒性和相对误差。 总之,基于opencv mediapipe手势识别技术,是一种精准、实时、可靠的手势识别方案,具有广泛的应用前景。通过这种技术,我们可以实现更加自然和直观的人机交互方式,带来更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值