文章目录
1 前言
通过Pyqt及mediapipe库实现动态手势识别效果演示,即“左右摇动手指”,使得QLabel的选择状态移动,手指向左摇动,选定的QLabel向左移动,手指向右移动,选定的QLabel向右移动。
Mediapipe是google的一个开源项目,支持跨平台的常用ML方案。可以提供人脸识别、人体关节点识别、人体手部关节点识别等功能,使用接口简单,直接import mediapipe as np
并选择相应的solution
,按照相应的步骤操作即能实现相应的识别操作。
通过Pyqt创建结果显示界面,opencv-python实现摄像头图像捕获功能。
2 效果
手势识别效果如下面几张动态图所示。画面左侧显示摄像头实时捕获图像,显示静态手势识别结果。右侧显示5行15个模拟按键,选中状态为红低黄色边框,未选中为灰色底绿色边框。
2.1 向左移动效果
向左移动效果。
2.2 向右移动效果
向右移动效果。
2.3 左右移动效果
左右移动效果。
3 核心代码
3.1 工程项目结构
整个工程文件夹结构如下图所示。
3.2 编程思路
3.2.1 静态手势识别
通过
mediapipe.soulutions.hands
模块设置手势识别相关参数。
其原理在于将一张RGB图像传入手势识别网络后,结果输出手部关节点的坐标,有了各个关节点的坐标后,根据关节点坐标之间的相对数值、各个关节线段之间的角度等特征进行手势的分类。
mediapipe
具体是通过什么样的算法将每个关节点的坐标给预测出来的,这里就不详细展开讲了,现在已经有很多开源的手部关节点识别模型。
总之通过关节点的相对位置,能够实现诸如“1”、“2”……等常规简单意义手势分类如下所示。
3.2.2 动态手势识别
动态手势即在某一时间段内静态手势按照一定的顺序的排列。如果人为定义一段“动态手势”时间为2秒,使用帧率为30hz的相机进行拍摄,那么在该手势执行段共获得2×30=60张图片,这60张图片一定是按“某种规则”进行的排序。
人类做出手势,从每个时间节点,人手的关节点在这60张图像中的坐标位置来说,严格意义上来讲,一定不存在两次“完全一样”的手势,只能说当两次手势均满足一定的规则意义,才将它们判定为同一种手势。
这种判定规则,需要算法设计人员自行定义。
在本次实验中,本人的思路是,先让手做出静态手势“1”的效果,因为“1”的食指现对于图片的方向应该是在垂直状态的,这个垂直
状态可以通过理论计算判定,达到这种“垂直”状态后,然后通过判断食指上的三个点的坐标与整幅图像的相对位置,从而判断手指是“向左”还是“向右”移动。
即食指所在的直线与图像的水平线的角度,如果在某段时间内是
<75°
,那么将其判断为“向左移动”
,如果在某段时间内是>115°
,那么将其判断为“向右移动”
。这里为什么不用90°
,是由于你即使将手指指向垂直方向,理论计算值也不会那么准,一定是在90°
附近的某个区间内,这里选定了,当计算值在[75°, 115°]
时,均将手势判断为垂直状态。
3.3 HandpostureDetect模块
HandpostureDetect模块集成了手势识别相关的操作任务,包括关节点角度等的计算。
这里参考了此篇博客的内容
https://blog.csdn.net/weixin_45930948/article/details/115444916,本人在其基础上,将其原来的detect()
函数封装成HandPostureDetect
类,方便在模块化编程中调用。添加了if __name__ == '__main__':
代码入口,可以直接运行本脚本,调用opencv输出窗口,并在窗口上打印识别结果。
在HandpostureDetect内定义两个pyqtsignal分别用于输出静态手势与动态手势的状态。
import cv2
import mediapipe as mp
import math
import imutils
import numpy as np
from PyQt5.QtCore import pyqtSignal, QObject
def vector_2d_angle(v1,v2):
'''
求解二维向量的角度
'''
v1_x = v1[0]
v1_y = v1[1]
v2_x = v2[0]
v2_y = v2[1]
try:
angle_ = math.degrees(math.acos((v1_x*v2_x+v1_y*v2_y)/(((v1_x**2+v1_y**2)**0.5)*((v2_x**2+v2_y**2)**0.5))))
except:
angle_ = 65535.
if angle_ > 180.:
angle_ = 65535.
return angle_
def hand_angle(hand_):
'''
获取对应手相关向量的二维角度,根据角度确定手势
'''
angle_list = []
# ---------------------------- thumb 大拇指角度
angle_ = vector_2d_angle(
((int(hand_[0][0]) - int(hand_[2][0])), (int(hand_[0][1]) -int(hand_[2][1]))),
((int(hand_[3][0]) - int(hand_[4][0])), (int(hand_[3][1]) - int(hand_[4][1])))
)
angle_list.append(angle_)
# ---------------------------- index 食指角度
angle_ = vector_2d_angle(
((int(hand_[0][0]) - int(hand_[6][0])), (int(hand_[0][1]) - int(hand_[6][1]))),
((int(hand_[7][0]) - int(hand_[8][0])), (int(hand_[7][1]) - int(hand_[8][1])))
)
angle_list.append(angle_)
# ---------------------------- middle 中指角度
angle_ = vector_2d_angle(
((int(hand_[0][0])- int(hand_[10][0])),(int(hand_[0][1])- int(hand_[10][1]))),
((int(hand_[11][0])- int(hand_[12][0])),(int(hand_[11][1])- int(hand_[12][1])))
)
angle_list.append(angle_)
# ---------------------------- ring 无名指角度
angle_ = vector_2d_angle(
((int(hand_[0][0])- int(hand_[14][0])),(int(hand_[0][1])- int(hand_[14][1]))),
((int(hand_[15][0])- int(hand_[16][0])),(int(hand_[15][1])- int(hand_[16][1])))
)
angle_list.append(angle_)
# ---------------------------- pink 小拇指角度
angle_ = vector_2d_angle(
((int(hand_[0][0])- int(hand_[18][0])),(int(hand_[0][1])- int(hand_[18][1]))),
((int(hand_[19][0])- int(hand_[20][0])),(int(hand_[19][1])- int(hand_[20][1])))
)
angle_list.append(angle_)
return angle_list
def h_gesture(angle_list):
'''
# 二维约束的方法定义手势
# fist five gun love one six three thumbup yeah
'''
thr_angle = 65.
thr_angle_thumb = 53.
thr_angle_s = 49.
gesture_str = None
if 65535. not in angle_list:
if (angle_list[0]>thr_angle_thumb) and (angle_list[1]>thr_angle) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "握拳"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]<thr_angle_s) and (angle_list[2]<thr_angle_s) and (angle_list[3]<thr_angle_s) and (angle_list[4]<thr_angle_s):
gesture_str = "five"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]<thr_angle_s) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "eight"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]<thr_angle_s) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]<thr_angle_s):
gesture_str = "seven"
elif (angle_list[0]>5) and (angle_list[1]<thr_angle_s) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "one"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]>thr_angle) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]<thr_angle_s):
gesture_str = "six"
elif (angle_list[0]>thr_angle_thumb) and (angle_list[1]<thr_angle_s) and (angle_list[2]<thr_angle_s) and (angle_list[3]<thr_angle_s) and (angle_list[4]>thr_angle):
gesture_str = "three"
elif (angle_list[0]<thr_angle_s) and (angle_list[1]>thr_angle) and (angle_list[2]>thr_angle) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "thumbUp"
elif (angle_list[0]>thr_angle_thumb) and (angle_list[1]<thr_angle_s) and (angle_list[2]<thr_angle_s) and (angle_list[3]>thr_angle) and (angle_list[4]>thr_angle):
gesture_str = "two"
return gesture_str
def get_line_decline(two_points_line):
k = - (two_points_line[1][1]-two_points_line[0][1]) / (two_points_line[1][0]-two_points_line[0][0])
result = np.arctan(k) * 57.30
return result
class HandPostureDetect(QObject):
handSignal = pyqtSignal(int)
gestureSignal = pyqtSignal(str)
def __init__(self, parent=None):
super(HandPostureDetect, self).__init__(parent)
print("[INFO] initalizing Handposture Detector...")
self.mp_drawing = mp.solutions.drawing_utils
self.mp_hands = mp.solutions.hands
self.hands = self.mp_hands.Hands(static_image_mode=False,
max_num_hands=1,
min_detection_confidence=0.75,
min_tracking_confidence=0.75)
self.degree_list = []
def detect(self, frame):
try:
# frame = imutils.resize(frame, width=720)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame = cv2.flip(frame, 1)
results = self.hands.process(frame)
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
self.mp_drawing.draw_landmarks(frame, hand_landmarks, self.mp_hands.HAND_CONNECTIONS)
hand_local = []
for i in range(21):
x = hand_landmarks.landmark[i].x * frame.shape[1]
y = hand_landmarks.landmark[i].y * frame.shape[0]
hand_local.append((x, y))
# 判断动态手势
two_points_line = [hand_local[8], hand_local[7]]
degree = get_line_decline(two_points_line)
degree = round(degree)
self.degree_list.append(degree)
if len(self.degree_list) > 20:
del(self.degree_list[0])
for i in range(len(self.degree_list)):
if self.degree_list[i] < -75 or 75 < self.degree_list[i]:
self.degree_list[i] = 0
# print(self.degree_list)
count = 0
for i in self.degree_list:
if i < 0:
count += 1
count_1 = 0
for i in self.degree_list:
if i > 0:
count_1 += 1
if self.degree_list[0] == 0 and self.degree_list[1] == 0 and self.degree_list[2]!= 0 and count > 13:
self.handSignal.emit(1)
if self.degree_list[0] == 0 and self.degree_list[1] == 0 and self.degree_list[2]!= 0 and count_1 > 13:
self.handSignal.emit(2)
if hand_local:
angle_list = hand_angle(hand_local)
gesture_str = h_gesture(angle_list)
print(gesture_str)
self.gestureSignal.emit(gesture_str)
# 在图像上写出识别结果
cv2.putText(frame, gesture_str, (0, 100), 0, 1.3, (0, 0, 255), 3)
else:
gesture_str = ''
self.gestureSignal.emit(gesture_str)
except:
frame = frame
return frame
if __name__ == '__main__':
cap = cv2.VideoCapture(0)
A = HandPostureDetect()
while True:
_ret, frame = cap.read()
# frame = cv2.flip(frame, 1)
frame = A.detect(frame)
cv2.imshow('1', frame)
cv2.waitKey(1)
3.4 Widget.py
Widget模块用于定义最顶层的GUI界面相关内容,GUI显示也是模块化,将左侧的图像显示区域与右侧的算法结果验证区域分开。
from UI.ui_Widget import *
from Camera import *
from menu import *
from PyQt5.QtGui import QImage
from HandpostureDetect import HandPostureDetect # 手势监测算子
from audio import Voice
class Widget(QWidget, Ui_Widget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
# 实例化标题栏
self.m1 = Menu()
self.setupUi(self)
self.stackedWidget.addWidget(self.m1)
self.stackedWidget.setCurrentIndex(0)
# 初始化多个相机 # 设置打开摄像头的序号数值(opencv打开多摄像头必须按降序开)
self.camera_thread = Camera()
self.camera_thread.set_cam_number(0)
# 设置手势监测算子
self.camera_thread.needflip = 0
self.camera_thread.set_detector(HandPostureDetect)
self.camera_thread.detector.handSignal[int].connect(self.m1.set_index)
self.camera_thread.detector.gestureSignal[str].connect(self.show_hand_posture)
self.camera_thread.sendPicture[QImage].connect(self.receive)
self.camera_thread.detect_flag = 1
self.camera_thread.open_camera()
# 创建音频
self.voice = Voice('xxxxxx有限公司')
self.voice.start()
# 更新QLabel图像数据
def receive(self, img):
try:
img_height = self.label_3.height()
img_width = self.label_3.width()
# QImage.scaled若图像尺寸较大,会卡顿,暂时没有什么好方法
new_img = img.scaled(QSize(img_width, img_height))
self.label_3.setPixmap(QPixmap.fromImage(new_img))
except:
pass
def show_hand_posture(self, hand_posture):
self.label_5.setText(hand_posture)
self.label_5.setStyleSheet('QLabel{'
'font: 16pt bold "楷体";'
'font-weight:900;'
'text-align:center center;'
'color:white;'
'border:5px solid;'
'border-radius:10px;'
'border-color: rgb(0, 255, 127);'
'background-color: grey;/*定义最小高度和最小宽度*/'
'min-height: 60px;'
'min-width: 60px;}')
4.4 Menu.py
具体实现手势识别验证QLabel相关操作。实现代码请联系本人获取。
4 代码下载
请与本人私信联系。