Технологии распознавания жестов в настоящее время становятся все более популярными благодаря стремительному развитию проектов виртуальной и дополненной реальности. В одной из предыдущих статей на нашем сайте мы уже рассматривали распознавание жестов с помощью Raspberry Pi и библиотек Tensorflow и Keras на основе известной игры "камень, ножницы, бумага". В этой же статье мы рассмотрим распознавание жестов с помощью библиотеки MediaPipe, написанной на Python. С помощью распознанных жестов руки пользователя мы будем управлять работой видеоплеера, запущенного на плате Raspberry Pi.
Всего в данном проекте мы будем распознавать 6 жестов: Open & Close Fist (раскрытие и закрытие кулака), Up, Down, Left, Right – движения руки вверх, вниз, влево, вправо. Открытие и закрытие кулака будут использоваться для запуска воспроизведения видео и постановки его на паузу. Движения руки вверх и вниз будут использоваться для увеличения и уменьшения громкости звука. Движения руки вправо и влево будут использоваться для быстрой перемотки видео назад и вперед.
Ранее на нашем сайте мы рассматривали управление видеоплеером с помощью жестов на основе платы Arduino.
Необходимые компоненты
- Плата Raspberry Pi 4 (купить на AliExpress).
- Камера для Raspberry Pi (купить на AliExpress).
В данном проекте мы будем использовать плату Raspberry Pi 4 с модулем камеры с установленными на нее библиотеками OpenCV и MediaPipe.
Что такое MediaPipe
MediaPipe – это фреймворк для создания кросс-платформенных (Android, iOS, web, оконечные устройства) мультимодальных (видео, аудио, временные последовательности данных) конвейеров (pipelines) машинного обучения (Machine Learning), который состоит из алгоритмов машинного обучения, классического компьютерного зрения и обработки медиа информации (то есть декодирования видео). MediaPipe включает в себя пакеты, написанные на python и других языках программирования), для решения следующих задач:
- обнаружение объектов;
- обнаружение лиц;
- отслеживание рук (как одной, так и нескольких);
- оценка положения тела;
- сегментации волос.
Пакет MediaPipe доступен на PyPI для Linux, macOS и Windows. Используйте следующую команду для установки MediaPipe на плату Raspberry Pi 4:
1 |
sudo pip3 install mediapipe-rpi4 |
Если у вас нет Raspberry Pi 4, то на Raspberry Pi 3 пакет MediaPipe можно установить с помощью команды:
1 |
sudo pip3 install mediapipe-rpi3 |
Установка OpenCV на Raspberry Pi 4
Перед началом установки OpenCV обновите программное обеспечение платы Raspberry Pi до последней версии с помощью команды:
1 |
sudo apt-get update |
Затем установим необходимые дополнения, которые нам понадобятся для дальнейшей установки OpenCV.
1 2 3 4 5 6 |
sudo apt-get install libhdf5-dev -y sudo apt-get install libhdf5-serial-dev –y sudo apt-get install libatlas-base-dev –y sudo apt-get install libjasper-dev -y sudo apt-get install libqtgui4 –y sudo apt-get install libqt4-test –y |
Далее установим OpenCV на Raspberry Pi с помощью установщика pip.
1 |
pip3 install opencv-contrib-python==4.1.0.25 |
Установка PyAutoGUI на Raspberry Pi
PyAutoGUI – это кросс-платформенный модуль, написанный на Python, для автоматизации GUI (Graphical User Interface - графический интерфейс пользователя). Данный модуль позволяет вашему скрипту на Python управлять мышкой и клавиатурой чтобы автоматизировать взаимодействие с другими приложениями. PyAutoGUI работает на Windows, macOS и Linux. Его можно использовать в Python 2 и 3. Для установки PyAutoGUI на Raspberry Pi выполните команду:
1 |
pip3 install pyautogui |
Объяснение программы для Raspberry Pi
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Вначале программы нам необходимо импортировать пакеты OpenCV, MediaPipe и PyAutoGUI. MediaPipe будет использоваться для отслеживания положения рук, а OpenCV для обработки изображений. PyAutoGUI мы будем использовать для управления клавиатурой в соответствии с обнаруженными жестами пользователя.
1 2 3 |
import cv2 import mediapipe as mp import pyautogui |
Далее мы объявим две переменные. Переменная mp_drawing будет использоваться для извлечения всех необходимых утилит рисования из пакета MediaPipe, а переменная mp_hands будет использоваться для импортирования модели отслеживания положения рук.
1 2 |
mp_drawing = mp.solutions.drawing_utils mp_hands = mp.solutions.hands |
После этого запрограммируем функцию под названием findPosition(). Как следует из ее названия, она будет использоваться для нахождения координат X, Y пальцев Index (указательного), Middle (среднего), Ring (безымянного) и Pinky (мизинца). Координаты всех пальцев будут сохраняться внутри переменной lmList[].
1 2 3 4 5 6 7 8 9 10 |
def fingerPosition(image, handNo=0): lmList = [] if results.multi_hand_landmarks: myHand = results.multi_hand_landmarks[handNo] for id, lm in enumerate(myHand.landmark): # print(id,lm) h, w, c = image.shape cx, cy = int(lm.x * w), int(lm.y * h) lmList.append([id, cx, cy]) return lmList |
Затем начнем трансляцию видеопотока с камеры Raspberry Pi с размерами кадра 720х640.
1 2 3 |
cap = cv2.VideoCapture(0) cap.set(3, wCam) cap.set(4, hCam) |
Далее мы создадим новый объект фида mediapipe чтобы получить доступ к модели отслеживания положения рук (Hand Tracking model), которую мы импортировали ранее. Также мы будем использовать два ключевых аргумента – минимальный доверительный уровень обнаружения и минимальный доверительный уровень отслеживания местоположения (mp_hands.Hands(min_detection_confidence=0.8, min_tracking_confidence=0.5)). Далее мы будем считывать видео кадры и сохранять их в переменной image.
1 2 |
while cap.isOpened(): success, image = cap.read() |
Изображение, которое мы будем получать из видеопотока, будет изначально в формате BGR. Поэтому мы сначала мы развернем его горизонтально чтобы удобнее было просматривать на дисплеях с горизонтальной ориентацией, и конвертируем его в формат RGB. Перезаписываемый флаг image установим в положение false.
1 2 |
image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) image.flags.writeable = False |
После этого мы будем передавать наше изображение в модель отслеживания положения рук (Hand Tracking model), а полученные с ее помощью результаты определения положения мы будем сохранять в переменной ‘results.’
1 |
results = hands.process(image) |
После того как распознавание жеста руки будет закончено, мы установим перезаписываемый флаг image в true и преобразуем изображение из формата RGB в формат BGR.
1 2 |
image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) |
Также, после получения результатов распознавания жеста, мы будем вызывать функцию mp_drawing чтобы нарисовать наши результаты распознавания на изображении и соединить их с утилитами для рисования (drawing utilities), которые мы импортировали ранее.
1 2 3 4 |
if results.multi_hand_landmarks: for hand_landmarks in results.multi_hand_landmarks: mp_drawing.draw_landmarks( image, hand_landmarks, mp_hands.HAND_CONNECTIONS) |
После этого мы будем вызывать функцию findPosition() чтобы считать идентификаторы и координаты всех обнаружений. Эти значения будут сохраняться в переменной lmList.
1 |
lmList = findPosition(image, draw=True) |
Теперь, когда мы будем знать координаты всех ключевых точек руки, мы будем использовать эту информацию для распознавания различных жестов руки и в первую очередь мы будем пытаться определить сжат или разжат кулак. Для этого мы будем сравнивать координаты кончиков пальцев [8, 12, 16, 20] и их средних точек [6, 10, 14, 19] и если кончики находятся ниже средних точек, то кулак сжат, и наоборот.
1 2 3 4 5 |
for id in range(1, 5): if lmList[tipIds[id]][2] < lmList[tipIds[id] - 2][2]: fingers.append(1) if (lmList[tipIds[id]][2] > lmList[tipIds[id] - 2][2]): fingers.append(0) |
В следующих строках кода мы будем вычислять общее количество определенных пальцев и сохранять его в переменной totalFingers.
1 2 |
totalFingers = fingers.count(1) print(totalFingers) |
И теперь, когда мы будем знать число пальцев, мы будем использовать эту информацию для проигрывания и постановки на паузу видео.
1 2 3 4 5 6 |
if totalFingers == 4: state = "Play" if totalFingers == 0 and state == "Play": state = "Pause" pyautogui.press('space') print("Space") |
Далее мы будем определять жесты движения руки влево, вправо, вверх и вниз. Чтобы распознать движения руки влево и вправо, мы сначала будем определять координату X указательного пальца и если она меньше 300, то это будет означать движение руки влево, а если она больше 400 – это будет означать движение руки вправо.
1 2 3 4 5 6 7 |
if totalFingers == 1: if lmList[8][1]<300: print("left") pyautogui.press('left') if lmList[8][1]>400: print("Right") pyautogui.press('Right') |
Аналогичным образом бы будем определять движение руки вверх и вниз на основе анализа координаты Y среднего пальца. Если она меньше 210 – это движение руки вверх, а если она больше 230 – это движение руки вниз.
1 2 3 4 5 6 7 |
if totalFingers == 2: if lmList[9][2] < 210: print("Up") pyautogui.press('Up') if lmList[9][2] > 230: print("Down") pyautogui.press('Down') |
Тестирование работы видеоплеера, управляемого жестами рук
Подключите модель камеры к плате Raspberry Pi как показано на следующем рисунке.
Удостоверьтесь в том, что камера работает корректно. После этого запустите программу проекта на выполнение, в результате чего на экране вы должны увидеть всплывающее окно, в которое будет транслироваться видеопоток с камеры. После этого вы можете осуществлять управление видеоплеером с помощью жестов руки. Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходный код программы на Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
import cv2 import mediapipe as mp import pyautogui mp_drawing = mp.solutions.drawing_utils mp_hands = mp.solutions.hands ################################## tipIds = [4, 8, 12, 16, 20] state = None Gesture = None wCam, hCam = 720, 640 ############################ def fingerPosition(image, handNo=0): lmList = [] if results.multi_hand_landmarks: myHand = results.multi_hand_landmarks[handNo] for id, lm in enumerate(myHand.landmark): # print(id,lm) h, w, c = image.shape cx, cy = int(lm.x * w), int(lm.y * h) lmList.append([id, cx, cy]) return lmList # For webcam input: cap = cv2.VideoCapture(0) cap.set(3, wCam) cap.set(4, hCam) with mp_hands.Hands( min_detection_confidence=0.8, min_tracking_confidence=0.5) as hands: while cap.isOpened(): success, image = cap.read() if not success: print("Ignoring empty camera frame.") # If loading a video, use 'break' instead of 'continue'. continue # Flip the image horizontally for a later selfie-view display, and convert # конвертирование BGR формата в RGB image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) image.flags.writeable = False results = hands.process(image) # рисуем аннотацию к управлению жестами на картинке image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) if results.multi_hand_landmarks: for hand_landmarks in results.multi_hand_landmarks: mp_drawing.draw_landmarks( image, hand_landmarks, mp_hands.HAND_CONNECTIONS) lmList = fingerPosition(image) #print(lmList) if len(lmList) != 0: fingers = [] for id in range(1, 5): if lmList[tipIds[id]][2] < lmList[tipIds[id] - 2][2]: #state = "Play" fingers.append(1) if (lmList[tipIds[id]][2] > lmList[tipIds[id] - 2][2] ): # state = "Pause" # pyautogui.press('space') # print("Space") fingers.append(0) totalFingers = fingers.count(1) print(totalFingers) #print(lmList[9][2]) if totalFingers == 4: state = "Play" # fingers.append(1) if totalFingers == 0 and state == "Play": state = "Pause" pyautogui.press('space') print("Space") if totalFingers == 1: if lmList[8][1]<300: print("left") pyautogui.press('left') if lmList[8][1]>400: print("Right") pyautogui.press('Right') if totalFingers == 2: if lmList[9][2] < 210: print("Up") pyautogui.press('Up') if lmList[9][2] > 230: print("Down") pyautogui.press('Down') #cv2.putText(image, str("Gesture"), (10,40), cv2.FONT_HERSHEY_SIMPLEX, # 1, (255, 0, 0), 2) cv2.imshow("Media Controller", image) key = cv2.waitKey(1) & 0xFF # если нажата клавиша `q`, то осуществляем выход из цикла if key == ord("q"): break cv2.destroyAllWindows() |