如何使用 OpenCV 开发虚拟键盘

 
 

点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

介绍

OpenCV 是最流行的计算机视觉任务库,它是用于机器学习、图像处理等的跨平台开源库,用于开发实时计算机视觉应用程序。

CVzone 是一个计算机视觉包,它使用 OpenCV 和 Media Pipe 库作为其核心,使我们易于运行,例如手部跟踪、人脸检测、面部标志检测、姿势估计等,以及图像处理和其他计算机视觉相关的应用程序。

使用OpenCV实现虚拟键盘

让我们创建一个虚拟键盘。

首先,让我们安装所需的模块。

pip install numpy

pip install opencv-python

pip install cvzone

pip install pynput

使用OpenCV为虚拟键盘导入库

现在让我们导入所需的模块

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector
from time import sleep
import numpy as np
from pynput.keyboard import Controller

这里我们从 cvzone.HandTrackingModule 导入 HandDetector 模块,然后为了使虚拟键盘工作,我们需要从 pynput.keyboard 导入Controller。

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.set(3, 1280)
cap.set(4, 720)

现在让我们从 cv2.Videocapture 获取实时输入

detector = HandDetector(detectionCon=0.8)
keyboard_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", ",", ".", "/"]]
final_text = ""

我们以 0.8 的检测置信度初始化 HandDetector 并将其分配给检测器。

然后我们根据键盘的布局创建一个列表数组,并定义一个空字符串来存储键入的键。

定义绘制函数

keyboard = Controller()
def draw(img, buttonList):
    for button in buttonList:
        x, y = button.pos
        w, h = button.size
        cvzone.cornerRect(img, (button.pos[0], button.pos[1],
                                                   button.size[0],button.size[0]), 20 ,rt=0)
        cv2.rectangle(img, button.pos, (int(x + w), int(y + h)), (255, 144, 30), cv2.FILLED)
        cv2.putText(img, button.text, (x + 20, y + 65),
                    cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)
    return img

初始化键盘控制器,并定义一个名为draw()的函数,它接受两个参数,即图像和按钮列表并返回图像。在draw()函数内部,我们使用 cvzone 的cornerRect函数在每个键的角落绘制矩形边缘。这是为了让我们的键盘布局看起来更好看。就像下面的图片。

a6de46ec56aabbebd78ab268f36e347d.png

你也可以尝试更改不同的颜色。

class Button():
    def __init__(self, pos, text, size=[85, 85]):
        self.pos = pos
        self.size = size
        self.text = text

然后我们定义一个名为 Button() 的类,并提供位置、文本和大小作为输入,以便我们可以按照明确定义的顺序排列键盘按键。

buttonList = []
# mybutton = Button([100, 100], "Q")
for k in range(len(keyboard_keys)):
    for x, key in enumerate(keyboard_keys[k]):
        buttonList.append(Button([100 * x + 25, 100 * k + 50], key))

上面的循环将遍历键盘按键和 Button 对象,我们在其中给出位置和文本作为输入附加在一个名为 button list 的列表中。稍后我们可以将这个列表传递给 draw 函数以在我们的实时框架之上进行绘制。

使用OpenCV的虚拟键盘主程序

重要的部分来了。

while True:
    success, img = cap.read()
    img = detector.findHands(img)
    lmList, bboxInfo = detector.findPosition(img)
    img = draw(img, buttonList)  # change the draw funtion to transparent_layout for transparent keys
    if lmList:
        for button in buttonList:
            x, y = button.pos
            w, h = button.size

if x < lmList[8][0]<x+w and y < lmList[8][1] < y+h:
cv2.rectangle(img, button.pos, (x + w, y + h),
(0, 255, 255), cv2.FILLED)
cv2.putText(img, button.text, (x + 20, y + 65),
cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)
l, _, _ = detector.findDistance(8,12, img, draw=False)
print(l)

if l < 25:
keyboard.press(button.text)
cv2.rectangle(img, button.pos, (x + w, y + h),
(0, 255, 0), cv2.FILLED)
cv2.putText(img, button.text, (x + 20, y + 65),
cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)
final_text += button.text
sleep(0.20)

cv2.rectangle(img, (25,350), (700, 450),
(255, 255, 255), cv2.FILLED)
cv2.putText(img, final_text, (60, 425),
cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)

# cv2.rectangle(img, (100,100), (200,200),
# (100, 255, 0), cv2.FILLED)
# cv2.putText(img, 'Q', (120,180), cv2.FONT_HERSHEY_PLAIN, 5,
# (0, 0, 0), 5)

# img = mybutton.draw(img)
cv2.imshow("output", img)
cv2.waitKey(1)

在 while 循环中,首先我们读取实时输入帧并将其存储在一个名为img的变量中。然后我们将该图像传递给*检测器.findHands()*以便在帧中找到手。然后在该图像中,我们需要找到检测到的手的位置和边界框信息。

在这里我们可以找到我们的食指和中指的顶点之间的距离,如果两者之间的距离小于某个阈值,那么我们就可以输入我们所指示的字母。

一旦我们获得了位置,我们就会遍历整个位置列表。从该列表中,我们找到按钮位置和按钮大小,然后根据明确定义的方式将其绘制在框架上。

45243a7b7e1298bb4ef82180cbd4095c.png

图 1:手地标模型

之后,我们需要找到食指和中指的顶点之间的距离。在上图中,你可以看到我们需要的最高点是点 8 和点 12。因此,我们需要在距离查找函数中传递 8, 12 以获得它们之间的距离。

在上面的代码中,你可以看到 detector.findDistance(),我们通过了 8、12 和图像来查找距离,并将绘制标志设置为 false,这样我们就不需要两点之间的任何线。

如果点之间的距离非常小,我们将使用 press() 函数来按下按键。在上面的代码keyboard.press() 中,我们传递button.text以显示按下的键。最后,我们在键盘布局下方绘制一个小的白色矩形框,以显示按下的键。

一旦你执行了整个代码,它看起来像这样。

fef3918ffc863d7fc4676843da4e5227.png

将食指和中指靠近特定字母的顶部后,你可以键入该字母。

6c4edf16e8fcce723f905fac0cde67f5.png

如果你需要更自定义的键盘布局,我们可以使键盘布局透明。我们只需要添加一个透明布局函数并将*draw()函数替换为transparent_layout()*函数即可。

让我们定义transparent_layout()函数。下面是函数的代码,它采用与draw()函数相同的输入。在这里,我们将 numpy 的zero_like()函数分配给 名为imgNew的变量,并对其执行所需的操作,例如获得角矩形、为每个键创建矩形框并将文本放入框内。之后,我们将该图像复制到一个新变量并创建一个imgNew掩码,然后我们使用 OpenCV 的*addWeighted()*函数将掩码放置在实际图像的顶部。因此,这使键盘布局透明。

自定义键盘

def transparent_layout(img, buttonList):
    imgNew = np.zeros_like(img, np.uint8)
    for button in buttonList:
        x, y = button.pos
        cvzone.cornerRect(imgNew, (button.pos[0], button.pos[1],
                                                   button.size[0],button.size[0]), 20 ,rt=0)
        cv2.rectangle(imgNew, button.pos, (x + button.size[0], y + button.size[1]),
                                   (255, 144, 30), cv2.FILLED)
        cv2.putText(imgNew, button.text, (x + 20, y + 65),
                    cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)
        out = img.copy()
        alpaha = 0.5
        mask = imgNew.astype(bool)
        print(mask.shape)
        out[mask] = cv2.addWeighted(img, alpaha, imgNew, 1-alpaha, 0)[mask] 
        return out

一旦将while 循环中的*draw()函数替换为transparent_layout()*函数,它将如下所示。(下图)

633c2d2b3c5a69c7dedefc01825bccab.png

完整代码

下面是完整的代码

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector
from time import sleep
import numpy as np
from pynput.keyboard import Controller

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.set(3, 1280)
cap.set(4, 720)

detector = HandDetector(detectionCon=0.8)
keyboard_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", ",", ".", "/"]]

final_text = ""

keyboard = Controller()


def draw(img, buttonList):
    for button in buttonList:
        x, y = button.pos
        w, h = button.size
        cvzone.cornerRect(img, (button.pos[0], button.pos[1],
                                                   button.size[0],button.size[0]), 20 ,rt=0)
        cv2.rectangle(img, button.pos, (int(x + w), int(y + h)), (255, 144, 30), cv2.FILLED)
        cv2.putText(img, button.text, (x + 20, y + 65),
                    cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)
    return img


def transparent_layout(img, buttonList):
    imgNew = np.zeros_like(img, np.uint8)
    for button in buttonList:
        x, y = button.pos
        cvzone.cornerRect(imgNew, (button.pos[0], button.pos[1],
                                                   button.size[0],button.size[0]), 20 ,rt=0)
        cv2.rectangle(imgNew, button.pos, (x + button.size[0], y + button.size[1]),
                                   (255, 144, 30), cv2.FILLED)
        cv2.putText(imgNew, button.text, (x + 20, y + 65),
                    cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)

    out = img.copy()
    alpaha = 0.5
    mask = imgNew.astype(bool)
    print(mask.shape)
    out[mask] = cv2.addWeighted(img, alpaha, imgNew, 1-alpaha, 0)[mask]
    return out


class Button():
    def __init__(self, pos, text, size=[85, 85]):
        self.pos = pos
        self.size = size
        self.text = text


buttonList = []
# mybutton = Button([100, 100], "Q")
for k in range(len(keyboard_keys)):
    for x, key in enumerate(keyboard_keys[k]):
        buttonList.append(Button([100 * x + 25, 100 * k + 50], key))


while True:
    success, img = cap.read()
    img = detector.findHands(img)
    lmList, bboxInfo = detector.findPosition(img)
    img = draw(img, buttonList)  # change the draw funtion to transparent_layout for transparent keys

    if lmList:
        for button in buttonList:
            x, y = button.pos
            w, h = button.size

            if x < lmList[8][0]<x+w and y < lmList[8][1] < y+h:
                cv2.rectangle(img, button.pos, (x + w, y + h),
                              (0, 255, 255), cv2.FILLED)
                cv2.putText(img, button.text, (x + 20, y + 65),
                            cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)
                l, _, _ = detector.findDistance(8,12, img, draw=False)
                print(l)

                if l < 25:
                    keyboard.press(button.text)
                    cv2.rectangle(img, button.pos, (x + w, y + h),
                                  (0, 255, 0), cv2.FILLED)
                    cv2.putText(img, button.text, (x + 20, y + 65),
                                cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)
                    final_text += button.text
                    sleep(0.20)

    cv2.rectangle(img, (25,350), (700, 450),
                  (255, 255, 255), cv2.FILLED)
    cv2.putText(img, final_text, (60, 425),
                cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 4)

    # cv2.rectangle(img, (100,100), (200,200),
    #               (100, 255, 0), cv2.FILLED)
    # cv2.putText(img, 'Q', (120,180), cv2.FONT_HERSHEY_PLAIN, 5,
    #             (0, 0, 0), 5)

    # img = mybutton.draw(img)
    cv2.imshow("output", img)
    cv2.waitKey(1)

总结

这是虚拟键盘的实现,如果你想完善它,你也可以试着添加按键声音,然后我们还可以让键盘布局在框架内移动。

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

下载2:Python视觉实战项目52讲

在「小白学视觉」公众号后台回复:Python视觉实战项目即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

下载3:OpenCV实战项目20讲

在「小白学视觉」公众号后台回复:OpenCV实战项目20讲即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

86e9f4012c83922bfc65dba6a7666d02.png

7b850aa1f81b8d12e0a947b4352b1f8d.png

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值