【机器视觉案例】(8) AI视觉,手势控制电脑鼠标,附python完整代码

各位同学好,今天和大家分享一下如何使用 MediaPipe+Opencv 通过手势识别来控制电脑鼠标的移动和点击,如果有兴趣的话,可以代替鼠标去打游戏。先放图看效果。用画图板来测试

黄框代表电脑屏幕的范围,将黄框的宽高映射到电脑屏幕的宽高。食指竖起并且中指弯下时,移动鼠标食指和中指都竖起,并且两个指尖距离小于50时,认为是点击鼠标左上角30代表FPS值

移动鼠标移动时,食指指尖有淡蓝色圆点,表明鼠标在移动,如右图的绿色线条是鼠标移动轨迹

点击鼠标当食指和中指间的距离小于50,食指指尖圆点变成绿色,点击鼠标,如画图板上的两个点,就是点击两下实现的。


1. 导入工具包

# 安装工具包
pip install opencv-contrib-python  # 安装opencv
pip install mediapipe  # 安装mediapipe
# pip install mediapipe --user  #有user报错的话试试这个
pip install cvzone  # 安装cvzone
pip install autopy  # 鼠标控制单元

# 导入工具包
import numpy as np
import cv2
from cvzone.HandTrackingModule import HandDetector  # 手部追踪方法
import mediapipe as mp
import time
import autopy

21个手部关键点信息如下,本节我们主要研究食指指尖"8"中指指尖"12"的坐标信息。


2. 手部关键点检测

(1) cvzone.HandTrackingModule.HandDetector()   手部关键点检测方法

参数:

mode: 默认为 False,将输入图像视为视频流。它将尝试在第一个输入图像中检测手,并在成功检测后进一步定位手的坐标。在随后的图像中,一旦检测到所有 maxHands 手并定位了相应的手的坐标,它就会跟踪这些坐标,而不会调用另一个检测,直到它失去对任何一只手的跟踪。这减少了延迟,非常适合处理视频帧。如果设置为 True,则在每个输入图像上运行手部检测,用于处理一批静态的、可能不相关的图像。

maxHands: 最多检测几只手,默认为 2

detectionCon: 手部检测模型的最小置信值(0-1之间),超过阈值则检测成功。默认为 0.5

minTrackingCon: 坐标跟踪模型的最小置信值 (0-1之间),用于将手部坐标视为成功跟踪,不成功则在下一个输入图像上自动调用手部检测。将其设置为更高的值可以提高解决方案的稳健性,但代价是更高的延迟。如果 mode 为 True,则忽略这个参数,手部检测将在每个图像上运行。默认为 0.5

它的参数和返回值类似于官方函数 mediapipe.solutions.hands.Hands()

(2)cvzone.HandTrackingModule.HandDetector.findHands()    找到手部关键点并绘图

参数:

img: 需要检测关键点的帧图像,格式为BGR

draw: 是否需要在原图像上绘制关键点及识别框

flipType: 图像是否需要翻转,当视频图像和我们自己不是镜像关系时,设为True就可以了

返回值:

hands: 检测到的手部信息,包含:21个关键点坐标,检测框坐标及宽高,检测框中心坐标,检测出是哪一只手。

img: 返回绘制了关键点及连线后的图像

手部检测的代码如下:

import cv2
from cvzone.HandTrackingModule import HandDetector   # 手部检测方法
import time

#(1)导数视频数据
cap = cv2.VideoCapture(0)  # 0代表自己电脑的摄像头
cap.set(3, 1280)  # 设置显示框的宽度1280
cap.set(4, 720)  # 设置显示框的高度720

pTime = 0  # 设置第一帧开始处理的起始时间

#(2)接收手部检测方法
detector = HandDetector(mode=False,  # 视频流图像 
                        maxHands=1,  # 最多检测一只手
                        detectionCon=0.8,  # 最小检测置信度 
                        minTrackCon=0.5)   # 最小跟踪置信度

#(3)处理每一帧图像
while True:
    
    # 图片是否成功接收、img帧图像
    success, img = cap.read()
    
    # 翻转图像,使自身和摄像头中的自己呈镜像关系
    img = cv2.flip(img, flipCode=1)  # 1代表水平翻转,0代表竖直翻转
    
    #(4)手部检测方法
    # 传入每帧图像, 返回手部关键点的坐标信息(字典构成的列表hands),绘制关键点后的图像img
    hands, img = detector.findHands(img, flipType=False)  # 上面反转过了,这里就不用再翻转了
    # print(hands)
    
    #(5)显示图像
    # 查看FPS
    cTime = time.time() #处理完一帧图像的时间
    fps = 1/(cTime-pTime)
    pTime = cTime  #重置起始时间
    
    # 在视频上显示fps信息,先转换成整数再变成字符串形式,文本显示坐标,文本字体,文本大小
    cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)  
    
    # 显示图像,输入窗口名及图像数据
    cv2.imshow('image', img)    
    if cv2.waitKey(1) & 0xFF==27:  #每帧滞留20毫秒后消失,ESC键退出
        break

# 释放视频资源
cap.release()
cv2.destroyAllWindows()

打印每帧图像检测到的手部信息hands列表,由字典组成。lmList 代表21个手部关键点的像素坐标;bbox 代表检测框的左上角坐标和框的宽高;center 代表检测框的中心点的像素坐标;type 代表检测出的是左手还是右手。

----------------------------------------------------------------------------
[{'lmList': [[522, 755], [621, 761], [709, 724], [765, 675], [794, 615], [705, 629], [761, 588], [749, 643], [715, 686], [676, 599], [743, 565], [713, 637], [664, 684], [634, 565], [710, 543], [668, 622], [613, 666], [576, 533], [657, 519], [640, 580], [597, 620]],
 'bbox': (522, 519, 272, 242), 
 'center': (658, 640), 
 'type': 'Left'}]
[{'lmList': [[520, 763], [620, 774], [716, 753], [779, 707], [816, 650], [716, 655], [781, 619], [767, 677], [727, 721], [689, 627], [759, 595], [731, 667], [683, 710], [649, 594], [727, 579], [680, 653], [620, 689], [593, 558], [674, 549], [655, 608], [608, 642]],
 'bbox': (520, 549, 296, 225),
 'center': (668, 661),
 'type': 'Left'}]
----------------------------------------------------------------------------

检测结果如图所示:


2. 移动鼠标

移动鼠标的思路是:如果检测到食指竖起,并且中指弯下,那么就认为是移动鼠标,鼠标的位置坐标是食指指尖所在的位置坐标

检测哪个手指是竖起的方法是 detector.fingersUp() ,传入检测到的某只手的手部信息hands[0]返回值由5个元素构成的列表元素为1代表该手指竖起,0代表手指弯下,例如:[0,1,1,0,0] 就代表食指和中指竖起,其他手指弯下。

当手指在摄像头画面的下半部分移动时,由于摄像头界限范围问题,手掌部分会在摄像头画面中消失,导致检测不到手部关键点,因此,在屏幕画面的偏上半部分绘制一个黄色的矩形框,手指只能在矩形框中移动,避免手部关键点的消失。

由于我们设置的矩形框大小明显要小于电脑屏幕的大小,导致手控的鼠标无法在整个电脑屏幕上移动。因此,需要将矩形框的宽和高映射到电脑屏幕的宽和高。使用线性插值方法 np.interp(x, xp, fp)  简单来说就是将变量x的范围从原来的xp映射到fp。如:np.interp(x1, (pt1[0], pt2[0]), (0, wScr)),就是将x坐标的范围从原来的 pt1[0]pt1[0]+w,映射到整个电脑屏幕  wScr

返回电脑屏幕的宽和高: autopy.screen.size()

移动鼠标的位置到坐标(x,y): autopy.mouse.move(x, y)

autopy具体使用方法见下文:https://blog.csdn.net/qq_30462003/article/details/100130472

因此,我们在上述代码中补充:

import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector   # 手部检测方法
import time
# pip install autopy  #鼠标控制单元
import autopy

#(1)导数视频数据
wScr, hScr = autopy.screen.size()   # 返回电脑屏幕的宽和高(1920.0, 1080.0)
wCam, hCam = 1280, 720   # 视频显示窗口的宽和高
pt1, pt2 = (100,100), (1100, 500)   # 虚拟鼠标的移动范围,左上坐标pt1,右下坐标pt2

cap = cv2.VideoCapture(0)  # 0代表自己电脑的摄像头
cap.set(3, wCam)  # 设置显示框的宽度1280
cap.set(4, hCam)  # 设置显示框的高度720

pTime = 0  # 设置第一帧开始处理的起始时间

#(2)接收手部检测方法
detector = HandDetector(mode=False,  # 视频流图像 
                        maxHands=1,  # 最多检测一只手
                        detectionCon=0.8,  # 最小检测置信度 
                        minTrackCon=0.5)   # 最小跟踪置信度

#(3)处理每一帧图像
while True:
    
    # 图片是否成功接收、img帧图像
    success, img = cap.read()
    
    # 翻转图像,使自身和摄像头中的自己呈镜像关系
    img = cv2.flip(img, flipCode=1)  # 1代表水平翻转,0代表竖直翻转
    
    # 在图像窗口上创建一个矩形框,在该区域内移动鼠标
    cv2.rectangle(img, pt1, pt2, (0,255,255), 5)
    
    #(4)手部关键点检测
    # 传入每帧图像, 返回手部关键点的坐标信息(字典),绘制关键点后的图像
    hands, img = detector.findHands(img, flipType=False)  # 上面反转过了,这里就不用再翻转了
    # print(hands)
    
    # 如果能检测到手那么就进行下一步
    if hands:
        
        # 获取手部信息hands中的21个关键点信息
        lmList = hands[0]['lmList']  # hands是由N个字典组成的列表,字典包每只手的关键点信息
        
        # 获取食指指尖坐标,和中指指尖坐标
        x1, y1 = lmList[8]  # 食指尖的关键点索引号为8
        x2, y2 = lmList[12] # 中指指尖索引12
        
        
        #(5)检查哪个手指是朝上的
        fingers = detector.fingersUp(hands[0])  # 传入
        # print(fingers) 返回 [0,1,1,0,0] 代表 只有食指和中指竖起
        
        # 如果食指竖起且中指弯下,就认为是移动鼠标
        if fingers[1] == 1 and fingers[2] == 0:
            
            # 开始移动时,在食指指尖画一个圆圈,看得更清晰一些
            cv2.circle(img, (x1,y1), 15, (255,255,0), cv2.FILLED)  # 颜色填充整个圆
            
            #(6)确定鼠标移动的范围
            # 将食指的移动范围从预制的窗口范围,映射到电脑屏幕范围
            x3 = np.interp(x1, (pt1[0], pt2[0]), (0, wScr))
            y3 = np.interp(y1, (pt1[1], pt2[1]), (0, hScr))

            #(7)移动鼠标
            autopy.mouse.move(x3, y3)  # 给出鼠标移动位置坐标
   
    #(8)显示图像
    # 查看FPS
    cTime = time.time() #处理完一帧图像的时间
    fps = 1/(cTime-pTime)
    pTime = cTime  #重置起始时间
    
    # 在视频上显示fps信息,先转换成整数再变成字符串形式,文本显示坐标,文本字体,文本大小
    cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)  
    
    # 显示图像,输入窗口名及图像数据
    cv2.imshow('image', img)    
    if cv2.waitKey(1) & 0xFF==27:  #每帧滞留20毫秒后消失,ESC键退出
        break

# 释放视频资源
cap.release()
cv2.destroyAllWindows()

效果图如下:


3. 点击鼠标

点击鼠标的思路是:如果食指和中指同时竖起,并且食指指尖和中指指尖之间的像素距离小于50时,那么就认为是点击鼠标。

检测哪个手指是竖起的方法是上面已经解释过的 detector.fingersUp() 方法,检测指尖距离的方法是: detector.findDistance(pt1, pt2, img) pt1 pt2 是两个点的坐标,传入img来绘制指尖连线图。

点击鼠标的函数,autopy.mouse.click()

由于用手指控制鼠标时,每一帧的坐标位置的变化幅度较大,导致电脑鼠标在手指控制下很容易晃动,很难准确定位到一个目标。因此需要平滑每一帧的坐标变化,使坐标变化更缓慢一些

例如:cLocx = pLocx + (x3 - pLocx) / smooth,式中:前帧的鼠标位置的x坐标 cLocx前一帧的鼠标位置的x坐标 pLocx当前鼠标位置的x坐标 x3自定义平滑系数smooth,值越大鼠标移动就越慢,平稳性就越高。

因此,在上述代码中补充:

import cv2
import numpy as np
from cvzone.HandTrackingModule import HandDetector   # 手部检测方法
import time
import autopy

#(1)导数视频数据
wScr, hScr = autopy.screen.size()   # 返回电脑屏幕的宽和高(1920.0, 1080.0)
wCam, hCam = 1280, 720   # 视频显示窗口的宽和高
pt1, pt2 = (100,100), (1100, 500)   # 虚拟鼠标的移动范围,左上坐标pt1,右下坐标pt2

cap = cv2.VideoCapture(0)  # 0代表自己电脑的摄像头
cap.set(3, wCam)  # 设置显示框的宽度1280
cap.set(4, hCam)  # 设置显示框的高度720

pTime = 0  # 设置第一帧开始处理的起始时间

pLocx, pLocy = 0, 0  # 上一帧时的鼠标所在位置

smooth = 4  # 自定义平滑系数,让鼠标移动平缓一些

#(2)接收手部检测方法
detector = HandDetector(mode=False,  # 视频流图像 
                        maxHands=1,  # 最多检测一只手
                        detectionCon=0.8,  # 最小检测置信度 
                        minTrackCon=0.5)   # 最小跟踪置信度

#(3)处理每一帧图像
while True:
    
    # 图片是否成功接收、img帧图像
    success, img = cap.read()
    
    # 翻转图像,使自身和摄像头中的自己呈镜像关系
    img = cv2.flip(img, flipCode=1)  # 1代表水平翻转,0代表竖直翻转
    
    # 在图像窗口上创建一个矩形框,在该区域内移动鼠标
    cv2.rectangle(img, pt1, pt2, (0,255,255), 5)
    
    #(4)手部关键点检测
    # 传入每帧图像, 返回手部关键点的坐标信息(字典),绘制关键点后的图像
    hands, img = detector.findHands(img, flipType=False)  # 上面反转过了,这里就不用再翻转了
    # print(hands)
    
    # 如果能检测到手那么就进行下一步
    if hands:
        
        # 获取手部信息hands中的21个关键点信息
        lmList = hands[0]['lmList']  # hands是由N个字典组成的列表,字典包每只手的关键点信息
        
        # 获取食指指尖坐标,和中指指尖坐标
        x1, y1 = lmList[8]  # 食指尖的关键点索引号为8
        x2, y2 = lmList[12] # 中指指尖索引12

        #(5)检查哪个手指是朝上的
        fingers = detector.fingersUp(hands[0])  # 传入
        # print(fingers) 返回 [0,1,1,0,0] 代表 只有食指和中指竖起
        
        # 如果食指竖起且中指弯下,就认为是移动鼠标
        if fingers[1] == 1 and fingers[2] == 0:
            
            # 开始移动时,在食指指尖画一个圆圈,看得更清晰一些
            cv2.circle(img, (x1,y1), 15, (255,255,0), cv2.FILLED)  # 颜色填充整个圆

            #(6)确定鼠标移动的范围
            # 将食指的移动范围从预制的窗口范围,映射到电脑屏幕范围
            x3 = np.interp(x1, (pt1[0], pt2[0]), (0, wScr))
            y3 = np.interp(y1, (pt1[1], pt2[1]), (0, hScr))

            #(7)平滑,使手指在移动鼠标时,鼠标箭头不会一直晃动
            cLocx = pLocx + (x3 - pLocx) / smooth  # 当前的鼠标所在位置坐标
            cLocy = pLocy + (y3 - pLocy) / smooth            
      
            #(8)移动鼠标
            autopy.mouse.move(cLocx, cLocy)  # 给出鼠标移动位置坐标
            
            # 更新前一帧的鼠标所在位置坐标,将当前帧鼠标所在位置,变成下一帧的鼠标前一帧所在位置
            pLocx, pLocy = cLocx, cLocy
 
        #(9)如果食指和中指都竖起,指尖距离小于某个值认为是点击鼠标
        if fingers[1] == 1 and fingers[2] == 1:  # 食指和中指都竖起
         
            # 计算食指尖和中指尖之间的距离distance,绘制好了的图像img,指尖连线的信息info
            distance, info, img = detector.findDistance((x1, y1), (x2, y2), img)
            # print(distance)
            
            # 当指间距离小于50(像素距离)就认为是点击鼠标
            if distance < 50:
                
                # 在食指尖画个绿色的圆,表示点击鼠标
                cv2.circle(img, (x1,y1), 15, (0,255,0), cv2.FILLED)
                
                # 点击鼠标
                autopy.mouse.click()

    #(10)显示图像
    # 查看FPS
    cTime = time.time() #处理完一帧图像的时间
    fps = 1/(cTime-pTime)
    pTime = cTime  #重置起始时间
    
    # 在视频上显示fps信息,先转换成整数再变成字符串形式,文本显示坐标,文本字体,文本大小
    cv2.putText(img, str(int(fps)), (70,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)  
    
    # 显示图像,输入窗口名及图像数据
    cv2.imshow('image', img)    
    if cv2.waitKey(1) & 0xFF==27:  #每帧滞留20毫秒后消失,ESC键退出
        break

# 释放视频资源
cap.release()
cv2.destroyAllWindows()

结果图像展示,在绘图板中每点击一次就绘制一个圆圈。

评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

立Sir

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

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

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

打赏作者

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

抵扣说明:

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

余额充值