mediapipe 手势0~10剪刀石头布正反面检测实现

# 本文内容

使用Google的mediapipe库实现手比数字或猜拳手势识别,相比前人的方案,可以实现手指弯曲状态(半闭合)检测。对于距离和角度没有太严格要求。有兴趣可以玩玩。
废话不多说,“Show me you code"
#================================================================
#   Copyright (C) 2024 Sangfor Ltd. All rights reserved.
#   
#   文件名称:oneHandRecognition.py
#   创 建 者:XXXXXXXXX
#   创建日期:2024年10月12日
#   描    述:手势识别
#
#================================================================
import cv2
import mediapipe as mp
import numpy as np
from enum import Enum
import math
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_hands = mp.solutions.hands

# 手指点常量
FINGER0_0 = 0
FINGER0_1 = 1
FINGER0_2 = 2
FINGER0_3 = 3
FINGER0_4 = 4
FINGER1_0 = 5
FINGER1_1 = 6
FINGER1_2 = 7
FINGER1_3 = 8
FINGER2_0 = 9
FINGER2_1 = 10
FINGER2_2 = 11
FINGER2_3 = 12
FINGER3_0 = 13
FINGER3_1 = 14
FINGER3_2 = 15 
FINGER3_3 = 16
FINGER4_0 = 17
FINGER4_1 = 18
FINGER4_2 = 19
FINGER4_3 = 20

X = 0
Y = 1

class Directory(Enum):
    Left = 0
    Right = 1
    Up = 2
    Down = 3
    Mid = 4
    # 主方向
    X = 5
    Y = 6

class FingerStatus(Enum):
    FingerOpen = 1
    FingerClose = 2
    FingerHalfOpen = 3

class Gesture(Enum):
    Cloth = 1
    Scissor = 2
    Stone = 3

# 获取手势信息,转换成二维数组更易于访问
def getHandInfo(image):
  lmList = []
  image.flags.writeable = False
  image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
  results = hands.process(image)

  image.flags.writeable = True
  image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
  if results.multi_hand_landmarks:
    for hand_landmarks in results.multi_hand_landmarks:
      for index, position in enumerate(hand_landmarks.landmark):
        # 位置信息存放到二维数组里面
        lmList.append([int(position.x * picWidth), int(position.y * picHeight)])
        # 视频中显示节点
      # mp_drawing.draw_landmarks(
          # image,
          # hand_landmarks,
          # mp_hands.HAND_CONNECTIONS,
          # mp_drawing_styles.get_default_hand_landmarks_style(),
          # mp_drawing_styles.get_default_hand_connections_style())
  return image, lmList

#
# @brife  : 获取手掌方向
# @param  : 手掌21个点
# @return : leftOrRight Directory.Left 向左 Directory.Right 向右
# @return : upOrDown Directory.Up 向上 Directory.Down 向下
def getHandDirection(marks):
    leftOrRight = Directory.Left
    upOrDown = Directory.Up
    mainDirectory = Directory.X

    # 计算手掌主方向,是X还是Y
    if(abs(marks[FINGER0_0][X] - marks[FINGER2_0][X]) > abs(marks[FINGER0_0][Y] - marks[FINGER2_0][Y])):
        mainDirectory = Directory.X
    else:
        mainDirectory = Directory.Y
    
    # 计算中指根到手掌根距离
    xDistance = marks[FINGER0_0][X] - marks[FINGER2_0][X]
    yDistance = marks[FINGER0_0][Y] - marks[FINGER2_0][Y]
    baseLineLenght = math.sqrt(math.pow(xDistance, 2) + math.pow(yDistance, 2))

    # 左右比较,差值很小
    if abs(marks[FINGER0_0][X] - marks[FINGER2_0][X]) < (baseLineLenght / 30):
        leftOrRight = Directory.Mid;
    else :
        if marks[FINGER0_0][X] > marks[FINGER2_0][X]:
            leftOrRight = Directory.Right
        else :
            leftOrRight = Directory.Left
    # 上下比较,差值很小时认为在中间
    if abs(marks[FINGER0_0][Y] - marks[FINGER2_0][Y]) < (baseLineLenght / 30):
        upOrDown = Directory.Mid;
    else :
        if marks[FINGER0_0][Y] > marks[FINGER2_0][Y]:
            upOrDown = Directory.Up
        else :
            upOrDown = Directory.Down

    return mainDirectory,leftOrRight, upOrDown

#
# @brife  : 获取手指子节点角度0 ~ 360
# @param  : marks 21个手掌节点
# @param  : startIndex 计算节点索引
# @return : 0 ~ 360 
def getFingerPartDegree(marks, startIndex):
    if handInfo[startIndex+1][Y] - handInfo[startIndex][Y] <= 0 and handInfo[startIndex+1][X] - handInfo[startIndex][X] < 0:
        fingerPartDegree = math.degrees(math.atan((handInfo[startIndex+1][Y] - handInfo[startIndex][Y]) / (handInfo[startIndex+1][X] - handInfo[startIndex][X])))
    elif handInfo[startIndex+1][Y] - handInfo[startIndex][Y] < 0 and handInfo[startIndex+1][X] - handInfo[startIndex][X] >= 0:
        if handInfo[startIndex+1][X] - handInfo[startIndex][X] == 0:
            fingerPartDegree = 90;
        else:
            fingerPartDegree = math.degrees(math.atan((handInfo[startIndex+1][Y] - handInfo[startIndex][Y]) / (handInfo[startIndex+1][X] - handInfo[startIndex][X]))) + 180
    elif handInfo[startIndex+1][Y] - handInfo[startIndex][Y] >= 0 and handInfo[startIndex+1][X] - handInfo[startIndex][X] > 0:
        fingerPartDegree = math.degrees(math.atan((handInfo[startIndex+1][Y] - handInfo[startIndex][Y]) / (handInfo[startIndex+1][X] - handInfo[startIndex][X]))) + 180
    else:
        if handInfo[startIndex+1][X] - handInfo[startIndex][X] == 0:
            fingerPartDegree = 270
        else:
            fingerPartDegree = math.degrees(math.atan((handInfo[startIndex+1][Y] - handInfo[startIndex][Y]) / (handInfo[startIndex+1][X] - handInfo[startIndex][X]))) + 360
    return int(fingerPartDegree)

#
# @brife  : 获取手掌主方向X或Y
# @param  : marks手掌节点
# @return : X Y 
def getHandMainDirectory(marks):
    if abs(marks[FINGER2_0][Y] - marks[FINGER0_0][Y]) > abs(marks[FINGER2_0][X] - marks[FINGER0_0][X]):
        return Directory.Y
    else:
        return Directory.X

#
# @brife  : 获取单根手指状态,打开(伸直),关闭(闭合),半打开
# @param  : handMainDirectory 手掌主方向X或Y
# @param  : marks 21个手节点
# @param  : 手指根节点编号
# @return : 5根手指状态
def getSingleFingerStatus(marks, fingerStartIndex):
    fingerDegree = []
    # 拇指不能用角度判断,不然不同角度会不准确
    if(fingerStartIndex < FINGER1_0):
        if Directory.Y == getHandMainDirectory(marks):
            # 大拇指指尖处于手掌外
            if (marks[FINGER0_4][X] > marks[FINGER0_2][X] and marks[FINGER0_2][X] > marks[FINGER4_0][X]) or \
                    (marks[FINGER0_4][X] < marks[FINGER0_2][X] and marks[FINGER0_2][X] < marks[FINGER4_0][X]):
                return FingerStatus.FingerOpen
            else:
                return FingerStatus.FingerClose
        else:
            # 大拇指指尖处于手掌外
            if (marks[FINGER0_4][Y] > marks[FINGER0_2][Y] and marks[FINGER0_2][Y] > marks[FINGER4_0][Y]) or \
                    (marks[FINGER0_4][Y] < marks[FINGER0_2][Y] and marks[FINGER0_2][Y] < marks[FINGER4_0][Y]):
                return FingerStatus.FingerOpen
            else:
                return FingerStatus.FingerClose
    
    # 其它手指使用指节角度判断
    else:
        fingerDegree.append(getFingerPartDegree(marks, fingerStartIndex))
        fingerDegree.append(getFingerPartDegree(marks, fingerStartIndex + 1))
        fingerDegree.append(getFingerPartDegree(marks, fingerStartIndex + 2))
        if max(fingerDegree) - min(fingerDegree) < 35:
            if Directory.Y == getHandMainDirectory(marks):
                if((marks[FINGER0_0][Y] < marks[fingerStartIndex + 1][Y] and marks[fingerStartIndex + 1][Y] < marks[fingerStartIndex][Y]) or \
                        (marks[FINGER0_0][Y] > marks[fingerStartIndex + 1][Y] and marks[fingerStartIndex + 1][Y] > marks[fingerStartIndex][Y])):
                    return FingerStatus.FingerClose
                else:
                    return FingerStatus.FingerOpen
            else:
                if((marks[FINGER0_0][X] < marks[fingerStartIndex + 1][X] and marks[fingerStartIndex + 1][X] < marks[fingerStartIndex][X]) or \
                        (marks[FINGER0_0][X] > marks[fingerStartIndex + 1][X] and marks[fingerStartIndex + 1][X] > marks[fingerStartIndex][X])):
                    return FingerStatus.FingerClose
                else:
                    return FingerStatus.FingerOpen

        # 初步判断出当前手指是弯曲的
        else:
            if Directory.Y == getHandMainDirectory(marks):
                if (marks[fingerStartIndex + 3][Y] > marks[fingerStartIndex][Y] and marks[fingerStartIndex][Y] > marks[FINGER0_0][Y]) or \
                        (marks[fingerStartIndex + 3][Y] < marks[fingerStartIndex][Y] and marks[fingerStartIndex][Y] < marks[FINGER0_0][Y]):
                    return FingerStatus.FingerHalfOpen
                else:
                    return FingerStatus.FingerClose
            else:
                if (marks[fingerStartIndex + 3][X] > marks[fingerStartIndex][X] and marks[fingerStartIndex][X] > marks[FINGER0_0][X]) or \
                        (marks[fingerStartIndex + 3][X] < marks[fingerStartIndex][X] and marks[fingerStartIndex][X] < marks[FINGER0_0][X]):
                    return FingerStatus.FingerHalfOpen
                else:
                    return FingerStatus.FingerClose

#
# @brife  : 获取5根手指状态,打开(伸直),关闭(闭合),半打开
# @param  : marks 21个手节点
# @return : 5根手指状态
def getFingersStatus(marks):
    fingerStatus = [];
    fingerStatus.append(getSingleFingerStatus(marks ,FINGER0_1))
    fingerStatus.append(getSingleFingerStatus(marks ,FINGER1_0))
    fingerStatus.append(getSingleFingerStatus(marks ,FINGER2_0))
    fingerStatus.append(getSingleFingerStatus(marks ,FINGER3_0))
    fingerStatus.append(getSingleFingerStatus(marks ,FINGER4_0))
    return fingerStatus

def getFingerMean(fingerStatus):
    if FingerStatus.FingerClose == fingerStatus[0] and \
        FingerStatus.FingerClose == fingerStatus[1] and \
        FingerStatus.FingerClose == fingerStatus[2] and \
        FingerStatus.FingerClose == fingerStatus[3] and \
        FingerStatus.FingerClose == fingerStatus[4]:
            return 0
    elif FingerStatus.FingerOpen == fingerStatus[0] and \
        FingerStatus.FingerOpen == fingerStatus[1] and \
        FingerStatus.FingerOpen == fingerStatus[2] and \
        FingerStatus.FingerOpen == fingerStatus[3] and \
        FingerStatus.FingerOpen == fingerStatus[4]:
            return 5
    elif FingerStatus.FingerOpen != fingerStatus[0] and \
        FingerStatus.FingerOpen == fingerStatus[1] and \
        FingerStatus.FingerOpen != fingerStatus[2] and \
        FingerStatus.FingerOpen != fingerStatus[3] and \
        FingerStatus.FingerOpen != fingerStatus[4]:
            return 1
    elif FingerStatus.FingerOpen != fingerStatus[0] and \
        FingerStatus.FingerOpen == fingerStatus[1] and \
        FingerStatus.FingerOpen == fingerStatus[2] and \
        FingerStatus.FingerOpen != fingerStatus[3] and \
        FingerStatus.FingerOpen != fingerStatus[4]:
            return 2
    elif FingerStatus.FingerOpen != fingerStatus[0] and \
        FingerStatus.FingerOpen == fingerStatus[1] and \
        FingerStatus.FingerOpen == fingerStatus[2] and \
        FingerStatus.FingerOpen == fingerStatus[3] and \
        FingerStatus.FingerOpen != fingerStatus[4]:
            return 3
    elif FingerStatus.FingerOpen != fingerStatus[0] and \
        FingerStatus.FingerOpen == fingerStatus[1] and \
        FingerStatus.FingerOpen == fingerStatus[2] and \
        FingerStatus.FingerOpen == fingerStatus[3] and \
        FingerStatus.FingerOpen == fingerStatus[4]:
            return 4
    elif FingerStatus.FingerOpen == fingerStatus[0] and \
        FingerStatus.FingerOpen != fingerStatus[1] and \
        FingerStatus.FingerOpen != fingerStatus[2] and \
        FingerStatus.FingerOpen != fingerStatus[3] and \
        FingerStatus.FingerOpen != fingerStatus[4]:
            return 6
    elif FingerStatus.FingerOpen != fingerStatus[0] and \
        FingerStatus.FingerOpen != fingerStatus[1] and \
        FingerStatus.FingerOpen != fingerStatus[2] and \
        FingerStatus.FingerOpen != fingerStatus[3] and \
        FingerStatus.FingerClose != fingerStatus[4]:
            return 7
    elif FingerStatus.FingerOpen == fingerStatus[0] and \
        FingerStatus.FingerOpen == fingerStatus[1] and \
        FingerStatus.FingerOpen != fingerStatus[2] and \
        FingerStatus.FingerOpen != fingerStatus[3] and \
        FingerStatus.FingerOpen != fingerStatus[4]:
            return 8
    elif FingerStatus.FingerOpen != fingerStatus[0] and \
        FingerStatus.FingerHalfOpen == fingerStatus[1] and \
        FingerStatus.FingerOpen != fingerStatus[2] and \
        FingerStatus.FingerOpen != fingerStatus[3] and \
        FingerStatus.FingerOpen != fingerStatus[4]:
            return 9
    else:
            return 'X'

#
# @brife  : 手势转换成文字
# @param  : gesture 手势
# @return : 手势的文字描述
def gestureToString(gesture):
    if Gesture.Cloth == gesture:
        return "Cloth"
    elif Gesture.Stone == gesture:
        return "Stone"
    else:
        return "Scissor"

#
# @brife  : 程序主循环
# @param  : none
# @return : none
cap = cv2.VideoCapture(0)
with mp_hands.Hands(
    model_complexity=0,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5) as hands:
  while cap.isOpened():
    success, image = cap.read()
    picWidth = image.shape[1]
    picHeight = image.shape[0]
    if not success:
      print("Ignoring empty camera frame.")
      # If loading a video, use 'break' instead of 'continue'.
      continue
    image, handInfo = getHandInfo(image);
    image = cv2.flip(image, 1)
    if len(handInfo):
        curGesture = getFingerMean(getFingersStatus(handInfo))
        cv2.putText(image, str(curGesture), (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 4)
    cv2.imshow('MediaPipe Hands', image)
    if cv2.waitKey(5) & 0xFF == 27:
      break
cap.release()

实现了正反面检测,但不能完成侧面检测。高手可以考虑实现全方位检测。

### 使用MediaPipe实现石头剪刀手势识别 #### 准备工作 为了使用MediaPipe实现石头剪刀手势识别,环境配置至关重要。确保安装Python版本为3.8,并且开发工具PyCharm版本应为2020年发的版本[^1]。 #### 安装依赖库 首先需安装必要的软件包,包括`opencv-python`用于视频处理以及`mediapipe`作为主要的部追踪和姿态估计框架。可以通过pip命令完成这些操作: ```bash pip install opencv-python mediapipe numpy ``` #### 初始化摄像头并加载模型 创建一个新的Python脚本文件,在其中初始化Webcam捕获对象,并设置MediaPipe Hands模块实例化参数以便后续调用掌关键点检测功能。 ```python import cv2 import mediapipe as mp cap = cv2.VideoCapture(0) mp_hands = mp.solutions.hands hands = mp_hands.Hands(static_image_mode=False, max_num_hands=2, # 只考虑一只的情况 min_detection_confidence=0.7) ``` #### 关键点提取与预处理 当捕捉到帧后,利用MediaPipe提供的Hands解决方案对掌区域的关键部位进行定位分析。对于每一个被成功跟踪到的形结构,获取其各个关节坐标位置信息。 ```python while cap.isOpened(): ret, frame = cap.read() if not ret: break image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = hands.process(image_rgb) if results.multi_hand_landmarks is None: continue handLms = results.multi_hand_landmarks[0] landmarks = [] for id, lm in enumerate(handLms.landmark): h, w, c = frame.shape cx, cy = int(lm.x *w), int(lm.y*h) landmarks.append([id, cx, cy]) # 进一步处理landmarks... ``` #### 判断手势类别 根据指的状态(即是否张开),可以定义不同的条件来区分“石头”, “剪刀” 和 "". 特别地,这里采用了一种简单直观的方法:统计五指中哪些处于展开状态;再依据特定组合模式匹配对应的游戏选项。 ```python def get_finger_status(fingertips_positions): fingers_up_down = [] thumb_tip_x = fingertips_positions[4][1] index_tip_y = fingertips_positions[8][2] # Thumb check (for right hand only here; adjust accordingly for left/right detection) if thumb_tip_x < fingertips_positions[5][1]: fingers_up_down.append(True) else: fingers_up_down.append(False) # Other four fingers checks for i in range(1, 5): finger_id = i*4 + 8 if fingertips_positions[finger_id][2] < index_tip_y: fingers_up_down.append(True) else: fingers_up_down.append(False) return fingers_up_down if len(landmarks)>0 and all(x>0 for x in [lm[1] for lm in landmarks]): fingertip_ids = [4, 8, 12, 16, 20] # IDs of the tips of each finger. fingertips_pos = [[landmarks[id][idx] for idx in [1, 2]] for id in fingertip_ids] fingers_state = get_finger_status(fingertips_pos) gesture_name = '' if sum(fingers_state)==0 or (sum(fingers_state[:])>=4 and fingers_state.count(True)>=4): gesture_name='Rock' elif fingers_state==[False,True,False,False,True]: gesture_name='Scissors' elif fingers_state==[True]*len(fingers_state): gesture_name='Paper' font = cv2.FONT_HERSHEY_SIMPLEX org = (50, 50) color = (255, 0, 0) thickness = 2 img_with_text=cv2.putText(frame,'Gesture Detected:'+gesture_name ,org,font,1,color,thickness,cv2.LINE_AA ) ``` 上述代码片段展示了如何解析部姿势数据以确定当前显示的是哪种游戏动作,并将其结果显示在实时视频流画面上[^2]。 #### 显示结果 最后,将带有标注文字的结果图像展示出来供用户查看。同时也要记得释放资源关闭窗口。 ```python cv2.imshow('Hand Gesture Recognition',img_with_text ) if cv2.waitKey(1)&0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值