В данной статье разберемся, как написать виртуальную клавиатуру, используя Python+Opencv.
Идея заключается в создании виртуальной клавиатуры, ее выводе на экран и возможности ее использования. Подразумевается, что клавиатура будет работать с текстовыми редакторами. Для того, чтобы нажать на кнопку, нам потребуется кликнуть
на нее, поэтому представим, что кликом
будет служить соединение указательного и среднего пальцев. Теперь можно писать код.
Все исходники можно найти на моем Github.
Установим все необходимые библиотеки для Python:
pip install opencv-python
pip install numpy
pip install cvzone
pip install pynput
В прошлых статьях я использовал класс handDetector
, поэтому добавим его и сюда. Создаем файл HandTrackingModule.py:
import cv2
import mediapipe as mp
import time
import math
class handDetector():
def __init__(self, mode=False, maxHands=2, modelComplexity=1, detectionCon=0.5, trackCon=0.5):
self.mode = mode
self.maxHands = maxHands
self.modelComplexity = modelComplexity
self.detectionCon = detectionCon
self.trackCon = trackCon
self.mpHands = mp.solutions.hands
self.hands = self.mpHands.Hands(self.mode, self.maxHands, self.modelComplexity, self.detectionCon, self.trackCon)
self.mpDraw = mp.solutions.drawing_utils
self.tipIds = [4, 8, 12, 16, 20]
def findHands(self, img, draw=True):
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
self.results = self.hands.process(imgRGB)
#print(results.multi_hand_landmarks)
if self.results.multi_hand_landmarks:
for handLms in self.results.multi_hand_landmarks:
if draw:
self.mpDraw.draw_landmarks(img, handLms, self.mpHands.HAND_CONNECTIONS)
return img
def findPosition(self, img, handNo=0, draw=True):
xList = []
yList = []
bbox = []
self.lmList = []
if self.results.multi_hand_landmarks:
myHand = self.results.multi_hand_landmarks[handNo]
for id, lm in enumerate(myHand.landmark):
#print(id, lm)
h, w, c = img.shape
cx, cy = int(lm.x*w), int(lm.y*h)
xList.append(cx)
yList.append(cy)
#print(id, cx, cy)
self.lmList.append([id, cx, cy])
if draw:
cv2.circle(img, (cx, cy), 5, (255,0,255), cv2.FILLED)
xmin, xmax = min(xList), max(xList)
ymin, ymax = min(yList), max(yList)
bbox = xmin, ymin, xmax, ymax
if draw:
cv2.rectangle(img, (bbox[0]-20, bbox[1]-20), (bbox[2]+20, bbox[3]+20), (0, 255, 0), 2)
return self.lmList, bbox
def findDistance(self, p1, p2, img, draw=True):
x1, y1 = self.lmList[p1][1], self.lmList[p1][2]
x2, y2 = self.lmList[p2][1], self.lmList[p2][2]
cx, cy = (x1+x2)//2, (y1+y2)//2
if draw:
cv2.circle(img, (x1,y1), 15, (255,0,255), cv2.FILLED)
cv2.circle(img, (x2,y2), 15, (255,0,255), cv2.FILLED)
cv2.line(img, (x1,y1), (x2,y2), (255,0,255), 3)
cv2.circle(img, (cx,cy), 15, (255,0,255), cv2.FILLED)
length = math.hypot(x2-x1, y2-y1)
return length, img, [x1, y1, x2, y2, cx, cy]
def fingersUp(self):
fingers = []
# Thumb
if self.lmList[self.tipIds[0]][1] < self.lmList[self.tipIds[0]-1][1]:
fingers.append(1)
else:
fingers.append(0)
# 4 Fingers
for id in range(1,5):
if self.lmList[self.tipIds[id]][2] < self.lmList[self.tipIds[id]-2][2]:
fingers.append(1)
else:
fingers.append(0)
return fingers
def main():
pTime = 0
cTime = 0
cap = cv2.VideoCapture(0)
detector = handDetector()
while True:
success, img = cap.read()
img = detector.findHands(img)
lmList = detector.findPosition(img)
if len(lmList) != 0:
print(lmList[1])
cTime = time.time()
fps = 1. / (cTime - pTime)
pTime = cTime
cv2.putText(img, str(int(fps)), (10,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,255), 3)
cv2.imshow("Image", img)
cv2.waitKey(1)
if __name__ == "__main__":
main()
Теперь создадим main.py
и подключим библиотеки:
from tkinter import E
import cv2
from HandTrackingModule import handDetector
from time import sleep
import numpy as np
import cvzone
from pynput.keyboard import Controller
Теперь можем подключить и настроить камеру:
cap = cv2.VideoCapture(0)
cap.set(3, 1280)
cap.set(4, 720)
При подключении камеры могут возникнуть ошибки, поменяйте 0
из `cap = cv2.VideoCapture(0)` на 1
или 2
.
Добавим поиск руки, нашу будущую клавиатуру и клавиатурный контроллер:
detector = handDetector(detectionCon=0.8)
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 = ""
keyboard = Controller()
По желанию список keys
можно расширить, например цифрами. Я не буду добавлять их. т.к. статья несет информационный характер.
< - будет backspace'ом
Создадим функцию для отображения клавиатуры на экране:
def drawALL(img, buttonList):
imgNew = np.zeros_like(img, np.uint8)
for button in buttonList:
x, y = button.pos
w, h = button.size
cvzone.cornerRect(imgNew, (x, y, w, h), 20, rt=0)
cv2.rectangle(imgNew, button.pos, (x + w, y + h), (255, 0, 255), cv2.FILLED)
cv2.putText(imgNew, button.text, (x + 20, y + 65), cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255), 4)
out = img.copy()
alpha = 0.5
mask = imgNew.astype(bool)
out[mask] = cv2.addWeighted(img, alpha, imgNew, 1-alpha, 0)[mask]
return out
Создаем простой класс Button. Этот класс будет отвечать за позицию, размер и текст каждой кнопки.
class Button():
def __init__(self, pos, text, size=[85, 85]):
self.pos = pos
self.size = size
self.text = text
Теперь каждой кнопке присваиваем позицию, ее размеры и текст, который она будет печатать:
buttonList = []
for i in range(len(keys)):
for j, key in enumerate(keys[i]):
buttonList.append(Button([100 * j + 50, 100 * i + 50], key))
Запускаем бесконечный цикл (можно добавить остановку), отображаем клавиатуру и ищем руку в кадре:
while True:
success, img = cap.read()
img = cv2.flip(img, 1)
img = detector.findHands(img)
lmList, bboxInfo = detector.findPosition(img)
img = drawALL(img, buttonList)
Рассмотри теперь изображение, где показаны Hand Land Marks
:
![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/dfe/214/f83/dfe214f830c95ffd8335589cd4dd91bf.png)
Если рука в кадре, то можно считывать положения пальцев, точнее только указательного пальца, который будет служить курсором:
if lmList:
for button in buttonList:
x, y = button.pos
w, h = button.size
if x < lmList[8][1] < x + w and y < lmList[8][2] < y + h:
cv2.rectangle(img, button.pos, (x + w, y + h), (175, 0, 175), cv2.FILLED)
cv2.putText(img, button.text, (x + 20, y + 65), cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255), 4)
l, _, _ = detector.findDistance(8, 12, img, draw=False)
Теперь мы можем определять на какой кнопке находится указательный палец. Осталось реализовать клик
и вывод текста.
Если считать, что кликом
будет соединение указательного и среднего пальцев, то значит нам нужно считывать расстояние между точками 8
и 12
.
if l < 40:
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, (255, 255, 255), 4)
if button.text == "<":
finalText = finalText[:-1]
keyboard.press('\010')
else:
finalText += button.text
keyboard.press(button.text)
sleep(0.15)
Если клик
не работает и вы не можете использовать клавиатуру, тогда замените 40
на большее значение, например на 50
, и попробуйте снова:
if length < 40
Эта проблема возникает из-за того, что у каждого человека руки разные и расстояние между пальцами отличается?.
Если клик был совершен, то кнопка меняет свой цвет на зеленый (на мгновение). В специальном поле и в любом редакторе появляется символ, указанный на кнопке.
И финальный этап:
cv2.rectangle(img, (50, 710), (700, 610), (175, 0, 175), cv2.FILLED)
cv2.putText(img, finalText, (60, 690), cv2.FONT_HERSHEY_PLAIN, 5, (255, 255, 255), 5)
cv2.imshow("Keyboard", img)
cv2.waitKey(1)
Здесь мы возвращаем кнопке первоначальный цвет, создаем поле, в котором будет выведен текст.
Можем проверять?:
https://github.com/paveldat/virtual_keyboard/blob/main/img/result_notepad.gif