opencv实战项目(1)—— 虚拟键盘

目录

1.导入库

2.链接摄像头

3.进行手势跟踪

4.创建按键类

5.计算按键大小

6.绘制键盘

7.获取手部信息

8.设置触碰按键的变化

9.设置点击按键的变化

10.规定每个按键的功能

11.显示字符

12.显示一帧图片

13.完整代码


1.导入库

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

2.链接摄像头

cap = cv2.VideoCapture(0)#读取摄像头
# 设置窗口大小:1280*720
cap.set(3, 1280)#窗口宽度
cap.set(4, 700)#窗口高度

1.cap=cv2.VideoCapture(0) 参数0表示读取摄像头

2.cap=cv2.VideoCapture('video.mp4') 表示读取视频

3.进行手势跟踪

keyboard = Controller()#键盘控制器
detector = HandDetector(mode=False,  # 视频流图像
                        maxHands=2 ,  # 最多检测一只手
                        detectionCon=0.8,  # 最小检测置信度
                        minTrackCon=0.5)  # 最小跟踪置信度

4.创建按键类

class Button():
    def __init__(self, pos:list, text:str, size=[75, 75]):
        self.pos = pos#位置
        self.size = size#大小
        self.text = text#文本
#按键名数组
keys_value = [["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "?"],
              ["A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "del"],
              ["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Enter"]]

5.计算按键大小

# 将不同属性的按键对象,存放在buttonList列表里
buttonList = []
for i in range(len(keys_value)):
    for index, key in enumerate(keys_value[i]):
        len_key = len(key)
        if len_key > 1:# 计算按键的字符个数,当超过1时,调整按键的大小;当超过四时,根据字符的个数更新按键大小
            buttonList.append(Button((80 + 100 * index, 100 * (i + 1)), key, size=(55 * (len_key // 4 + 2), 75)))
        else:
            buttonList.append(Button((80 + 100 * index, 100 * (i + 1)), key))

6.绘制键盘

# 定义函数,调用buttonList列表中所有的Button对象,并进行绘制;另外进行透明的显示
def drawAll_transparence(img, buttonList):
    imgNew = np.zeros_like(img, dtype=np.uint8)#创建img的同型矩阵
    for button in buttonList:#遍历每个按键
        # 根据每个矩形框中心点的位置,在一帧图像中画上每个矩形框
        x, y = button.pos#获取按键位置
        w, h = button.size#获取按键大小
        cv2.rectangle(imgNew, (x, y), (x + w, y + h), (255, 0, 255), cv2.FILLED)#绘制矩形,并填充
        cvzone.cornerRect(imgNew, (x, y, w, h), 20,rt=0,colorC=(0, 255, 0))#绘制边角
        cv2.putText(imgNew, button.text, (x + 25, y + 60), cv2.FONT_HERSHEY_PLAIN, 3, (255, 255, 255), thickness=3)#写入文字
    out = img.copy()
    alpha = 0.3
    mask = imgNew.astype(bool)#转换数据类型
    out[mask] = cv2.addWeighted(img, alpha, imgNew, 1 - alpha, 0)[mask]#透明处理
    return out

①numpy.zeros_like

numpy.zeros_like(a,dtype = None,order ='K',subok = True)

1.a : array_like用a的形状和数据类型,来定义返回数组的属性

2.dtype : 数据类型,可选覆盖结果的数据类型。

3.subok : bool,可选。值为True是使用a的内部数据类型,值为False是使用a数组的数据类型。

返回值类型 : ndarray。其与a相同形状和数据类型的数组,并且数组中的值都为0

②cv2.rectangle

cv2.rectangle(img,pt1,pt2,color,thickness,lineType,shift)

1.img:要绘制的图片

2.pt1:矩形左上点坐标

3.pt2:矩形右下点坐标

4.color:颜色

5.thickness:线条粗细

6.lineType:线条类型

7.shift:表示点坐标中的小数位数。shift 为 1 就相当于坐标全部除以2^{1},shift 为 2 就相当于坐标全部除以2^{2}

③cvzone.cornerRect

cvzone.cornerRect(img,bbox,l,t,rt,colorR,colorC)

1.img:要绘制的图片

2.bbox:起点坐标和大小[x,y,w,h]

3.l:边角线的长度

4.t:边角线的宽度

5.rt:矩形边线的粗细

6.colorR:矩形的颜色

7.colorC:边角线的颜色

④cv2.putText

putText(img,text,org,fontFace,fontScale,color,thickness,lineType)

1.img:绘制的图片

2.text:绘制的文本

3.org:左下角左边

4.fontFace:字体

5.fontScale:字体大小

6.color:字体颜色

7.thickness:线条粗细

8.lineType:线条类型

⑤cv2.addWeighted

addWeighted(src1,alpha,src2,beta,gamma,dst,dtype)

1.scr1:第一张图片

2.alpha:第一张图片的权重

3.scr2:第二张图片,需要和第一个数组拥有相同的尺寸和通道数

4.beta:第二张图片的权重

5.gamma:一个加到权重总和上的标量值,可以理解为加权和后的图像的偏移量

6.dst:输出的数组,和输入的两个数组拥有相同的尺寸和通道数。

dst = src1[I] \cdot *alpha + src2[I] \cdot beta + gamma

7.dype:输出阵列的深度,有默认值-1。当两个输入数组具有相同深度时,这个参数设置为-1(默认值),即等同于src1.depth()。

7.获取手部信息

opencv实战项目(1)—— 虚拟键盘_第1张图片

real_num_text = 0  # 记录finalText中真实存在的字符个数
num_text = 0  # 记录finalText中的字符个数,为了保证能每60个字符换一次行
finalText = ""  # 定义输出文本为空,字符串
while True:
    ret, img = cap.read()#读取一帧图片
    img = cv2.flip(img, 1)  # 因为摄像头是镜像的,所以需要将摄像头水平翻转
    hand,img = detector.findHands(img,flipType=False)#检测手部,获取手部信息
    img = drawAll_transparence(img, buttonList)#绘制键盘
    if hand:#检测到手部
       lmList = hand[0]['lmList']
       x1, y1 = lmList[8]#获取8号位置(食指上端)坐标
       x2, y2 = lmList[12]#获取12号位置(中指上端)坐标
       for button in buttonList:
           x, y = button.pos
           w, h = button.size

cv2.flip

flip(src,flipCode,dst)

1.src:要处理的原始图像

2.flipCode:旋转类型

3.dst:和src具有相同大小、类型的目标图像

旋转类型说明:
flipCode = 0:x轴方向旋转
flipCode > 0:y轴方向旋转
flipCode < 0:x轴y轴方向同时旋转

8.设置触碰按键的变化

if x <= x1 <= x + w and y <= y1 <= y + h:
    # 当食指的位置,在矩形框中,将矩形框的颜色变浅;文本字体变大
    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 255), cv2.FILLED)#矩形
    cvzone.cornerRect(img, (x, y, w, h), 20, rt=0, colorC=(0, 175, 0))#矩形的边角
    cv2.putText(img, button.text, (x + 22, y + 65), cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255), thickness=3)#输入字母

9.设置点击按键的变化

len, _, img = detector.findDistance((x1,y1), (x2,y2), img)#获取食指和中指指尖的距离
# 当食指与中指的距离小于50时,执行if语句中的操作
if len < 50:
    # 当食指与中指的距离小于50时,变换矩形框的颜色;文本字体变大
    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 255), cv2.FILLED)
    cvzone.cornerRect(img, (x, y, w, h), 20, rt=0, colorC=(255, 0, 0))
    cv2.putText(img, button.text, (x + 22, y + 65), cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255),thickness=3)

10.规定每个按键的功能

# 输出button.text的内容到finalText中
if button.text == "del":
    finalText = finalText[0:-1]#删除最后的字符
    num_text = num_text - 1
if button.text == "Enter":
    finalText += (50 - num_text % 50) * " "#添加空格进行换行
    for i in range(50 - num_text % 50):
        num_text += 1
if button.text != "del" and button.text != "Enter":
    finalText += button.text#添加字符
    num_text += 1
time.sleep(0.2)  # 每次按键的间隔时间

11.显示字符

# 实现换行:当遇到Enter按键时,直接换行;每行满50个字符时,换行
times = num_text // 50#是否换行的标志
cv2.rectangle(img, (80, 400), (1200, 450 + times * 25), (255, 0, 255), cv2.FILLED)#调整输出框大小
for i in range(times + 1):
    cv2.putText(img, finalText[50 * i:50 * (i + 1)], (90, 425 + 25 * i),cv2.FONT_HERSHEY_PLAIN, 2, (255, 255, 255),thickness=2)#输出字母

12.显示一帧图片

# 显示一帧图像
cv2.imshow("Image", img)
if cv2.waitKey(1) & 0xFF == ord('q'):  # q键退出
    break

cv2.waitKey

waitKey(delay)

参数:

1、delay≤0:一直等待按键;

2、delay>0:等待按键的时间,比如cv2.waitKey(1),则是等待1ms,即每一帧的间隔时间。

返回值:

1、等待期间有按键:返回按键的ASCII码(比如:q的ASCII码为113);

2、等待期间没有按键:返回 -1;

if cv2.waitKey(1) & 0xFF == ord('q'):
    break

计算机中以补码形式存储二进制数码

-1 的补码:1111 1111

0xFF补:1111 1111

按为取&后:1111 1111 = 255

可以看出,任何数对0xFF进行与操作后都等于本身,因此,ord('q')返回的是自身的ASII码,当按下q时,if语句将会执行。

13.完整代码

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

#链接摄像头
cap = cv2.VideoCapture(0)#读取摄像头
# 设置窗口大小:1280*720
cap.set(3, 1280)#窗口宽度
cap.set(4, 700)#窗口高度

#识别手势
keyboard = Controller()#键盘控制器
detector = HandDetector(mode=False,  # 视频流图像
                        maxHands=2 ,  # 最多检测一只手
                        detectionCon=0.8,  # 最小检测置信度
                        minTrackCon=0.5)  # 最小跟踪置信度

#设置按键类
class Button():
    def __init__(self, pos:list, text:str, size=[75, 75]):
        self.pos = pos#位置
        self.size = size#大小
        self.text = text#文本

keys_value = [["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "?"],
              ["A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "del"],
              ["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Enter"]]

# 将不同属性的按键对象,存放在buttonList列表里
buttonList = []
for i in range(len(keys_value)):
    for index, key in enumerate(keys_value[i]):
        len_key = len(key)
        if len_key > 1:# 计算按键的字符个数,当超过1时,调整按键的大小;当超过四时,根据字符的个数更新按键大小
            buttonList.append(Button((80 + 100 * index, 100 * (i + 1)), key, size=(55 * (len_key // 4 + 2), 75)))
        else:
            buttonList.append(Button((80 + 100 * index, 100 * (i + 1)), key))

#绘制键盘
# 定义函数,调用buttonList列表中所有的Button对象,并进行绘制;另外进行透明的显示
def drawAll_transparence(img, buttonList):
    imgNew = np.zeros_like(img, dtype=np.uint8)#创建img的同型矩阵
    for button in buttonList:#遍历每个按键
        # 根据每个矩形框中心点的位置,在一帧图像中画上每个矩形框
        x, y = button.pos#获取按键位置
        w, h = button.size#获取按键大小
        cv2.rectangle(imgNew, (x, y), (x + w, y + h), (255, 0, 255), cv2.FILLED)#绘制矩形,并填充
        cvzone.cornerRect(imgNew, (x, y, w, h), 20,rt=0,colorC=(0, 255, 0))#绘制边角
        cv2.putText(imgNew, button.text, (x + 25, y + 60), cv2.FONT_HERSHEY_PLAIN, 3, (255, 255, 255), thickness=3)#写入文字
    out = img.copy()
    alpha = 0.3
    mask = imgNew.astype(bool)#转换数据类型
    out[mask] = cv2.addWeighted(img, alpha, imgNew, 1 - alpha, 0)[mask]#透明处理
    return out

real_num_text = 0  # 记录finalText中真实存在的字符个数
num_text = 0  # 记录finalText中的字符个数,为了保证能每60个字符换一次行
finalText = ""  # 定义输出文本为空,字符串
# 每次读取一帧图像,除非有break出现,否则一直在读取并显示变化后摄像头每一帧的图像
while True:
    ret, img = cap.read()
    img = cv2.flip(img, 1)  # 因为摄像头是镜像的,所以需要将摄像头水平翻转
    hand,img = detector.findHands(img,flipType=False)
    # 存放手指点的信息和手的边界框信息
    #lmList, bboxInfo = detector.findPosition(img)
    # draw the visual keyboard
    img = drawAll_transparence(img, buttonList)

    if hand:
        lmList = hand[0]['lmList']
        x1, y1 = lmList[8]
        x2, y2 = lmList[12]
        for button in buttonList:
            x, y = button.pos
            w, h = button.size

            if x <= x1 <= x + w and y <= y1 <= y + h:
                # 当食指的位置,在矩形框中,将矩形框的颜色变浅;文本字体变大
                cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 255), cv2.FILLED)#矩形背景
                cvzone.cornerRect(img, (x, y, w, h), 20, rt=0, colorC=(0, 175, 0))#矩形边角
                cv2.putText(img, button.text, (x + 22, y + 65), cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255), thickness=3)#输入的字母

                # when clicked
                len, _, img = detector.findDistance((x1,y1), (x2,y2), img)
                # 当食指与中指的距离小于50时,执行if语句中的操作
                if len < 50:
                    # 当食指与中指的距离小于50时,变换矩形框的颜色;文本字体变大
                    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 255), cv2.FILLED)
                    cvzone.cornerRect(img, (x, y, w, h), 20, rt=0, colorC=(255, 0, 0))
                    cv2.putText(img, button.text, (x + 22, y + 65), cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255),thickness=3)

                    # 输出button.text的内容到finalText中
                    if button.text == "del":
                        finalText = finalText[0:-1]#删除最后的字符
                        num_text = num_text - 1
                    if button.text == "Enter":
                        finalText += (50 - num_text % 50) * " "#补充空格进行换行
                        for i in range(50 - num_text % 50):
                            num_text += 1
                    if button.text != "del" and button.text != "Enter":
                        finalText += button.text#添加字符
                        num_text += 1
                    time.sleep(0.2)  # 每次按键的间隔时间

    # 显示字符;
    # 实现换行:当遇到Enter按键时,直接换行;每行满50个字符时,换行
    times = num_text // 50
    cv2.rectangle(img, (80, 400), (1200, 450 + times * 25), (255, 0, 255), cv2.FILLED)
    for i in range(times + 1):
        cv2.putText(img, finalText[50 * i:50 * (i + 1)], (90, 425 + 25 * i), cv2.FONT_HERSHEY_PLAIN, 2, (255, 255, 255),thickness=2)

    # 显示一帧图像
    cv2.imshow("Image", img)
    if cv2.waitKey(1) & 0xFF == ord('q'):  # q键退出
        break
cap.release()#停止捕获视频
cv2.destroyAllWindows()#关闭所有显示窗口

opencv实战项目(1)—— 虚拟键盘_第2张图片

 opencv实战项目(1)—— 虚拟键盘_第3张图片

你可能感兴趣的:(opencv项目实战,opencv,人工智能,计算机视觉)