ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-01-01 18:03:31  阅读:584  来源: 互联网

标签:鼠标 img AI 检测 cv2 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()

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

标签:鼠标,img,AI,检测,cv2,python,坐标,图像,视觉
来源: https://blog.csdn.net/dgvv4/article/details/122268203

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有