Mediapipe实现手势识别教程

1 编写背景

        在2024年的高效智能创意大赛中,有一个手势识别的任务,需要通过四足机器人的摄像头识别手势然后做出相应动作。在这我将介绍我的手势识别的思路和相关代码,已记录我那2024.7.21死去的比赛。

2 任务要求

        比赛规则如下:

        

3 问题分析与解决

3.1 MediaPipe介绍     

        MediaPipe 是一款由 Google Research 开发并开源的机器学习模型应用框架,专门用于实时流媒体的多模态处理。Mediapipe hand库内置了已经训练好的手部检测和关键点识别模型,这些模型由Mediapipe团队使用大规模标注数据进行训练和优化,确保了高精度和高效能,开发者在使用时无需再进行模型训练,只需调用相关API即可实现手部检测功能。

此外,Mediapipe还训练了获取手部21个地标的模型。这21个地标是预定义好的,具体位置如下:

(1)手腕(1个地标)

          0: 手腕

(2拇指(4个地标)

        1: 拇指根部

        2: 拇指第一个关节

        3: 拇指第二个关节

        4: 拇指指尖

(3食指(4个地标)

        5: 食指根部

        6: 食指第一个关节

        7: 食指第二个关节

        8: 食指指尖

(4中指(4个地标)

        9: 中指根部

        10: 中指第一个关节

        11: 中指第二个关节

        12: 中指指尖

(5)无名指(4个地标)

        13: 名指根部

        14: 无名指第一个关节

        15: 无名指第二个关节

        16: 无名指指尖

(6 小指(4个地标)

        17: 小指根部

        18: 小指第一个关节

        19: 小指第二个关节

        20: 小指指尖

21个地标位置
21个手部地标

3.2 MediaPipe的使用

        此框架最大的好处是不需要自己去训练,模型已经被训练并封装好在python软件包里面,我们可以通过调用他的API很容易实现手部信息获取。

3.2.1 下载安装

        要使用此包,则需要在python3.8环境(建议,比3.8低了没有此包,好像最高可以3.10?)下

pip install mediapipe

3.2.2 调用方法和代码

        话不多说,我直接给出完整的代码和详细注释:

# -*- coding:utf-8 -*-

"""
@ By: ZhengXuan
@ Date: 2024-4-21
"""

import cv2
import mediapipe as mp
import numpy as np


class HandDetector:
    """
    使用mediapipe库查找手。导出地标像素格式。添加了额外的功能。
    如查找方式,许多手指向上或两个手指之间的距离。而且提供找到的手的边界框信息。
    """

    def __init__(self, mode=False, maxHands=2, detectionCon=0.5, minTrackCon=0.5):
        """
        :param mode: 在静态模式下,对每个图像进行检测
        :param maxHands: 要检测的最大手数
        :param detectionCon: 最小检测置信度
        :param minTrackCon: 最小跟踪置信度
        """
        self.results = None
        self.mode = mode
        self.maxHands = maxHands
        self.modelComplex = False
        self.detectionCon = detectionCon
        self.minTrackCon = minTrackCon

        # 初始化手部识别模型
        self.mpHands = mp.solutions.hands
        self.hands = self.mpHands.Hands(self.mode, self.maxHands, self.modelComplex,
                                        self.detectionCon, self.minTrackCon)
        self.mpDraw = mp.solutions.drawing_utils  # 初始化绘图器
        self.tipIds = [4, 8, 12, 16, 20]  # 指尖列表
        self.fingers = []  # 存储手的状态
        self.lmList = []  # 储检测到的手部的每个关键点的坐标

    def findHands(self, img, draw=True):
        """
        从图像(BRG)中找到手部。
        :param img: 用于查找手的图像。
        :param draw: 在图像上绘制输出的标志。
        :return: 带或不带图形的图像
        """
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 将传入的图像由BGR模式转标准的Opencv模式——RGB模式,
        self.results = self.hands.process(imgRGB)  # 处理图像,返回包含检测到的手部信息的结果。这个结果通常包含了手部的关键点坐标

        # 画出手的关键点和线条
        if self.results.multi_hand_landmarks:
            for handLms in self.results.multi_hand_landmarks:
                if draw:
                    self.mpDraw.draw_landmarks(img, handLms,
                                               self.mpHands.HAND_CONNECTIONS)
        return img

    def findPosition(self, img, handNo=0, draw=True):
        """
        查找单手的地标并将其放入列表中像素格式。还可以返回手部周围的边界框。
        :param img: 要查找的主图像
        :param handNo: 如果检测到多只手,则为手部id
        :param draw: 在图像上绘制输出的标志。(默认绘制矩形框)
        :return: 像素格式的手部关节位置列表;手部边界框
        """
        # 保存关键点的像素坐标
        xList = []
        yList = []
        bbox = []
        bboxInfo = []  # 保存首部检测框信息
        self.lmList = []
        if self.results.multi_hand_landmarks:
            myHand = self.results.multi_hand_landmarks[handNo]
            for id, lm in enumerate(myHand.landmark):  # 遍历手部关键点,id表示关键点下标,lm表示关键点对象
                h, w, c = img.shape
                #  lm是存储的是关键点归一化(0~1)的相对位置,
                px, py = int(lm.x * w), int(lm.y * h)  # 转换为图像中的像素坐标
                xList.append(px)
                yList.append(py)
                self.lmList.append([px, py])
                if draw:
                    cv2.circle(img, (px, py), 5, (255, 0, 255), cv2.FILLED)  # 用红点标记关键点
            # 获取手关键点的左上点和右下点
            xmin, xmax = min(xList), max(xList)
            ymin, ymax = min(yList), max(yList)
            # 边界框信息存储
            boxW, boxH = xmax - xmin, ymax - ymin
            bbox = xmin, ymin, boxW, boxH
            # 边界框中心坐标
            cx, cy = bbox[0] + (bbox[2] // 2), \
                     bbox[1] + (bbox[3] // 2)
            # id含义是指的手部最后一个关键点的下标
            bboxInfo = {"id": id, "bbox": bbox, "center": (cx, cy)}

            if draw:
                cv2.rectangle(img, (bbox[0] - 20, bbox[1] - 20),
                              (bbox[0] + bbox[2] + 20, bbox[1] + bbox[3] + 20),
                              (0, 255, 0), 2)

        return self.lmList, bboxInfo

    def fingcurved(self):
        """
        查找除拇指外的四个手指弯曲状态
        计算方式:
            取出除了大拇指以外的四个手指指尖坐标a1、a2、a3、a4(对应地标8,12,16,20),
            然后取出地标为6,10,14,18的坐标b1、b2、b3、b4(即每个指尖以下第二个关节),
            通过比较指尖(a1、a2、a3、a4)到手腕地标(0)和指关节(b1、b2、b3、b4)到地标0的欧几里得距离,
            即可区分手指是否弯曲
        :return: 弯曲手指的列表
        """
        finger = []
        for id in range(1, 5):
            point1 = np.array((self.lmList[self.tipIds[id]][0], self.lmList[self.tipIds[id]][1]))
            point2 = np.array((self.lmList[self.tipIds[id] - 2][0], self.lmList[self.tipIds[id] - 2][1]))
            point3 = np.array((self.lmList[0][0], self.lmList[0][1]))
            if np.linalg.norm(point1 - point3) < np.linalg.norm(point2 - point3):  # 计算两点之间的距离
                finger.append(1)
            else:
                finger.append(0)

        return finger

    def okgesture(self):
        """
        特殊手势处理:判断是否手势为ok
        判断方式:
            ok手势,其拇指指尖地标a0和食指指尖地标a1十分接近,于是我们这样处理:如果中指、无名  
            指、小拇指伸直并且食指指尖到大拇指指尖的距离小于食指指尖到中指指尖距离则断定为ok手   
            势。
        """
        f1, f2, f3, f4 = self.fingcurved()
        if (f2 == 0 and f3 == 0 and f4 == 0):
            point1 = np.array((self.lmList[8][0], self.lmList[8][1]))
            point2 = np.array((self.lmList[4][0], self.lmList[4][1]))
            point3 = np.array((self.lmList[12][0], self.lmList[12][1]))
            if np.linalg.norm(point1 - point2) < np.linalg.norm(point1 - point3):
                return True

    def handType(self):
        """
        检查传入的手部是左还是右
        :return: "Right" 或 "Left"
        """
        if self.results.multi_hand_landmarks:
            if self.lmList[17][0] < self.lmList[5][0]:
                return "Right"
            else:
                return "Left"

 我将mediapipe检测手和获取手部信息封装在了HandDetect类中,下面给出调用代码:

detector = HandDetector()
camera = cv2.VideoCapture(0, cv2.CAP_DSHOW)
while True:
      ret, img = camera.read()
      if ret:
         img = detector.findHands(img)  # 获取你的手部的关键点信息
         cv2.imshow('hand',img)

检测效果如下图所示:

21个地标获取效果图

3.2.3 手势分类

 在获取到这21个地标后,我们通过判断手指弯曲与否和弯曲数量就可以判断大部分手势:

比如:

在比赛规则中的后退手势,当四个手指全伸直就是后退;

前进手势,食指伸直,另外三个手指弯曲就是前进:

但是也可能遇上一些不能简单通过弯曲和伸直来判断的手势:

例如我遇到的右平移手势,看起来是食指弯曲,后三个手指伸直,但是在我自己比画的时候,他很容易将食指识别为伸直,那这样就和上面的后退手势混淆了,所以我写了一个特殊手势处理,在上面代码中我给出了!:

最后我附上实现调用的完整代码:

# -*- coding:utf-8 -*-

"""

@ By: ZhengXuan
@ Date: 2024-4-21

"""
import cv2
from HandTrackingModule import HandDetector


class Main:

    def __init__(self):
        self.detector = None
        self.camera = cv2.VideoCapture(0, cv2.CAP_DSHOW)  # 以视频流传入
        self.camera.set(3, 1280)  # 设置分辨率
        self.camera.set(4, 720)

    def Gesture_recognition(self):
        self.detector = HandDetector()
        gesture_buffer = [None] * 3  # 只有连续三帧都为同一手势,才输出该手势,提高识别鲁棒性
        while True:
            ret, img = self.camera.read()
            if ret:
                img = self.detector.findHands(img)  # 获取你的手部的关键点信息
                cv2.imshow('hand',img)
                lmList, bbox = self.detector.findPosition(img)  # 获取你手部的关键点的像素坐标和边界框
                if lmList:
                    x_1, y_1 = bbox["bbox"][0], bbox["bbox"][1]
                    f1, f2, f3, f4 = self.detector.fingcurved()
                    # 根据手指弯曲状态识别手势并在图像上显示相应文本
                    if f1 == 0 and f2 == 1 and f3 == 1 and f4 == 0:
                        gesture = "twist"
                    elif f1 == 0 and f2 == 1 and f3 == 1 and f4 == 1:
                        gesture = "forward"
                    elif self.detector.okgesture():
                        gesture = "right_move"
                    elif f1 == 0 and f2 == 0 and f3 == 0 and f4 == 0:
                        gesture = "back"
                    elif f1 == 0 and f2 == 0 and f3 == 1 and f4 == 1:
                        gesture = "left_move"
                    elif f1 == 1 and f2 == 1 and f3 == 1 and f4 == 0:
                        gesture = "left_twist"
                    elif f1 == 0 and f2 == 1 and f3 == 0 and f4 == 0:
                        gesture = "right_twist"
                    else:
                        gesture = None
                    if gesture:
                        cv2.putText(img, gesture, (x_1, y_1), cv2.FONT_HERSHEY_PLAIN, 3, (0, 0, 255), 3)
                        gesture_buffer.insert(0, gesture)
                        gesture_buffer.pop()

                cv2.imshow("camera", img)
                if gesture_buffer[0] is not None and all(ges == gesture_buffer[0] for ges in gesture_buffer):
                    self.print_gesture(gesture_buffer[0])
            if cv2.getWindowProperty('camera', cv2.WND_PROP_VISIBLE) < 1:
                break
            # 通过关闭按钮退出程序
            cv2.waitKey(1)
            # if cv2.waitKey(1) & 0xFF == ord("q"):
            #     break # 按下q退出

    def print_gesture(self, gesture):
        if gesture == "twist":
            print("原地扭身")
        elif gesture == "forward":
            print("前进")
        elif gesture == "right_move":
            print("右平移")
        elif gesture == "back":
            print("后退")
        elif gesture == "left_twist":
            print("左旋转")
        elif gesture == "right_twist":
            print("右旋转")
        elif gesture == "left_move":
            print("左平移")


if __name__ == '__main__':
    main = Main()
    main.Gesture_recognition()

4 测试效果

运行上面代码,可以得到如下所示的测试效果:

后退
后退
前进
右平移
原地转身
左旋转
右平移
左旋转

 5 结束语

        虽然比赛没有进入国赛,但我相信不是我的问题,而是比赛主办方的问题,在这我将给出一个宝贵的建议:那就是线上答辩比赛,如果裁判规定了时间,比如我的答辩规定只有6分钟,那么一定一定要提前排练控制在时间内,不然会输的很难看。我希望将技术分享出来帮助到今后有需要的人来完成他们的梦想,希望我今天写的博客能为你的比赛、项目或者学习提供绵薄之力,如果觉得有用,请为我点上一个小小的赞,让更多人看到这篇博客!一起加油!

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: PythonOpenCV库和MediaPipe工具包是可以一起使用的,以实现手势识别的功能。 首先,需要在Python中安装OpenCV库和MediaPipe工具包。可以使用pip命令来安装它们: ``` pip install opencv-python pip install mediapipe ``` 安装完成后,就可以开始使用了。 首先,导入必要的库: ```python import cv2 import mediapipe as mp ``` 接下来,创建一个MediaPipe的Hand对象和一个OpenCV的VideoCapture对象,用于读取摄像头输入: ```python mp_hands = mp.solutions.hands hands = mp_hands.Hands() cap = cv2.VideoCapture(0) ``` 然后,使用一个循环来读取摄像头输入并进行手势识别: ```python while True: ret, frame = cap.read() if not ret: break frame_RGB = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = hands.process(frame_RGB) if results.multi_handedness: for hand_landmarks in results.multi_hand_landmarks: # 在这里可以对hand_landmarks进行处理和识别手势的操作 cv2.imshow('Gesture Recognition', frame) if cv2.waitKey(1) == ord('q'): break ``` 在循环中,首先将读取到的帧转换为RGB格式,然后使用Hands对象的process方法对该帧进行手势识别。得到的结果存储在results变量中。 在对每个检测到的手部进行循环处理时,可以使用hand_landmarks来获取该手的关键点坐标。可以根据这些关键点的位置和运动轨迹来实现手势的识别和分析。 最后,通过cv2.imshow方法显示图像,并使用cv2.waitKey方法等待用户操作。当用户按下"q"键时,循环终止,程序退出。 通过以上步骤,就可以使用PythonOpenCV库和MediaPipe工具包实现手势识别的功能了。当然,实际的手势识别算法和操作需要根据具体需求进行进一步的开发和优化。 ### 回答2: Python OpenCVMediaPipe结合使用可以实现手势识别。首先,我们需要安装必要的库和工具,包括Pythonopencv-pythonmediapipe和其他依赖项。 然后,我们可以使用MediaPipe提供的HandTracking模块来检测手部的关键点。它使用机器学习模型来识别手势,并返回手部关键点的坐标。我们可以通过OpenCV的视频捕捉模块读取摄像头的实时图像。 接下来,我们通过应用MediaPipe的HandTracking模块获取手部关键点的坐标,并使用OpenCV将这些坐标绘制到图像上,以便我们可以实时看到手部的位置和动作。 完成这些基本的设置后,我们可以定义特定的手势,例如拇指和食指的指尖接触,作为一个简单的示例。我们可以通过检查特定的关键点之间的距离和角度来识别这种手势。如果关键点之间的距离较小并且角度较小,则我们可以确定手势是拇指和食指的指尖接触。 我们可以使用类似的方法来识别其他手势,比如手掌的张开和闭合,拳头的形成等等。我们可以定义一系列规则和阈值来确定特定手势的识别。 最后,我们可以根据检测到的手势执行特定的操作。例如,当识别到拇指和食指的指尖接触时,我们可以触发相机的快门,实现手势拍照。 总之,PythonOpenCVMediaPipe结合使用可以实现手势识别。我们可以利用MediaPipe的HandTracking模块检测手部关键点,并使用OpenCV实时绘制手势位置。通过定义特定手势的规则,我们可以识别各种手势并执行相应操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值