Python | 基于Mediapipe框架的手势识别系统

一、项目要求

        1、题目

         本题着力于解决会商演示系统中的非接触式人机交互问题,具体而言,其核心问题就是通过计算机视觉技术实现对基于视频流的手势动作进行实时检测和识别。通过摄像头采集并识别控制者连续的手势动作,完成包括点击、平移、缩放、抓取、旋转等5种基本交互功能,除此之外还可针对不同客户的具体业务需求,可在这五种基本手势动作的基础上进行扩展。

        选手可利用传统计算机视觉方法或基于机器学习/深度学习的方法,通过对基于摄像头采集的连续视频输入中用户的手势动作进行检测和识别,输出相应的控制信号,从而完成会商演示系统的交互。结合业务需求,进行算法模型的开发,实现真实环境下对用户控制手势的识别,达到实时交互的目的。

        2、技术要求与指标

        能够实现对摄像头拍摄的视频流中控制手势进行检测和识别,并以此实时控制演示系统。指标要求:

        (1)每一种手势动作的检测识别准确率达到80%以上

        (2)每一个手势动作的检测和识别时间(即从执行完手势动作到输出结果之间的时间)不超过500ms

        一般开发环境以及开发语言不限(可使用Python+OpencCV,深度 学习框架可使用PyTorch、TensorFlow 等)。开发过程允许使用开源代码,但需要在文档中详细注明,且其许可证需保证商业可用,不能采用商用模块。

二、运行环境

        本系统能够运行在基于PC操作系统Windows环境下,要求Windows操作系统安装Python 3.9 及以上环境, 要求安装相关库OpenCV、Mediapipe、PyQt5、Qtawesome。

安装OpenCV —— python3.9安装OpenCV

安装Mediapipe —— Python安装Mediapipe

安装PyQt5 —— PyCharm安装PyQt5及其工具(Qt Designer、PyUIC、PyRcc)

安装Qtawesome —— Python安装Qtawesome

三、效果

        1、基本动作

                包括点击、抓取、平移、缩放、旋转5个基本动作

        2、扩展动作

                包括数字一、二、三、四、五、六、我爱你

四、代码

        1、项目结构

        2、项目代码

                (1)布局代码

# -*- coding: utf-8 -*-
import sys

import qtawesome
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon


class WindowLayout(object):
    def __init__(self):
        # 容器
        self.central_widget = None

        # 组件
        self.close_button = None
        self.other_button = None
        self.minimize_button = None

        self.start_button = None
        self.camera_button = None

        self.camera_label = None
        self.result_label = None
        self.running_label = None

    def setupUi(self, MainWindow):
        # 平台
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1250, 730)

        # ------------- 代码布局 ------------- #
        self.central_widget = QtWidgets.QWidget(MainWindow)
        self.central_widget.setObjectName("central_widget")
        MainWindow.setCentralWidget(self.central_widget) # 把容器放到平台上面

        # 关闭按钮
        self.close_button = QtWidgets.QPushButton(self.central_widget)
        self.close_button.setGeometry(QtCore.QRect(60, 60, 30, 30))
        self.close_button.setObjectName("close_button")
        # 空白按钮
        self.other_button = QtWidgets.QPushButton(self.central_widget)
        self.other_button.setGeometry(QtCore.QRect(120, 60, 30, 30))
        self.other_button.setObjectName("other_button")
        # 最小化按钮
        self.minimize_button = QtWidgets.QPushButton(self.central_widget)
        self.minimize_button.setGeometry(QtCore.QRect(180, 60, 30, 30))
        self.minimize_button.setObjectName("minimize_button")

        # 打开摄像头 按钮
        self.camera_button = QtWidgets.QPushButton(self.central_widget)
        self.camera_button.setIcon(qtawesome.icon('fa5s.video', color='white'))
        self.camera_button.setText(" 打开相机")
        self.camera_button.setGeometry(QtCore.QRect(60, 130, 150, 40))
        self.camera_button.setObjectName("camera_button")
        # 手势检测 按钮
        self.start_button = QtWidgets.QPushButton(self.central_widget)
        self.start_button.setIcon(qtawesome.icon('fa5s.eye', color='white'))
        self.start_button.setText(" 手势检测")
        self.start_button.setGeometry(QtCore.QRect(60, 190, 150, 40))
        self.start_button.setObjectName("start_button")

        # 检测结果展示部分
        self.result_label = QtWidgets.QLabel(self.central_widget)
        self.result_label.setText("结果")
        self.result_label.setGeometry(QtCore.QRect(50, 490, 170, 170))
        self.result_label.setObjectName("result_label")

        # 摄像头展示部分
        self.camera_label = QtWidgets.QLabel(self.central_widget)
        self.camera_label.setText("手势识别")
        self.camera_label.setGeometry(QtCore.QRect(300, 60, 900, 600))
        self.camera_label.setObjectName("camera_label")

        # 程序运行状态
        self.running_label = QtWidgets.QLabel(self.central_widget)
        self.running_label.setText("程序运行状态")
        self.running_label.setGeometry(QtCore.QRect(310, 665, 900, 40))
        self.running_label.setObjectName("running_label")

        # ------------- 界面美化 ------------- #
        self.central_widget.setStyleSheet('''
            QWidget#central_widget{
                border-radius:7px;
                border-image:url(background.png)};}
            ''')

        self.close_button.setStyleSheet('''
            QPushButton{
                background-color: rgba(247, 102, 119, 0.8);
                border-radius:8px;}
            QPushButton:hover{
                background-color: rgba(255, 0, 0, 0.7);}''')
        self.other_button.setStyleSheet('''
            QPushButton{
                background-color: rgba(250, 210, 116, 0.8);
                border-radius:8px;}
            QPushButton:hover{
                background-color: rgba(255, 255, 0, 0.8);}''')
        self.minimize_button.setStyleSheet('''
            QPushButton{
                background-color: rgba(50, 200, 50, 0.8);
                border-radius:8px;}
            QPushButton:hover{
                background-color: rgba(0, 250, 0, 0.8);}''')

        self.camera_button.setStyleSheet('''        
            QPushButton{
                border:none;
                color:white;
                font-size:15px;
                font-weight:bold;
                border-radius:8px;
                font-family:Roman times;
                background-color: rgba(200, 200, 200, 0.5);}
            QPushButton:hover{
                background-color: rgba(200, 200, 200, 0.6);}
        ''')

        self.start_button.setStyleSheet('''
            QPushButton{
                border:none;
                color:white;
                font-size:15px;
                font-weight:bold;
                border-radius:8px;
                font-family:Roman times;
                background-color: rgba(200, 200, 200, 0.4);}
            QPushButton:hover{
                background-color: rgba(200, 200, 200, 0.6);}
        ''')

        self.camera_label.setAlignment(Qt.AlignCenter)
        self.camera_label.setStyleSheet('''
                color:white;
                font-size:45px;
                font-weight:bold;
                font-family:Roman times;
                background-color: rgba(255, 255, 255, 0.3)
            ''')

        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setStyleSheet('''
                border-radius:5px;
                color:white;
                font-size:35px;
                font-weight:bold;
                font-family:Roman times;
                background-color: rgba(255, 255, 255, 0.3)
            ''')

        self.running_label.setStyleSheet('''
                color:white;
                font-size:16px;
                font-weight:bold;
                font-family:Roman times;
            ''')

        # 设置整体样式
        MainWindow.setWindowOpacity(1)  # 设置窗口透明度
        MainWindow.setAttribute(QtCore.Qt.WA_TranslucentBackground)  # 隐藏外围边框
        MainWindow.setWindowFlag(QtCore.Qt.FramelessWindowHint)  # 隐藏系统状态栏,并且生成的窗口用户不能移动和改变大小
        MainWindow.setWindowIcon(QIcon('Logo.ico'))  # 设置logo

        QtCore.QMetaObject.connectSlotsByName(MainWindow)



if __name__ == '__main__':
    # 创建一个Qt应用程序对象,用于管理应用程序的事件循环和窗口系统的交互。
    app = QtWidgets.QApplication(sys.argv)
    # 创建一个WindowLayout(自己写的类)对象,创建的时候自动进行初始化__init__
    windowLayout = WindowLayout()
    # 生成一个QtWidgets.QMainWindow对象,用于设置到 WindowLayout.setupUi() 方法中
    mainWindow = QtWidgets.QMainWindow()
    # 调用 WindowLayout.setupUi() 方法,将QtWidgets.QMainWindow对象作为参数传入
    windowLayout.setupUi(mainWindow)
    # 调用 QWidget.setupUi() 方法,展示界面
    mainWindow.show()
    # 调用系统方法进行界面关闭
    sys.exit(app.exec_())

                (2)逻辑代码

# -*- coding: utf-8 -*-
import math
import sys
from time import time

import cv2
import mediapipe as mp
from PyQt5.QtGui import *
from PyQt5.QtGui import QImage
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
from PyQt5.QtWidgets import QDesktopWidget

from window_layout import WindowLayout


class WindowLogic(QMainWindow, WindowLayout):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        # ====== UI逻辑属性 ====== #
        self.close_button.clicked.connect(self.close_window)            # 关闭窗口按钮
        self.minimize_button.clicked.connect(self.showMinimized)        # 最小化窗口按钮
        self.camera_button.clicked.connect(self.camera_judgement)       # 打开相机按钮
        self.start_button.clicked.connect(self.recognition_judgement)   # 手势识别按钮

        # ====== 相机属性 ====== #
        self.cap = cv2.VideoCapture()   # 相机
        self.source = 0                 # 相机标号
        self.WIN_WIDTH = 900    # 相机展示画面的宽度
        self.WIN_HEIGHT = 600   # 相机展示画面的高度

        # ====== 手势识别属性 ====== #
        self.sole_hand_name = ''            # 单一手势名称
        self.sole_hand_landmarks = []       # 单一手势坐标
        self.current_frame_name = []        # 当前帧中手势名称
        self.current_frame_landmarks = []   # 当前帧中手势坐标
        self.current_frame_msg = []         # 当前帧信息
        self.stream_frame_msg = []          # 视频流前30帧信息
        self.INF = 65535.                   # 角度错误值
        self.normal_unbend_angle = 49.      # 正常手指伸直阈值
        self.normal_threshold_angle = 65.   # 正常手指弯曲阈值
        self.thumb_threshold_angle = 53.    # 大拇指弯曲阈值
        self.isGestureRecognition_flag = False  # 是否打开手势识别标志
        self.resultRetainFrame = 0

        # ====== 手势控制属性 ====== #
        self.desktop = QDesktopWidget()
        self.screen_rect = self.desktop.screenGeometry()
        self.screen_width = self.screen_rect.width()
        self.screen_height = self.screen_rect.height()
        self.x = None
        self.y = None

        # ====== 时间属性 ====== #
        self.camera_start_time = None
        self.recognize_start_time = None

    # ================================== 打开相机 ================================== #
    def camera_judgement(self):
        """ 打开相机按钮 逻辑判断器 """
        # 打开摄像头
        if not self.cap.isOpened():
            self.running_label.setText(u"正在打开相机,请稍等...")
            QApplication.processEvents()
            self.camera_start_time = time()
            self.cap.open(self.source)
            self.running_label.setText("相机模块初始化时间 : {:.3f}".format(time() - self.camera_start_time) + 's')
            try:
                self.camera_button.setText(u' 关闭相机')
                self.show_camera()
            except:
                QMessageBox.about(self, '警告', '相机不能正常被打开')
        # 关闭摄像头,释放cap
        else:
            if self.isGestureRecognition_flag:
                QMessageBox.about(self, '提示', '请先关闭手势识别模块')
            else:
                self.cap.release()
                self.camera_button.setText(u' 打开相机')
                self.start_button.setText(u' 手势检测')
                self.running_label.setText(u'已关闭相机模块')

    def show_camera(self):
        """ 展示摄像头画面 """
        while self.cap.isOpened():
            ret, frame = self.cap.read()
            QApplication.processEvents()
            show = cv2.resize(frame, (self.WIN_WIDTH, self.WIN_HEIGHT))
            show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)
            show_image = QImage(show.data, show.shape[1], show.shape[0], QImage.Format_RGB888)
            self.camera_label.setPixmap(QPixmap.fromImage(show_image))
        self.camera_label.setPixmap(QPixmap(""))

    # ================================== 手势识别 ================================== #
    def recognition_judgement(self):
        """ 手势识别按钮 逻辑判断器 """
        if not self.cap.isOpened():
            QMessageBox.about(self, '提示', '请先打开摄像头')
        else:
            if not self.isGestureRecognition_flag:
                self.isGestureRecognition_flag = True
                self.start_button.setText(u' 关闭检测')
                self.gesture_recognize()
                self.isGestureRecognition_flag = False
                self.start_button.setText(u' 手势检测')
            else:
                self.isGestureRecognition_flag = False
                self.start_button.setText(u' 手势检测')
                self.running_label.setText(u'已关闭检测模块')
                self.result_label.setText("结果")

    def gesture_recognize(self):
        """ 手势识别 """
        recognize_start_time = time()
        self.running_label.setText(u"正在打开手势识别模块,请稍等...")
        QApplication.processEvents()
        mp_drawing = mp.solutions.drawing_utils
        mp_hands = mp.solutions.hands
        hands = mp_hands.Hands(static_image_mode=False, max_num_hands=2,
                               min_detection_confidence=0.75,
                               min_tracking_confidence=0.75)
        self.stream_frame_msg = []
        self.running_label.setText("检测模块初始化时间 : {:.3f}".format(time() - recognize_start_time) + 's')

        while self.isGestureRecognition_flag:
            self.recognize_start_time = time()
            QApplication.processEvents()
            ret, frame = self.cap.read()
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            result = hands.process(frame)
            frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)  # 颜色空间的转换

            self.current_frame_name = []
            self.current_frame_landmarks = []


            if result.multi_handedness:
                hand_num = len(result.multi_handedness)

                # 一只手
                if hand_num == 1:
                    if result.multi_hand_landmarks:

                        # 跑1次 for 循环
                        for hand_landmarks in result.multi_hand_landmarks:
                            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

                            self.sole_hand_name = ''
                            self.sole_hand_landmarks = []
                            for i in range(21):
                                x = hand_landmarks.landmark[i].x * frame.shape[1]
                                y = hand_landmarks.landmark[i].y * frame.shape[0]
                                self.sole_hand_landmarks.append((x, y))

                            if self.sole_hand_landmarks:
                                angle_list = self.get_hand_angle(self.sole_hand_landmarks)
                                self.sole_hand_name = self.recognize_static_gesture(angle_list)

                        self.current_frame_landmarks.append(self.sole_hand_landmarks)
                        self.current_frame_landmarks.append([])

                        self.current_frame_name.append(self.sole_hand_name)
                        self.current_frame_name.append('null')
                # 两只手
                elif hand_num == 2:
                    if result.multi_hand_landmarks:
                        # 跑2次 for 循环
                        for hand_landmarks in result.multi_hand_landmarks:
                            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

                            self.sole_hand_name = ''
                            self.sole_hand_landmarks = []

                            for i in range(21):
                                x = hand_landmarks.landmark[i].x * frame.shape[1]
                                y = hand_landmarks.landmark[i].y * frame.shape[0]
                                self.sole_hand_landmarks.append((x, y))

                            if self.sole_hand_landmarks:
                                angle_list = self.get_hand_angle(self.sole_hand_landmarks)
                                self.sole_hand_name = self.recognize_static_gesture(angle_list)

                            self.current_frame_landmarks.append(self.sole_hand_landmarks)
                            self.current_frame_name.append(self.sole_hand_name)

                    # 将当前手势的坐标、名称保存到 current_frame_msg 中
                self.current_frame_msg = [self.current_frame_landmarks, self.current_frame_name]
                # 将当前帧信息保存到 stream_frame_msg 中
                self.stream_frame_msg.append(self.current_frame_msg)

                self.recognize_dynamic_gesture()

                if len(self.stream_frame_msg) > 80:
                    j = self.stream_frame_msg[-40:-1]
                    j.append(self.stream_frame_msg[-1])
                    self.stream_frame_msg = j

            show_video = cv2.cvtColor(cv2.resize(frame, (self.WIN_WIDTH, self.WIN_HEIGHT)), cv2.COLOR_BGR2RGB)
            show_image = QImage(show_video.data, show_video.shape[1], show_video.shape[0], QImage.Format_RGB888)
            self.camera_label.setPixmap(QPixmap.fromImage(show_image))

        self.show_camera()

    def recognize_dynamic_gesture(self):
        """ 识别动态手势 """
        if len(self.stream_frame_msg) > 15:
            # 抓取
            if self.one_hand_judge_fist(-1) and not self.one_hand_judge_fist(-5):
                self.show_label('抓取/零')
            # 点击
            elif self.one_hand_judge_point(-1) and not self.one_hand_judge_point(-5):
                self.show_label('点击/一')
            # 平移
            elif self.one_hand_judge_spread(-1) and self.one_hand_judge_spread(-5) and self.one_hand_judge_spread(-10):
                if math.fabs(self.count_one_hand_delta_x(-1, -10)) >= 180 or \
                        math.fabs(self.count_one_hand_delta_x(-1, -5)) >= 90:
                    self.show_label('平移')
            elif self.two_hand_judge_spread(-1) and self.two_hand_judge_spread(-5) and self.two_hand_judge_spread(-10):
                # 缩放
                if (self.count_two_hand_delta_x(-1,-10,0) <= -50 and self.count_two_hand_delta_x(-1,-10,1) >= 50
                    and math.fabs(self.count_two_hand_delta_y(-1, -10, 0)) <= 20 and math.fabs(self.count_two_hand_delta_y(-1, -10, 1)) <= 20) or \
                        (self.count_two_hand_delta_x(-1,-10,0) >= 50 and self.count_two_hand_delta_x(-1,-10,1) <= -50
                         and math.fabs(self.count_two_hand_delta_y(-1, -10, 0)) <= 20 and math.fabs(self.count_two_hand_delta_y(-1, -10, 1)) <= 20):
                    self.show_label('缩放')
                # 旋转
                elif math.fabs(self.count_two_hand_delta_y(-1, -10, 0)) >= 50 \
                        and math.fabs(self.count_two_hand_delta_x(-1, -10, 0)) >= 50 \
                        and math.fabs(self.count_two_hand_delta_y(-1, -10, 1)) >= 50 \
                        and math.fabs(self.count_two_hand_delta_x(-1, -10, 1)) >= 50:
                    self.show_label('旋转')
            # I love Y
            elif self.two_hand_judge_thumbUp(-1) and self.two_hand_judge_thumbUp(-5) and self.two_hand_judge_thumbUp(-10):
                self.show_label('我爱你')
            # 手势 二
            elif self.one_hand_judge_two(-1) and not self.one_hand_judge_two(-5):
                self.show_label("二")
            # 手势 三
            elif self.one_hand_judge_three(-1) and not self.one_hand_judge_three(-5):
                self.show_label("三")
            # 手势 四
            elif self.one_hand_judge_four(-1) and not self.one_hand_judge_four(-5):
                self.show_label("四")
            # 手势 五
            elif self.one_hand_judge_spread(-1) and not self.one_hand_judge_spread(-5):
                self.show_label("五")
            # 手势 六
            elif self.one_hand_judge_six(-1) and not self.one_hand_judge_six(-5):
                self.show_label("六")

    def recognize_static_gesture(self, angle_list):
        """
        识别静态手势
        :param angle_list: 手势弯曲角度列表
        :return: 静态手势类型
        """
        gesture_type = None
        if self.INF not in angle_list:
            if (angle_list[0] > self.thumb_threshold_angle) and (angle_list[1] > self.normal_threshold_angle) \
                    and (angle_list[2] > self.normal_threshold_angle) and (angle_list[3] > self.normal_threshold_angle) \
                    and (angle_list[4] > self.normal_threshold_angle):
                gesture_type = "fist"

            elif (angle_list[0] < self.normal_unbend_angle) and (angle_list[1] < self.normal_unbend_angle) \
                    and (angle_list[2] < self.normal_unbend_angle) and (angle_list[3] < self.normal_unbend_angle) and \
                    (angle_list[4] < self.normal_unbend_angle):
                gesture_type = "spread"

            elif (angle_list[0] > 5) and (angle_list[1] < self.normal_unbend_angle) \
                    and (angle_list[2] > self.normal_threshold_angle) and (angle_list[3] > self.normal_threshold_angle) \
                    and (angle_list[4] > self.normal_threshold_angle):
                gesture_type = "point"

            elif (angle_list[0] > self.thumb_threshold_angle) and (angle_list[1] < self.normal_unbend_angle) and (
                    angle_list[2] < self.normal_unbend_angle) and (angle_list[3] > self.normal_threshold_angle) and (
                    angle_list[4] > self.normal_threshold_angle):
                gesture_type = "two"

            elif (angle_list[0] > self.thumb_threshold_angle) and (angle_list[1] < self.normal_unbend_angle) and (
                    angle_list[2] < self.normal_unbend_angle) and (angle_list[3] < self.normal_unbend_angle) and (
                    angle_list[4] > self.normal_threshold_angle):
                gesture_type = "three"

            elif (angle_list[0] > self.thumb_threshold_angle) and (angle_list[1] < self.normal_unbend_angle) and (
                    angle_list[2] < self.normal_unbend_angle) and (angle_list[3] < self.normal_unbend_angle) and (
                    angle_list[4] < self.normal_threshold_angle):
                gesture_type = "four"

            elif (angle_list[0] < self.normal_unbend_angle) and (angle_list[1] > self.normal_threshold_angle) and (
                    angle_list[2] > self.normal_threshold_angle) and (angle_list[3] > self.normal_threshold_angle) and (
                    angle_list[4] < self.normal_unbend_angle):
                gesture_type = "six"
            elif (angle_list[0] < self.normal_unbend_angle) and (angle_list[1] > self.normal_threshold_angle) and (
                    angle_list[2] > self.normal_threshold_angle) and (angle_list[3] > self.normal_threshold_angle) and (
                    angle_list[4] > self.normal_threshold_angle):
                gesture_type = "thumbUp"

        return gesture_type

    def get_hand_angle(self, coordinate):
        """
        获取当前手势弯曲角度
        :param coordinate: 手势弯曲角度坐标
        :return: 手势弯曲角度列表
        """
        angle_list = []
        # 大拇指角度
        angle = self.compute_hand_angle(
            ((int(coordinate[0][0]) - int(coordinate[2][0])),(int(coordinate[0][1]) - int(coordinate[2][1]))),
            ((int(coordinate[3][0]) - int(coordinate[4][0])),(int(coordinate[3][1]) - int(coordinate[4][1]))))
        angle_list.append(angle)

        # 食指角度
        angle = self.compute_hand_angle(
            ((int(coordinate[0][0]) - int(coordinate[6][0])),(int(coordinate[0][1]) - int(coordinate[6][1]))),
            ((int(coordinate[7][0]) - int(coordinate[8][0])),(int(coordinate[7][1]) - int(coordinate[8][1]))))
        angle_list.append(angle)

        # 中指角度
        angle = self.compute_hand_angle(
            ((int(coordinate[0][0]) - int(coordinate[10][0])),(int(coordinate[0][1]) - int(coordinate[10][1]))),
            ((int(coordinate[11][0]) - int(coordinate[12][0])),(int(coordinate[11][1]) - int(coordinate[12][1]))))
        angle_list.append(angle)

        # 无名指角度
        angle = self.compute_hand_angle(
            ((int(coordinate[0][0]) - int(coordinate[14][0])),(int(coordinate[0][1]) - int(coordinate[14][1]))),
            ((int(coordinate[15][0]) - int(coordinate[16][0])),(int(coordinate[15][1]) - int(coordinate[16][1]))))
        angle_list.append(angle)

        # 小拇指角度
        angle = self.compute_hand_angle(
            ((int(coordinate[0][0]) - int(coordinate[18][0])),(int(coordinate[0][1]) - int(coordinate[18][1]))),
            ((int(coordinate[19][0]) - int(coordinate[20][0])),(int(coordinate[19][1]) - int(coordinate[20][1]))))
        angle_list.append(angle)
        return angle_list

    def compute_hand_angle(self, A, B):
        """
        计算指定手指弯曲角度
        :param A: 向量端点A
        :param B: 向量端点B
        :return: 向量AB
        """
        ax, ay = A[0], A[1]
        bx, by = B[0], B[1]
        try:
            angle = math.degrees(math.acos((ax * bx + ay * by) / (((ax ** 2 + ay ** 2) ** 0.5) * ((bx ** 2 + by ** 2) ** 0.5))))
        except:
            angle = self.INF

        if angle > 180.:
            angle = self.INF
        return angle

    def one_hand_judge_fist(self, i):
        return self.stream_frame_msg[i][1][0] == 'fist' and self.stream_frame_msg[i][1][1] == 'null'

    def one_hand_judge_spread(self, i):
        return self.stream_frame_msg[i][1][0] == 'spread' and self.stream_frame_msg[i][1][1] == 'null'

    def one_hand_judge_point(self, i):
        return self.stream_frame_msg[i][1][0] == 'point' and self.stream_frame_msg[i][1][1] == 'null'

    def two_hand_judge_fist(self, i):
        return self.stream_frame_msg[i][1][0] == 'fist' and self.stream_frame_msg[i][1][1] == 'fist'

    def two_hand_judge_spread(self, i):
        return self.stream_frame_msg[i][1][0] == 'spread' and self.stream_frame_msg[i][1][1] == 'spread'

    def two_hand_judge_thumbUp(self, i):
        return self.stream_frame_msg[i][1][0] == 'thumbUp' and self.stream_frame_msg[i][1][1] == 'thumbUp'

    def one_hand_judge_two(self, i):
        return self.stream_frame_msg[i][1][0] == 'two' and self.stream_frame_msg[i][1][1] == 'null'

    def one_hand_judge_three(self, i):
        return self.stream_frame_msg[i][1][0] == 'three' and self.stream_frame_msg[i][1][1] == 'null'

    def one_hand_judge_four(self, i):
        return self.stream_frame_msg[i][1][0] == 'four' and self.stream_frame_msg[i][1][1] == 'null'

    def one_hand_judge_six(self, i):
        return self.stream_frame_msg[i][1][0] == 'six' and self.stream_frame_msg[i][1][1] == 'null'

    def count_one_hand_delta_x(self, i, j):
        """
        组合计算 - 计算一只手横坐标偏移量
        :param i: 起始手势帧数
        :param j: 终止手势帧数
        :return: 手势关键点水平偏移量 delta_x
        """
        return self.stream_frame_msg[i][0][0][0][0] - self.stream_frame_msg[j][0][0][0][0]

    def count_two_hand_delta_x(self, i, j, hand):
        """
        组合计算 - 计算 两只手中指定手 横坐标偏移量
        :param i: 起始手势帧数
        :param j: 终止手势帧数
        :param hand: 指定手编号
        :return: 指定手势关键点水平偏移量 delta_x
        """
        return self.stream_frame_msg[i][0][hand][0][0] - self.stream_frame_msg[j][0][hand][0][0]

    def count_two_hand_delta_y(self, i, j, hand):
        """
        组合计算 - 计算 两只手中指定手 纵坐标偏移量
        :param i: 起始手势帧数
        :param j: 终止手势帧数
        :param hand: 指定手编号
        :return: 指定手势关键点垂直偏移量 delta_y
        """
        return self.stream_frame_msg[i][0][hand][0][1] - self.stream_frame_msg[j][0][hand][0][1]

    def show_label(self,text):
        """
        打印结果
        :param text: 展示到结果区域的文字
        """
        self.result_label.setText(text)
        self.running_label.setText(u"手势检测中...")

    # ================================== 页面控制 ================================== #
    def close_window(self):
        """ 关闭窗口 """
        if self.cap.isOpened():
            self.cap.release()
        self.close()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = WindowLogic()
    win.show()
    sys.exit(app.exec_())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/300227.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于无人机上层控制的PID算法的思考

一、前言 背景介绍&#xff1a;PID虽然出现了很多年&#xff0c;但是目前工业界还是把PID作为主流的控制算法&#xff08;尽管学术界有很多非常时尚的控制算法&#xff0c;包括鲁邦控制&#xff0c;神经网络控制等等&#xff09;&#xff0c;PID的算法在于其不需要对系统进行复…

编程语言的生命力

一、目前主流的编程语言 目前流行的编程语言有很多种&#xff0c;可谓是百花齐放、百家争鸣。根据不同的应用场景和领域&#xff0c;有不同的编程语言被广泛使用。一些目前主流的编程语言HTML5、Python、JavaScript 、Java 、C 、PHP 、Swift 等等。 还有许多其他的编程语言&am…

leetcode算法题之递归--综合练习(二)

本章目录 1.N皇后2.有效的数独3.解数独4.单词搜索5.黄金矿工6.不同路径III 1.N皇后 N皇后 class Solution {vector<vector<string>> ret;vector<string> path;int n;bool checkCol[10],checkDig1[20],checkDig2[20]; public:vector<vector<string&g…

1018:奇数偶数和1028:I love 闰年!和1029:三角形判定

1018&#xff1a;奇数偶数 要求&#xff1a;输入一个整数&#xff0c;判断该数是奇数还是偶数。如果该数是奇数就输出“odd”&#xff0c;偶数就输出“even”&#xff08;输出不含双引号&#xff09;。 输入样例&#xff1a;8 输出样例&#xff1a;even 程序流程图&#xff1a…

解决Canvas画图清晰度问题

最近在开发Web端远程桌面的时候遇到的一个问题&#xff0c;解决记录一下&#xff0c;分享给各位有需要用到的朋友。 先吹下水&#xff1a;远程桌面的连接我们是通过Websocket连接后&#xff0c;后端不断返回远程端的界面二进制数据流&#xff0c;我接收到之后转为图像&#xf…

MySQL练习-DDL语法练习

文章目录 1、数据库操作2、表操作3、DDL数据类型 突然想起来好久没写过SQL了&#xff0c;写一下SQL练习一下&#x1f60a; 个人写sql比较喜欢用小写&#x1f601; 什么是DDL&#xff1a;DDL是对数据库和表的操作 在这里练习DLL的时候先不添加约束&#xff0c;后面会把约束集中…

YOLOv8模型yaml结构图理解(逐层分析)

前言 YOLO-V8&#xff08;官网地址&#xff09;&#xff1a;https://github.com/ultralytics/ultralytics 一、yolov8配置yaml文件 YOLOv8的配置文件定义了模型的关键参数和结构&#xff0c;包括类别数、模型尺寸、骨架&#xff08;backbone&#xff09;和头部&#xff08;hea…

迟到的总结:回望 2023 年,期盼 2024 新机会、新挑战

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、RocketMQ&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏…

2023湾区产城创新大会:培育数字化供应链金融新时代

2023年12月26日&#xff0c;由南方报业传媒集团指导&#xff0c;南方报业传媒集团深圳分社主办的“新质新力——2023湾区产城创新大会”在深圳举行。大会聚集里国内产城研究领域的专家学者以及来自产业园区、金融机构、企业的代表&#xff0c;以新兴产业发展为议题&#xff0c;…

Proteus 各版本安装指南

Proteus下载链接 https://pan.baidu.com/s/1vHgg8jK9KSHdxSU9SDy4vQ?pwd0531 1.鼠标右击【Proteus8.15(64bit&#xff09;】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到Proteus8.15(64bit&#xff09; 】。 2.打开解压后的文件夹&#…

FastDFS安装与测试

目录 目标 版本 环境 官方文档 相关概念 安装FastDFS 启动FastDFS 关闭FastDFS 重启FastDFS 用命令测试上传文件 用命令测试下载文件 用命令测试删除文件 用HTTP的方式访问FastDFS中的文件 用HTTP的方式访问FastDFS中的文件整体流程 目标 在Linux服务器上搭建单…

遇见未来的你——感谢你带给我的感悟

目录 一、背景介绍二、思路&方案三、过程1.都说有的人出生就在罗马而有的人却用一辈子都在去向罗马的路上1.1.物质&#xff1a;1.2.精神&#xff1a; 2.做事情要看大再看细3.心存善念&#xff0c;常怀感恩&#xff0c;从小事做起4.所谓的面子在母爱面前像是一粒微尘5.讲道理…

React 中条件渲染的 N 种方法

本文作者系360奇舞团前端开发工程师 条件渲染在React开发中非常重要的功能&#xff0c;它允许开发人员根据条件控制渲染的内容&#xff0c;在创建动态和交互式用户界面方面发挥着至关重要的作用&#xff0c;本文总结了常用的的条件渲染方法。 1.If-else if-else是一种控制流程的…

qt自定义控件的封装

刚学了一个很有意思的东西,前面学了list,Tree,Table三大控件和一部分常用基础控件,但感觉没啥意思,就是用别人的直接用,刚学了一个自定义控件的封装,流程如下: 想把两个不相关的组件封装在一块,直接用ui不行,所以先新添加了qt设计师页面,新添加了一个SmallWidget *ui 在smal…

SLURM作业管理系统之3种作业提交方式

文章目录 前言定义基本概念三种作业提交模式1. 批处理作业&#xff08;采用 sbatch 命令提交&#xff09;2. 交互式作业提交&#xff08;采用 srun 命令提交&#xff09;3. 分配模式作业&#xff08;采用 salloc 命令提交&#xff09; 管理节点部署Slurm常用命令 前言 在高性能…

unity 游戏开发中傻傻分不清URP、HDRP和SRP

文章目录 **URP (Universal Render Pipeline)**:**HDRP (High Definition Render Pipeline)**:**区别**&#xff1a; Unity的URP&#xff08;Universal Render Pipeline&#xff09;和HDRP&#xff08;High Definition Render Pipeline&#xff09;都是基于SRP&#xff08;Scri…

k8s yaml文件pod的生命周期

Pod是k8s中最小限额资源管理组件&#xff0c;也是最小化运行容器化的应用的资源管理对象。 Pod是一个抽象的概念&#xff0c;可以理解为一个或者多个容器化应用的集合。 在一个pod当中运行一个容器是最常用的方式。 在一个pod当中同时运行多个容器&#xff0c;在一个pod当中…

2024阿里云服务器可用区选择方法

阿里云服务器地域和可用区怎么选择&#xff1f;地域是指云服务器所在物理数据中心的位置&#xff0c;地域选择就近选择&#xff0c;访客距离地域所在城市越近网络延迟越低&#xff0c;速度就越快&#xff1b;可用区是指同一个地域下&#xff0c;网络和电力相互独立的区域&#…

Strict MIME type checking is enforced for module scripts per HTML spec.

目录 前言错误信息如下:前言 最近使用docker打包Nginx和vue 为镜像文件,启动镜像时报错 错误信息如下: index89886.js:1 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Stri…

labelme的安装

首先尝试在(openmmlab)的python3.8的环境下安装&#xff08;失败&#xff09;。应该是我环境其他部分不对&#xff0c;和python版本应该没什么关系。&#xff08;后续&#xff0c;创建新的环境后成功&#xff0c;可直接看最后一部分。&#xff09; 首先安装是没问题的 pip in…