基于mediapipe和opencv的手势控制电脑鼠标

通过我的上一篇文章,可以了解到mediapipe关于手部检测的使用方法。这时我们就可以进行一些更加炫酷的操作。这篇文章我就来讲解一下如何用手势来控制电脑鼠标。

在开始之前我们要介绍一个能够操作电脑鼠标的库pyautogui,这里我简单介绍一下该库的一些函数,方便大家观看最后的源码

函数名 作用
pyautogui.size() 获取屏幕的分辨率,返回值为width,height
pyautogui.click(x,y,button = ‘left/right’) 在屏幕的(x,y)处进行左键或右键的点击操作
pyautogui.doubleClick(x,y) 在屏幕的(x,y)处双击左键
pyautogui.moveTo(x, y, duration=0) 将鼠标移动到指定的(x,y)处;duration 的作用是设置移动时间,是可选参数
pyautogui.FAILSAFE =True 默认这项功能为True, 这项功能意味着:当鼠标在屏幕的最左上角,程序会报错;目的是防止程序一直控制鼠标导致程序无法结束

由于mediapipe的使用方法在上一篇文章中已经介绍过了,这里就不再重复介绍了。如果小伙伴不知道mediapipe是什么的话,点击这里,看我的上一篇文章

思路

首先使用opencv调用摄像头,从摄像头读取图像,将图像反转(摄像头读取的图像与现实中是相反的),并将图像转换为RGB模式。接着使用mediapipe进行手部检测,并用列表存储手部的21个关键点的坐标。利用该列表进行检测:
        当手指食指小于160度的时候认定鼠标点击左键
        当手指中食小于160度的时候认定鼠标点击右键
        当手指食指和中值之间的距离小于40的时候认定鼠标点双击左键
        (如果不知道如何检测手指角度数,请看我的上一篇文章)
然后我用手部关键点的0和9的坐标的中值来代替鼠标,大概就在下图我标注的绿色点的位置
在这里插入图片描述

实现过程的难点

这里有两个难点:第一,一开始的时候我获取到屏幕的分辨率(width,height),然后再在获取的每一帧的图像的大小(x,y),接着获取比例关系(ratio_x=width/x,ratio_y=height/y),当得到如上图的绿色点的坐标乘上比例关系就获得了屏幕鼠标应该在的位置。这样做看起来很好,但在实践的过程中有很大的问题,因为你会发现屏幕上的鼠标会到达不了屏幕的边缘区域(你可以亲自测试一下)。
第二,如果手指悬停在某一点处,电脑上的鼠标会一直晃动。因为你的手指会一直抖动,做不到完全静止,这细微的变化乘上比例,就会被放大,这样就会造成看似手指悬停,但是屏幕上的鼠标会抖动的原因。

解决办法:

第一:在网上看见了numpy.interp(),这是一个一维的插值函数(该函数的参数,这里就不讲解了,在最后的源代码中我会做注释讲解的),下图时插值函数的效果图,图上的x,y轴代表了图像和屏幕的大小。虽然这里的插值和我使用的比例方法可以都看作是线性的直线方程,但是还是插值函数效果更好。这令我很困惑。
在这里插入图片描述
第二:对于手指悬停,鼠标一直抖动的问题。我们可以先记录一下这次屏幕上鼠标的坐标(pre_x, pre_y),等得到下一次鼠标坐标(mouse_x,mouse_y)的时候,
进行计算x = (mouse_x - pre_x)/ smooth 和 y =(mouse_y - pre_y)/ smooth
这里的(mouse_x - pre_x)和(mouse_y - pre_y)相当与计算出了这两次鼠标的距离,除以smooth
相当于缩短了距离,然后用(pre_x + x, pre_y + y)用于当前鼠标的坐标位置,然后更新
pre_x = pre_x + x, pre_y = pre_y + y。这样就缩减了前后两次鼠标的距离,来防止鼠标的抖动,且更新了鼠标的位置。
这里要注意(mouse_x - pre_x)和 (mouse_y - pre_y)的相减顺序,以及smooth一定要大于1(小于1相当于扩大了距离),当smooth的值越大时,鼠标移动会变慢,鼠标会稳定;值越小,鼠标移动就会变快,鼠标会抖动。

完整代码;

import pyautogui
import time
import cv2
import numpy as np
import math
import mediapipe as mp

# 获取手指的坐标,全部获取存入列表中并返回该列表
def finger_coordinate(hand):
    finger = []
    for handlms in hand.multi_hand_landmarks:
        for lm in handlms.landmark:
            x, y = int(lm.x * cap_x), int(lm.y * cap_y)
            finger.append([x, y])
        #mpdraw.draw_landmarks(frame, handlms, mphand.HAND_CONNECTIONS)
    return finger

#判断手指的弯曲度数以及食指和中值的距离,将判断的结果存入judge_finfer列表中,
#如果食指弯曲则judge_finger[0] = 1,中指弯曲则judge_finger[1] = 1,如果两手指的距离小于40则judge_finger[2] = 1
#最后返回judge_finger
def check_finger(finger):
    judge_finger = [0,0,0]
    for i,id in enumerate([5,9]):
        a = round(math.hypot(finger[id][0]-finger[id+1][0],finger[id][1]-finger[id+1][1]),2)
        b = round(math.hypot(finger[id+1][0]-finger[id+2][0],finger[id+1][1]-finger[id+2][1]),2)
        c = round(math.hypot(finger[id][0]-finger[id+2][0],finger[id][1]-finger[id+2][1]),2)
        try:
            angle = math.acos((a**2+b**2-c**2)/(2*a*b))*57
        except ValueError:
            angle = 180
        except ZeroDivisionError:
            angel = 0
        if angle < 160:
            judge_finger[i] = 1

    dist = math.hypot(finger[8][0]-finger[12][0],finger[8][1]-finger[12][1])
    cv2.circle(frame, finger[8], 25, (255, 0, 255), -1, )
    cv2.circle(frame, finger[12], 25, (255, 0, 255), -1, )
    if dist < 40:
        judge_finger[2] = 1
    return judge_finger


########################
#变量设置区域
offset_x = 60
offset_y = 150

smooth = 5
pre_x = 0
pre_y = 0
########################

########################
#初始化区域
cap = cv2.VideoCapture(0)

cap_x = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
cap_y = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
screen_x,screen_y = pyautogui.size()

mphand = mp.solutions.hands
hands = mphand.Hands()
mpdraw = mp.solutions.drawing_utils
########################


while cap.isOpened():
    #从摄像头中获取图像,并将图像反转,转换为RGB模式
    _,frame = cap.read()
    frame = cv2.flip(frame, 1)
    img = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
    cv2.rectangle(frame, (offset_x, offset_y), (cap_x - offset_x, cap_y), (0, 255, 0), 2)

    #进行手势的检测,返回关于手势的检测值
    hand = hands.process(img)
    #判断是否检测到手部
    if hand.multi_hand_landmarks:
        #检测手指的坐标值,并返回列表
        finger = finger_coordinate(hand)

        #1.确定鼠标的坐标

        #以编号0和9的中值为鼠标点
        x,y = (finger[0][0]+finger[9][0])//2,(finger[0][1]+finger[9][1])//2
        cv2.circle(frame, (x,y), 25, (255, 0, 255), -1)
        #使用插值法进行图像点到屏幕鼠标点的映射
        mouse_x = np.interp(x,(offset_x,cap_x-offset_x),(0,screen_x))
        mouse_y = np.interp(y,(offset_y,cap_y),(0,screen_y))
        #平滑鼠标坐标
        mouse_x = pre_x + (mouse_x-pre_x) / smooth
        mouse_y = pre_y + (mouse_y-pre_y) / smooth
        pre_x = mouse_x
        pre_y = mouse_y
        #将确定的鼠标点进行在屏幕上移动
        pyautogui.moveTo(mouse_x, mouse_y, duration=0)

        #2.检测手指来确定鼠标的点击操作

        #以第二根和第三根手指弯曲的角度为左右键,小于160度视为点击操作,以及两手指的距离小于40视为左键双击
        judge_finger = check_finger(finger)
        # 每次只能进行一个操作,所以返回值只能有一个为真,如果有超过1个为真,则进行下一次循环
        count = judge_finger.count(1)
        if count == 1:
            index = judge_finger.index(1)
            if index == 0:
                pyautogui.click(mouse_x,mouse_y,button = 'left')
                cv2.putText(frame, 'click left', (10, 20), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 1)
            elif index == 1:
                pyautogui.click(mouse_x,mouse_y,button = 'right')
                cv2.putText(frame, 'click right', (10, 20), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 1)
            else:
                pyautogui.doubleClick(mouse_x,mouse_y)
                cv2.putText(frame, 'click double', (10, 20), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 1)
        else:
            cv2.putText(frame,'no or more operate',(10,20),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),1)
    else:
        cv2.putText(frame, 'plese put your hand', (10, 20), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 1)

    #显示图像,并在按下ESC按键时退出程序
    cv2.imshow('img',frame)
    ret = cv2.waitKey(1)
    if ret == 27:
        break

#释放摄像头
cap.release()
cv2.destroyAllWindows()

'''
英语单词
capture  捕获
frame   帧
coordinate 坐标
offset 偏移
smooth 平滑
previous  以前
'''

总结

现在的计算机视觉,语音识别,自然语言的处理都离不开机器学习和深度学习。刚好学院现在也开了机器学习这门课,其中的内容和公式真是令人望而生畏,所以接下来我打算记录下自己的学习过程,以及自己的理解感悟,分享给大家。创作不易,希望大家能多多支持。在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_59151709/article/details/129282975