Pyqt+mediapipe python实现动态手势,摇动手指识别控制QLabel的选中状态

5 篇文章 1 订阅


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 代码下载

请与本人私信联系。

  • 10
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_chao118

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值