PythonJogaPong — Parte 1 Capturando a tela e extraindo informações
Esse post faz parte de uma série onde explico o projeto PythonJogaPong, que usa redes neurais para jogar o jogo clássico de atari Pong. Veja também as outras partes: Parte 2 Parte 3 Parte 4
Para o projeto iremos usar uma implementação de pong no browser, acessada por https://pong-2.com, mas obviamente você pode usar qualquer implementação de pong que quiser, desde que faça as adaptações necessárias no projeto.
O arquivo completo está disponível em: https://github.com/pilotorobo/pongplay/blob/master/screen_features.py
from grabscreen import grab_screen
import cv2
import numpy as np
Primeiro importamos 3 coisas:
- A biblioteca numpy utilizada para operações matemáticas mais avançadas como algebra linear.
- A biblioteca de visão computacional opencv (cv2) utilizada para manipulação e extração de dados de imagens.
- A função
grab_screen
(definida em https://github.com/pilotorobo/pongplay/blob/master/grabscreen.py) utilizada para capturar uma região determinada da tela.
A função get_screen
retorna uma região da tela da tela em escalas de cinza. Primeiro a função grab_screen
retorna a região definida pelos pontos x1=200, y1=200, x2=720+200 e y2=405+200. Colocamos os valores finais em forma de soma (720+200) devido a facilidade de modificar o tamanho da região capturada e seu offset, no caso a região de tamanho 720x405 com offset de 200 na horizontal e 200 na vertical será retornada. A função cvtColor
converte as cores da imagem capturada para escalas de cinza.
Essa função recebe a imagem da tela do jogo em escalas de cinza e retorna 6 valores dentro de um vetor: A posição horizontal e vertical da bola, a posição horizontal e vertical da primeira barra e a posição horizontal e vertical da segunda barra (que controlamos). Ela inicia chamando a função connectedComponentsWithStats
para capturar grupos de pixels conectados na imagem. Então usamos algumas heurísticas entre as linhas 8 e 20 para determinar a localização da bola do jogo e das barras dos jogadores:
- A bola do jogo costuma ser um quadrado perfeito, logo seu comprimento dividido pela sua altura deve ser aproximadamente 1. Como não possui furos ou buracos, a área ocupada pela figura (número de pixels ‘ativos’) é igual a área do quadrado que delimita a figura, logo a área dela deve ser igual ao comprimento x altura.
- Como as barras são um retângulos, a idéia é semelhante, a única diferença que com experimentos verificamos que a razão da altura pelo comprimento é aproximadamente 4.6.
Com isso em mão, calculamos para todos os elementos valores baseados nessas heurísticas, que tendem a ser 0 em seu valor absoluto caso seja a bola do jogo ou as barras. Então pegamos os elementos com esses valores mais próximos de 0 e escolhemos eles como os elementos do jogo. É um método relativamente frágil facilmente “enganavel”, mas como a imagem do jogo é bem regular e praticamente não existe ruído, esse método é suficiente.
Aqui combinamos as funções get_screen e get_object locations para mostrar a localização dos objetos na tela com círculos cinzas para fins de debug.
Por fim, ao executar o arquivo, capturamos a tela e as informações dos objetos do jogo; Em seguida fazemos uma stream do conteúdo da imagem capturada em uma tela auxiliar até que o programa seja fechado ou a tecla ‘q’ for pressionada.