admin管理员组

文章数量:1544594

各位同学好,今天和大家分享一下如何使用 opencv+mediapipe 完成远程手势控制电脑键盘。感兴趣的可以看一下我前面一篇手势控制电脑鼠标:https://blog.csdn/dgvv4/article/details/122268203?spm=1001.2014.3001.5501把这两个结合起来去打游戏会不会很有意思呢。先放张图看效果。

这里用百度搜索栏做测试,搜索框中的内容和opencv显示窗口上的内容是同步打印出来的。

工作原理:如果检测到食指指尖关键点坐标在某个按键框的范围内部,那么该按键显示绿色;如果食指指尖和中指指尖之间的距离小于规定距离,就认为是点击食指指尖所在的按键,按键变为红色;设置1秒内同一个按键只能点击一次,避免重复出现多个相同按键值。


1. 安装工具包

# 安装工具包
pip install opencv-contrib-python  # 安装opencv
pip install mediapipe  # 安装mediapipe
# pip install mediapipe --user  #有user报错的话试试这个
pip install cvzone  # 安装cvzone
pip install pynput  # 键盘控制单元
 
# 导入工具包
import numpy as np
import cv2
from cvzone.HandTrackingModule import HandDetector  # 手部追踪方法
import mediapipe as mp
import time
from pynput.keyboard import Controller  # 键盘控制模块

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


2. 手部关键点检测,制作虚拟键盘

2.1 手部关键点检测

(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: 返回绘制了关键点及连线后的图像


2.2 绘制虚拟键盘

本节只创建键盘上的部分按键用于演示,定义类Button不需要一个一个单独绘制矩形按键。使用一个循环,分别对每个按键实例化将实例化结果保存在 buttonList 列表中。在第(6)步绘制键盘时,逐个取出实例化对象,在窗口上绘制30个按键。

代码如下:

import cv2
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块

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

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

#(3)创建一个类用于构造键盘按键
class Button:
    
    # 初始化,按键的左上坐标pos(列表类型),文本信息text(字符串类型),按键的宽高size
    def __init__(self, pos:list, text:str, size=[90,90]):
        
        # 分配属性
        self.pos = pos
        self.text = text
        self.size = size
    
    # 在类的内部定义方法
    def draw(self, img):
        
        x1, y1 = self.pos  # 矩形框的左上角坐标
        w, h = self.size   # 矩形框的宽高
        
        # img画板,矩形框左上角坐标,矩形框右下角坐标,颜色,-1代表颜色填充
        cv2.rectangle(img, (x1, y1), (x1+w, y1+h), (255,0,0), -1)
        # 美化一下矩形框
        cv2.rectangle(img, (x1, y1), (x1+w, y1+h), (255,255,0), 4)
        # 在矩形框上显示字符串信息
        cv2.putText(img, self.text, (x1+25, y1+65), cv2.FONT_HERSHEY_COMPLEX, 1.8, (255,255,255), 3)


# 创建一个列表存放键盘上每个按键的文本信息
keys = [['Q','W','E','R','T','Y','U','I','O','P'],
        ['A','S','D','F','G','H','J','K','L',';'],
        ['Z','X','C','V','B','N','M',',','.','/']]
    
# 存放每一个按键的信息
buttonList = []

# 通过循环来实例化所有按键
for i in range(3):  # 键盘文本列表有三行
    
    # 分别实例化每一行的按钮信息 
    for x, key in enumerate(keys[i]):  #返回每个元素的索引和值

        # 确定每个按键的左上角坐标位置
        px = 115*x + 30 + 40*i  #水平方向每两个按键之间间隔115,每次换行缩进40,初始位置x=30
        py = 115*i + 50  # 竖直方向每两个按键之间间隔115,初始位置y=50
        
        # 将实例化后的对象存放在列表中
        buttonList.append(Button([px, py], key))
    
    # 每次换行后px坐标重置
    x = 0

#(4)处理每一帧图像
while True:
    
    # 返回是否读取成功,和读取的帧图像
    success, img = cap.read()
    
    # 翻转图像,让自己和摄像头呈镜像关系
    img = cv2.flip(img, 1)  # 1代表水平翻转,0代表上下翻转
    
    
    #(5)检测手部关键点
    # 检测手部关键点信息,返回手部信息hands,绘制关键点后的图像img
    hands, img = detector.findHands(img, flipType=False)
    
    #(6)绘制键盘
    for i in range(3*len(keys[0])):  # 一共有3行10列个按键
    
        # 调用类中的绘图方法,显示每个键盘按键
        buttonList[i].draw(img)

    #(7)显示图像
    cv2.imshow('video', img)
    
    # 每帧图像滞留时间,ESC键退出
    if cv2.waitKey(1) & 0xFF==27:
        break

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

手部检测结果及虚拟键盘如下图所示。


3. 锁定按键位置

从下面代码的第(6)步开始是锁定键盘按键的位置。通过 detector.findHands() 返回手部检测信息hands列表,由0或1或2个字典组成,如果检测到一只手就返回一个字典。字典中包含:lmList 代表21个手部关键点的像素坐标;bbox 代表检测框的左上角坐标和框的宽高;center 代表检测框的中心点的像素坐标;type 代表检测出的是左手还是右手。

只需要知道食指指尖坐标 lmList[8] 在哪个按键的范围内,并计算食指指尖和中指指尖之间的距离,距离小于某个值认为是点击按键。距离计算方法,detector.findDistance(pt1, pt2, img) 传入两个关键点坐标。返回值distance 代表两个关键点之间的距离, info 代表指尖连线的起点、中点、终点, img 代表绘制指尖连线后的图像。

在上述代码中补充:

import cv2
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块


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


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

#(3)创建一个类用于构造键盘按键
class Button:
    
    # 初始化,按键的左上坐标pos(列表类型),文本信息text(字符串类型),按键的宽高size
    def __init__(self, pos:list, text:str, size=[90,90]):
        
        # 分配属性
        self.pos = pos
        self.text = text
        self.size = size
    
    # 在类的内部定义方法,默认内部深蓝色填充,边框为浅蓝色
    def draw(self, img, colorIn, colorBd):
        
        x1, y1 = self.pos  # 矩形框的左上角坐标
        w, h = self.size   # 矩形框的宽高
        
        # img画板,矩形框左上角坐标,矩形框右下角坐标,颜色,-1代表颜色填充
        cv2.rectangle(img, (x1, y1), (x1+w, y1+h), colorIn, -1)
        # 美化一下矩形框
        cv2.rectangle(img, (x1, y1), (x1+w, y1+h), colorBd, 4)
        # 在矩形框上显示字符串信息
        cv2.putText(img, self.text, (x1+25, y1+65), cv2.FONT_HERSHEY_COMPLEX, 1.8, (255,255,255), 3)
        
        
# 创建一个列表存放键盘上每个按键的文本信息
keys = [['Q','W','E','R','T','Y','U','I','O','P'],
        ['A','S','D','F','G','H','J','K','L',';'],
        ['Z','X','C','V','B','N','M',',','.','/']]

# 存放每一个按键的信息
buttonList = []
        
# 矩形按键的颜色
colorIn = (255,0,0)  # 按键内部填充的颜色 
colorBd = (255,255,0)  # 按键的边框颜色            
        
# 通过循环来实例化所有按键
for i in range(3):  # 键盘文本列表有三行
    
    # 分别实例化每一行的按钮信息 
    for x, key in enumerate(keys[i]):  #返回每个元素的索引和值

        # 确定每个按键的左上角坐标位置
        px = 115*x + 30 + 40*i  #水平方向每两个按键之间间隔115,每次换行缩进40,初始位置x=30
        py = 115*i + 50  # 竖直方向每两个按键指尖间隔115,初始位置y=50
        
        # 将实例化后的对象存放在列表中
        buttonList.append(Button([px, py], key))
    
    # 每次换行后px坐标重置
    x = 0

#(4)处理每一帧图像
while True:
    
    # 返回是否读取成功,和读取的帧图像
    success, img = cap.read()
    
    # 翻转图像,让自己和摄像头呈镜像关系
    img = cv2.flip(img, 1)  # 1代表水平翻转,0代表上下翻转
    
    
    #(5)绘制键盘
    for i in range(3*len(keys[0])):  # 一共有3行10列个按键
    
        # 调用类中的绘图方法,显示每个键盘按键
        buttonList[i].draw(img, colorIn, colorBd)
    
    
    #(6)检测手部关键点
    # 检测手部关键点信息,返回手部信息hands,绘制关键点后的图像img
    hands, img = detector.findHands(img, flipType=False)
        
    # 如果检测到手了,才执行下一步
    if hands:
        
        # 获取21个手部关键点信息
        lmList = hands[0]['lmList']
        # 获取食指指尖关键点坐标
        x1, y1 = lmList[8]
        # 获取中指指尖关键点坐标
        x2, y2 = lmList[12]
        
        
        #(7)遍历所有的按键,检查食指指尖在哪个按键的范围内
        for index, button in enumerate(buttonList): # botton存放的是类实例化后的对象
            
            # 所在矩形的左上坐标和宽高
            x0, y0 = button.pos
            w, h = button.size
        
            # 如果食指指尖在某个矩形框中,改变键盘按键颜色
            if x0<=x1<=x0+w and y0<=y1<=y0+h:
                    
                # 按键内部填充颜色
                cv2.rectangle(img, (x0, y0), (x0+w, y0+h), (0,255,0), -1)
                # 按键边框颜色
                cv2.rectangle(img, (x0, y0), (x0+w, y0+h), (0,0,255), 4)
                # 按键上的字符串
                cv2.putText(img, button.text, (x0+25, y0+65), cv2.FONT_HERSHEY_COMPLEX, 1.8, (255,255,255), 3)
            
            
                #(8)计算食指和中指指尖之间的距离
                distance, _, img = detector.findDistance((x1,y1), (x2,y2), img)
                
                # 如果指尖距离小于80,认为是点击按键
                if distance<50:
                
                    # 点击按键改变按键颜色
                    cv2.rectangle(img, (x0, y0), (x0+w, y0+h), (0,0,255), -1)
                    # 按键上的字符串
                    cv2.putText(img, button.text, (x0+25, y0+65), cv2.FONT_HERSHEY_COMPLEX, 1.8, (255,255,255), 3)

    #(9)显示图像
    cv2.imshow('video', img)
    
    # 每帧图像滞留时间,ESC键退出
    if cv2.waitKey(1) & 0xFF==27:
        break

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

显示结果如下,如果食指在某个按键矩形中并且食指和中指之间的距离大于规定值,那么这个按键填充绿色,边界框变红色食指在某个按键矩形中并且食指和中指之间的距离小于规定值,那么这个按键和边界框都填充红色


4. 激活键盘按键

下面的第(9)步,为了验证点击按键时 opencv 画面上的点击内容和百度搜索框的输入内容是否同步,在opencv画面上创建一个矩形框用来显示字符。

通过 keyboard.press() 获得键盘响应,输入值是键盘上的某个字符,表示点击键盘上的该字符。

由于每一帧播放的非常快,可能只点击了一次按键,却打印出来很多相同的字符。使用休眠函数 time.sleep(t) 每点击一次按键就暂停程序的执行,暂停 t 秒时间。  

import cv2
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块
from time import sleep
from pynput.keyboard import Controller  # 键盘控制单元

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

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

# 接收键盘控制单元
keyboard = Controller()

#(3)创建一个类用于构造键盘按键
class Button:
    
    # 初始化,按键的左上坐标pos(列表类型),文本信息text(字符串类型),按键的宽高size
    def __init__(self, pos:list, text:str, size=[90,90]):
        
        # 分配属性
        self.pos = pos
        self.text = text
        self.size = size
    
    # 在类的内部定义方法,默认内部深蓝色填充,边框为浅蓝色
    def draw(self, img, colorIn, colorBd):
        
        x1, y1 = self.pos  # 矩形框的左上角坐标
        w, h = self.size   # 矩形框的宽高
        
        # img画板,矩形框左上角坐标,矩形框右下角坐标,颜色,-1代表颜色填充
        cv2.rectangle(img, (x1, y1), (x1+w, y1+h), colorIn, -1)
        # 美化一下矩形框
        cv2.rectangle(img, (x1, y1), (x1+w, y1+h), colorBd, 4)
        
        # 在矩形框上显示字符串信息
        cv2.putText(img, self.text, (x1+25, y1+65), cv2.FONT_HERSHEY_COMPLEX, 1.8, (255,255,255), 3)

# 创建一个列表存放键盘上每个按键的文本信息
keys = [['Q','W','E','R','T','Y','U','I','O','P'],
        ['A','S','D','F','G','H','J','K','L',';'],
        ['Z','X','C','V','B','N','M',',','.','/']]

# 保存最终的输出结果
finalText = ''

# 存放每一个按键的信息
buttonList = []
        
# 矩形按键的颜色
colorIn = (255,0,0)  # 按键内部填充的颜色 
colorBd = (255,255,0)  # 按键的边框颜色            
        
# 通过循环来实例化所有按键
for i in range(3):  # 键盘文本列表有三行
    
    # 分别实例化每一行的按钮信息 
    for x, key in enumerate(keys[i]):  #返回每个元素的索引和值

        # 确定每个按键的左上角坐标位置
        px = 115*x + 30 + 40*i  #水平方向每两个按键之间间隔115,每次换行缩进40,初始位置x=30
        py = 115*i + 50  # 竖直方向每两个按键指尖间隔115,初始位置y=50
        
        # 将实例化后的对象存放在列表中
        buttonList.append(Button([px, py], key))
    
    # 每次换行后px坐标重置
    x = 0

#(4)处理每一帧图像
while True:
    
    # 返回是否读取成功,和读取的帧图像
    success, img = cap.read()
    
    # 翻转图像,让自己和摄像头呈镜像关系
    img = cv2.flip(img, 1)  # 1代表水平翻转,0代表上下翻转

    #(5)绘制键盘
    for i in range(3*len(keys[0])):  # 一共有3行10列个按键
    
        # 调用类中的绘图方法,显示每个键盘按键
        buttonList[i].draw(img, colorIn, colorBd)

    #(6)检测手部关键点
    # 检测手部关键点信息,返回手部信息hands,绘制关键点后的图像img
    hands, img = detector.findHands(img, flipType=False)
        
    # 如果检测到手了,才执行下一步
    if hands:
        
        # 获取21个手部关键点信息
        lmList = hands[0]['lmList']
        # 获取食指指尖关键点坐标
        x1, y1 = lmList[8]
        # 获取中指指尖关键点坐标
        x2, y2 = lmList[12]

        #(7)遍历所有的按键,检查食指指尖在哪个按键的范围内
        for index, button in enumerate(buttonList): # botton存放的是类实例化后的对象
            
            # 所在矩形的左上坐标和宽高
            x0, y0 = button.pos
            w, h = button.size
        
            # 如果食指指尖在某个矩形框中,改变键盘按键颜色
            if x0<=x1<=x0+w and y0<=y1<=y0+h:
                    
                # 按键内部填充颜色
                cv2.rectangle(img, (x0, y0), (x0+w, y0+h), (0,255,0), -1)
                # 按键边框颜色
                cv2.rectangle(img, (x0, y0), (x0+w, y0+h), (0,0,255), 4)
                # 按键上的字符串
                cv2.putText(img, button.text, (x0+25, y0+65), cv2.FONT_HERSHEY_COMPLEX, 1.8, (255,255,255), 3)

                #(8)计算食指和中指指尖之间的距离
                distance, _, img = detector.findDistance((x1,y1), (x2,y2), img)
                
                # 如果指尖距离小于50,认为是点击按键
                if distance<50:
                
                    # 点击按键改变按键颜色
                    cv2.rectangle(img, (x0, y0), (x0+w, y0+h), (0,0,255), -1)
                    # 按键上的字符串
                    cv2.putText(img, button.text, (x0+25, y0+65), cv2.FONT_HERSHEY_COMPLEX, 1.8, (255,255,255), 3)
                    
                    # 点击键盘上某个按键
                    keyboard.press(button.text) # 键盘上的某个符号,'A'
                    
                    # 在文本框中显示该字符
                    finalText += button.text
                    
                    # 点击一次后,0.2秒之后才能再点一次
                    sleep(0.2)

    #(9)创建虚拟文本框
    # 文本框内部
    cv2.rectangle(img, (100, 450), (700, 550), (255,255,255), -1)
    # 文本框边框
    cv2.rectangle(img, (100, 450), (700, 550), (0,0,0), 5)    
    # 按键上的字符串
    cv2.putText(img, finalText, (110, 525), cv2.FONT_HERSHEY_COMPLEX, 1.8, (0,0,0), 3)

    #(10)显示图像
    cv2.imshow('video', img)
    
    # 每帧图像滞留时间,ESC键退出
    if cv2.waitKey(1) & 0xFF==27:
        break

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

显示结果如图,当食指在某个按键的范围内,并且指尖距离大于规定值,认为是搜索按键,按键变成绿色,边界框变成红色;如果指尖距离小于规定值,认为是点击按键,按键内部和边框都变成红色。使用百度搜索框测试,能够实现同步输入。

本文标签: 视觉手势键盘机器完整