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

Комментарии (4)


  1. naim
    22.11.2023 07:31
    +2

    Спасибо огромное за статью !


  1. rm-hbr
    22.11.2023 07:31
    +4

    Спасибо за статью. Была у меня похожая идея, только для слепой печати. Для этого камеру нужно направлять на клавиатуру и когда проводишь пальцы над клавишами, то на экране отображать над какими именно клавишами сейчас находятся пальцы в данный момент. Таким образом можно не оглядываясь на клавиатуру знать какую кнопку будешь нажимать...

    Если кому-то интересно было бы пользоваться, отпишитесь под комментом или поставьте плюс. Вдруг будет востребованно


    1. Pavel_Dat Автор
      22.11.2023 07:31

      Мне было бы интересно почитать тоже!


  1. Pavel_nobranch
    22.11.2023 07:31

    Мега спасибо за статью. Для моего проекта самое то.