Controlando jogo dino com Python e OpenCV

Desenvolver projetos é uma forma bem divertida de adquiri novas habilidades, então aqui vai mais um projetor para aprendermos mais sobre visão computacional.

Com certeza você conhece o jogo do dinossauro do chrome, tem alguns projeto de IA para jogar esse jogo.

Aqui vamos jogar usando gestos da mão. A pouco tempo, capturar pontos da mão era uma tarefa muito passado, restrita a GPUs. Porém, um projeto Google parece resolver esse problema, o mediapipe.

O mediapipe é um framework que usa a tecnologia xnnpack de aceleração gráfica, usando modelos de tensorflow lite é possivel obter possível obter um desempenho incrível.

Existem alguns exemplos no mediapipe, dentre eles um detector de pontos da mão.

landmarks

Landmarks (marcos) são modelos criados para detectar conjuntos de pontos relacionados.

Fonte: https://github.com/google/mediapipe/blob/master/docs/solutions/hands.md#static_image_mode

O mediapipe combina dois modelos, um para detectar a mão e outro para detectar os conjuntos de 21 pontos.

Fonte: https://github.com/google/mediapipe/blob/master/docs/solutions/hands.md#max_num_hands

Descrição do projeto

Utilizamos apenas os pontos 4 e 8 detectados para interagir com jogo. Calculamos a distância entre eles. Quando a distância é menor que 20 passamos o comando para o dinossauro se manter abaixado.

Quando a distância é maior que 80 geramos um evento de pressionar a tecla espaço (pular).

Para emular o teclado usamos pyautogui. OpenCv foi usado para obter fluxo de vídeo e pré-processamento.

Instalando pacotes

Todas as dependências podem ser instalada diretamente via Pip.

Com python3 e pycharm configurado. Basta abrir o projeto, criar um ambiente virtual e instalar os pacotes abaixo (Veja aqui) .

  • opencv-python
  • mediapipe
  • pyautogui

Codando

Primeiro passo, importar os pacotes utilizados. PRESSED_SPACE e PRESSED_DOWN são flags que usamos para não executar eventos do teclado desnecessário.

import numpy as np
import cv2
import pyautogui
import mediapipe as mp
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
PRESSED_SPACE=False
PRESSED_DOWN=False

Por algum motivo o pyautogui estava demorando um pouco para executar o primeiro evento do teclado então coloquei um evento no início na linha 9. Aqui também instanciei um objeto do mediapipe que chamei de hands.

As flags max_num_hands=1 limita o total de mão detectado para apenas uma

static_image_mode=False, usa um rastreador, assim não será executado o detector para todo frame, isso deixa os movimentos mais suaves, aumenta a desempenho. Por outro lado, é um pouco menos preciso.

pyautogui.press("space")
cap = cv2.VideoCapture(0)
with mp_hands.Hands(
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5,
    max_num_hands=1,
    static_image_mode=False) as hands: 

Na linha 16 Iniciamos o loop de processamento. A image é convertida para RGB e passada para o pipeline de processamento do mediapipe.

  while cap.isOpened():
    success, image = cap.read()

    if not success:
      continue
    image=cv2.flip(image, 1)
    image_RGB = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    image.flags.writeable = False

    results = hands.process(image_RGB)

Na linha 27 verificamos se encontrou uma mão. Se verdade, percorremos o objeto results.multi_hand_landmarks para obter os pontos.

Nas linhas pegamos pontos que precisamos e convertamos para uma tupa de inteiros.

    if results.multi_hand_landmarks:
        image_height, image_width, _ = image.shape
        annotated_image = image.copy()
        for hand_landmarks in results.multi_hand_landmarks:

            dedao_x=hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP].x * image_width
            dedao_y=hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP].y * image_height
            dedao=(int(dedao_x),int(dedao_y))
            indicador_x = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].x * image_width
            indicador_y=hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].y * image_height
            indicador=(int(indicador_x),int(indicador_y))

     

Calculamos a distância entre os pontos. Criei também uma tupla COLOR, inicialmente como verde (0,255,0) esse é o caso em que nem um evento de teclado é disparado.

Quando a distância é maior que 80 COLOR é alterado para azul (255,0,0) e PRESSED_SPACE recebe True, isso evita que múltiplos eventos da tecla espaço seja executado.

O comando pyautogui.press invoca um evento de pyautogui.keyDown() seguido de pyautogui.keyUp. Isso corresponde ao evento de pressionar e soltar a tecla.

Para manter o dinossauro abaixado, fazemos uso dessas duas funções ao invés de pyautogui.press()

   distancia=int(np.sqrt((indicador_x-dedao_x)**2+(indicador_y-dedao_y)**2))

            COLOR=(0,255,0)
            if distancia>80:
                COLOR=(255,0,0)
                if not PRESSED_SPACE:
                    print("tecla")
                    pyautogui.press("space")
                    PRESSED_SPACE=True
            else:
                PRESSED_SPACE=False
                if distancia<20:
                    if not PRESSED_DOWN:
                        print("tecla")
                        pyautogui.keyDown('Down')
                        PRESSED_DOWN=True
                    COLOR=(0,0,255)
                else:
                    if PRESSED_DOWN:
                        print("tecla")
                        pyautogui.keyUp('Down')
                        PRESSED_DOWN=False

E por fim o resultado é desenhado na imagem

            cv2.line(image, (dedao), (indicador), COLOR, 2, cv2.LINE_AA)
            cv2.circle(image,indicador,4,(255,0,255),2,cv2.LINE_AA)
            cv2.circle(image,dedao, 4, (255, 0, 0), 2, cv2.LINE_AA)

            mp_drawing.draw_landmarks(image, hand_landmarks, mp_hands.HAND_CONNECTIONS)
    cv2.imshow('visioncompy', image)
    if cv2.waitKey(30) & 0xFF == 27:
      break
cap.release()

Para interagir com o jogo, basta abrir chrome://dino (seu chrome pode substituir chrome:// por http:// isso não funcionara).

A pagina do chrome precisa estar em primeiro plano (secionada, pois evento de teclado não é processado por programas em segundo plano).

Conclusão

Aqui está um video de exemplo do que acabamos de construir.

Você pode baixar ao código fonte completo aqui

Fique a vontade para usar os campos de perguntas abaixo. Me siga no Instagram @elton.py para mais informação sobre visão computacional.

Referência:

Mediapipe: https://github.com/google/mediapipe/blob/master/docs/solutions/hands.md

One Reply on “Controlando jogo dino com Python e OpenCV”

  1. Bom dia Elton, to fazendo meu tcc da faculdade e vou utilizar a biblioteca do mediapipe hands para fazer reconhecimento de das mãos para fazer o abcdario de libras, e gostaria de fazer uma pergunta pra vc. Vc sabe se tem alguma forma de vez de eu abrir o video para fazer o reconhecimento da mão eu abrir uma imagem em png e trabalhar as cordenadas em cima dela e depois salvar em txt para eu jogar dentro de um treinamento de uma rede neural?

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *