Reconhecimento de Gestos

As bases para identificação de gestos em um jogo jokenpo (pedra-papel-tesoura) usando Visão computacional



Projetei este pequeno aplicativo utilizando python e opencv2 (v 3.4.5). Opencv foi eleito por compatibilidade com inúmeros dispositivos, multiplicidade de ambientes e por possuir fáceis recursos para captura da câmera, apresentação das imagens e manipulação gráfica da matriz bitmap.

Para melhor situar o caro leitor, o opencv foi desenvolvido em 2000 pela Intel, é livre regida sob a licença BSD. Nos últimos anos com atenções voltadas a área de machine learning e algorítimos de reconhecimento, recebeu importantes contribuições que enriqueceram suas bibliotecas. Originalmente projetada em C, foi migrada para C++ e possui implementação em Python via wrappers.

Instalando o OpenCv


Pré-requisitos: numpy, python / Recomendado: python 3.6.

No Ubuntu:

# Atualizamos o ambiente
sudo apt-get update
sudo apt-get install build-essential cmake unzip pkg-config
sudo apt-get install libjpeg-dev libpng-dev libtiff-dev
sudo apt-get install libxvidcore-dev libx264-dev

# Instalamos o OpenCv
sudo apt-get install git
git clone https://github.com/opencv/opencv.git
mkdir build
cd build
cmake ../
# Instalamos dependencias e bibliotecas
sudo apt-get install python-dev python-pip python3-dev python3-pip
# Instalamos o adds-on de contribuicao
git clone https://github.com/opencv/opencv_contrib.git
cd opencv_contrib
git checkout 3.4.5
cd ..
make
sudo make install

# fast-way sem garantias:
sudo apt-get install python-opencv
sudo pip install numpy
sudo pip install opencv2
# 1,2,3 .. testando
$ python
> import cv2 as cv
> print(cv.__version__)
Tutorial alternativo completo aqui.

No Windows:

.. a partir dos binários pré-compilados:


  1. Baixe o pacote OpenCv diretamente do sourceforge
  2. Copie e cole o cv2.pyd no diretório de lib\site-packages do Anaconda.
  3. Adicione %OPENCV_DIR%\bin ao Path, se já não estiver para que o Anaconda saiba onde encontrar o utilitário FFMPEG.
  4. Faça alguns testes para confirmar que o OpenCV e o FFMPEG estão funcionando:

.. compilando o source-code:



  1. Baixe o Visual Studio Build Tools
  2. Baixe e instale o CMake
  3. instale o numpy via pip ou conda (pip install numpy)
  4. Baixe o código fonte diretamente do source-forge, descompacte-o (\opencv) e crie uma nova pasta para o build (\opencv\mybuild)
  5. Abra o CMake-GUI e configure:Browse Source (\opencv)
  • Browse Build (\opencv\mybuild)
  • Clique em Configure, selecione Visual Studio 11, e Finish
  • Após a análise, desmarque a opção ENABLE_SOLUTION_FOLDERS.
  • Se possuir GPU e desejar utilizá-la, habilite BUILD_opencv_gpuwarping
      6. Abra o CMake-GUI e configure:Browse Source (\opencv)
      7. No gerenciador de soluções, clique com o botão direito do mouse em Solution e execute o Build.

Pegue um café e aguarde.

Python

A aplicação funcionou bem no Python 2 e 3. Mas para evitar erros de execução inesperados, recomendo a versão 3.6, que é onde concluí o projeto.

A parte mais chatinha realmente é a instalação do opencv, infelizmente. Uma vez realizada porém é possível avançar alguns passos em direção ao processamento de imagens, entendimento de algorítimos avançados que possuem aplicação variada, e tirará sua mente do limbo CRUD, webservices, databases, etc.

Projeto e Resultados

De antemão, peço desculpas pela qualidade do vídeo. Dá para notar a funcionalidade em execução que é o mais importante. Mais que isso: logo mais você mesmo estará executando-o em sua máquina, o código fonte está disponível no github.




Idéia geral do processo 


  • Processamento quadro a quadro
  • Determinar um espaço máximo para a captura da foto, para eliminar objetos diferentes da mão;
  • Eliminar o máximo de ruidos da foto e limiarizá-la em preto e branco;
  • Usar a função de convecidade do OpenCV, capturando o contorno da imagem.
  • Usar o calculo de distância, área e ângulos para estimar qual a gesto a mão está realizando

Código - Inicialização

Inicialmente, realizamos a importação das bibliotecas e dizemos para o opencv que queremos que ele inicialize a conexão com a câmera conectada ao computador.

Se houver mais de uma câmera, por óbvio que deve-se mudar o indice do equipamento. Como eu tenho apenas a câmera built-in do notebook, o indice dela é o zero. Conforme abaixo:

import cv2
import numpy as np
import math
cap = cv2.VideoCapture("0")
while(cap.isOpened()):
    ret, img = cap.read()


No loop, que lê cada quadro, img é o que nos importa. É a imagem capturada pelo opencv. É com essa imagem que iremos trabalhar !

Escala de Cinzas

Antes de converter a imagem para preto e branco, temos de convertê-la para uma escala de cinza



Utilizando esta chamada ao OpenCv:

grey = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY)

Desfoque

A aplicação de desfoque (blur) gaussiano, é útil para eliminar ruidos, homogeneizar a foto e equilibrar melhor a distribuição dos cantos onde eles realmente existirem. Na prática um kernel gaussiano é aplicado em progressão a partir de cada ponto. Neste caso um kernel de tamanho 35, tornando-a menos sucetivel a erros na próxima etapa.


value = (35, 35)
blurred = cv2.GaussianBlur(grey, value, 0)




Binarização da imagem

A binarização da imagem ocorre mediante alguns parametros, como o range médio limiar esperado de equilibrio ao qual espera-se chegar. Variando o parâmetro, variamos o resultado, conforme podemos ver abaixo:

O OpenCv utiliza o treshhold baseado na binarização de Otsu's Binarization, criado pelo matemático Nobuyuki Otsu que dá nome ao mesmo e é usado para prever o limiar de imagem baseado em agrupamento de pixels, resultando em uma imagem binária. Espero um equilibrio médio, pegando a partir de 127 até 255.


_, thresh1 = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

O Contorno

A partir daqui, peço para o opencv realizar o calculo do contorno e a superficie da área do contorno capturado:
image, contours, hierarchy = cv2.findContours(thresh1.copy(), \ cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) cnt = max(contours, key = lambda x: cv2.contourArea(x)) hull = cv2.convexHull(cnt, returnPoints=False)

Na minha imagem de corte, no canto, apenas minha mão é o objeto em cena, então o maior objeto em cena será ela, o restante serão ruidos.
defects = cv2.convexityDefects(cnt, hull) count_defects = 0

Então calculo a convecidade do ângulo do contorno.


defects = cv2.convexityDefects(cnt, hull) count_defects = 0

São as linhas em vermelho que me interessam, e é a partir dela que determinarei qual gesto a mão está realizando.



Um pouco de trigonometria e aritimética nos permitem dizer o grau dos ângulos formados pelas linhas. Me permitindo distinguir entre mão fechada e aberta. E o número de quebras dos ângulos me permitirá dizer quantos dedos estão sendo mostrados (2 para tesoura, 5 para papel).


defects = cv2.convexityDefects(cnt, hull)
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    a = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2)
    b = math.sqrt((far[0] - start[0])**2 + (far[1] - start[1])**2)
    c = math.sqrt((end[0] - far[0])**2 + (end[1] - far[1])**2)
    # regra do cosceno
    angle = math.acos((b**2 + c**2 - a**2)/(2*b*c)) * 57
    # ignora angulos menores que 90
    if angle <= 90:
        count_defects += 1
        cv2.circle(crop_img, far, 1, [0,0,255], -1)
    count_large = 0

O artigo ficou um pouco longo, sinto se ficou cansativo. Mas para detalhar tantos pequenos passos é importante para mostraros pequenos truques utilizados na visão computacional. A beleza desta solução algorítimica é que ela é extremamente rápida.

Novamente, o código fonte está disponível no github. Mande suas sugestões e melhorias ! Será grande a satisfação em conectar conhecimentos.


Grande abraço !