目录
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 就相当于坐标全部除以,shift 为 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:输出的数组,和输入的两个数组拥有相同的尺寸和通道数。
7.dype:输出阵列的深度,有默认值-1。当两个输入数组具有相同深度时,这个参数设置为-1(默认值),即等同于src1.depth()。
7.获取手部信息
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()#关闭所有显示窗口