# 本文内容
使用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()
实现了正反面检测,但不能完成侧面检测。高手可以考虑实现全方位检测。