В данной статье разберемся, как написать виртуальную клавиатуру, используя 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 :

Если рука в кадре, то можно считывать положения пальцев, точнее только указательного пальца, который будет служить курсором:
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