Introduction: Haar Cascade - Python + OpenCV, Treinando E Detectando Objetos

Este tutorial irá descrever como funciona o processo Haar Cascade de detecção de objetos e irá mostrar como treinar o seu algoritmo para que detecte um objeto desejado e após treinar como fazer um script para detectá-lo através de uma webcam.

De acordo com o Site do OpenCV temos que o Método Haar Cascade é um método eficaz de detecção de objetos proposto por Paul Viola e Michael Jones em seu artigo, "Rapid Object Detection using a Boosted Cascade of Simple Features" em 2001. É uma abordagem baseada em Machine Learning, em que uma função cascade é treinada com o usp de muitas imagens positivas e negativas. É então usado para detectar objetos em outras imagens.

Fonte: https://docs.opencv.org/3.3.0/d7/d8b/tutorial_py_...

Inicialmente, o algoritmo precisa de muitas imagens:

- Positivas:

Imagens do objeto que se deseja detectar, contém diferentes representações do objeto que se deseja detectar sob diferentes perspectivas, condições de iluminação, tamanhos e etc.

- Negativas:

Imagens sem o objeto que se deseja detectar.Contém tudo o que não se deseja detectar com o modelo.

A imagem deste

Fonte da imagem desta seção: http://melvincabatuan.github.io/Object-Detection/

Step 1: Preparo Do Ambiente

Serão utilizados neste tutorial os seguintes programas:

- Python 2.7.14 (Esta versão já vem como pip Instalado);

- Numpy (Numerical Python);

- OpenCV último release estável disponível.

Como já há o pip instalado nesta versão do Python será possível baixar os pacotes Python necessários.

Para começarmos devemos criar o diretório do projeto como o nome que desejar. Dentro do Diretório devem ser criadas os seguintes diretórios. Esta estrutura será utilizada para quando for feito o treinamento com apenas uma imagem do objeto que se deseja detectar:

- data (onde será salvo o resultado do treinamento do algoritmo, arquivo XML que será utilizado para detecção de objetos);

- positivas (imagens do objeto que se deseja detectar, na resolução 50 x 50 pixesl e em escala de cinza);

- negativas (imagens negativas, imagens que não contém o objeto que se deseja detectar);

- feias (imagens com erro de que não existem mais nos links onde seriam baixadas); - info (amostras criadas com base nas imagens negativas e positivas).

Esta estrutura será utilizada para quando for feito o treinamento com mais de uma imagem do objeto que se deseja detectar:

- Será feita uma pasta info para cada imagem que se deseja detectar.

Step 2: Baixando O Dataset De Imagens Negativas

Este passo consiste em baixar o dataset de imagens negativas, são necessárias algumas centenas de imagens negativas para que o treinamento do algoritmo fique preciso e que a detecção funcione com a precisão desejada. Baixar esta quantidade de imagens manualmente é completamente inviável, para esta finalidade já existe um site que transforma imagens da Web em suas respectivas URLs para que você apenas utilize no seu script e as baixe.

Um Exemplo de Datasets é o que pode ser visto no link abaixo:

http://image-net.org/api/text/imagenet.synset.get...

Você pode acessar o site:

http://image-net.org

Clicar em Download -> Image URLs e então na página que se abrirá haverá uma explicação de como chegar à página com a lista de URLs como a mostrada acima. Pode ser feita pesquisa de qualquer coisa que você desejar utilizar como imagem negativa, lembre que a pesquisa deve ser feita em inglês para maior assertividade.(conforme imagem desta seção).

Abaixo o Script em Python para fazer o download:

import urllib

import numpy as np

import cv2

import os

link_imagens_negativas = 'http://image-net.org/api/text/imagenet.synset.geturls?wnid=n07942152' urls_imagens_negativas = urllib.urlopen(link_imagens_negativas).read().decode()

if not os.path.exists('negativas'):

os.makedirs('negativas')


numero_imagem = 1


for i in urls_imagens_negativas.splitlines():

try:

print(i)

urllib.urlretrieve(i, "negativas/"+str(numero_imagem)+".jpg")

img = cv2.imread("negativas/"+str(numero_imagem)+".jpg",cv2.IMREAD_GRAYSCALE)

imagem_redimensionada = cv2.resize(img, (100,100))

cv2.imwrite("negativas/"+str(numero_imagem)+".jpg",imagem_redimensionada)

numero_imagem += 1


except Exception as e:

print(str(e))

O script pegará a lista de imagens do site citado anteriormente e irá separar cada URL em uma linha e irá ler linha por linha, transformar em imagem e irá colocar imagem por imagem em escala de cinza e as redimensionará para a escala de 100 x 100 pixels, lembrando que quanto maior o tamanho das imagens mais demorado será o processo de treinamento e criação de amostras para o processo de treinamento do algoritmo de reconhecimento.

É interessante que tenhamos para um resultado satisfatório um número grande de imagens (mais de mil e quinhentas já é considerado um número satisfatório), no caso você deverá verificar qual o número da última imagem no diretório e para baixar mais imagens sem sobrescrever as outras:

- Substituir o valor inicial da variável numero_imagem para o valor da última imagem do diretório + 1;

- Substituir o link da variável link_imagens negativas para outro link de imagens no formato de URL do site citado anteriormente.

Step 3: Removendo Imagens Falhas

Às vezes a imagem referente ao link listado na página se encontra indisponível pois não existe mais ou foi removida daí são baixadas algumas imagens de aviso informando que a página/imagem não existem mais. Estas imagens aqui vamos chamar de Uglies ou Imagens Feias.

Exemplos de imagens feias estão localizados no início desta seção.

Você localizará apenas uma de cada imagem que esteja feia e moverá para o diretório "feias".

À partir disso será utilizado um script para que sejam removidas as imagens do seu dataset que são iguais às imagens feias.

import numpy as np

import cv2

import os

igual = False

for file_type in ['negativas']:

for img in os.listdir(file_type):

for feia in os.listdir('feias'):

try:

caminho_imagem = str(file_type)+'/'+str(img)

feia = cv2.imread('feias/'+str(feia))

pergunta = cv2.imread(caminho_imagem)

if feia.shape == pergunta.shape and not(np.bitwise_xor(feia,pergunta).any()):

print('Apagando imagem feia!')

print(caminho_imagem)

os.remove(caminho_imagem)

except Exception as e:

print(str(e))

Este script removerá uma série de imagens que corresponderão às imagens que não desejamos utilizar no treinamento do algoritmo.

Step 4: Renomeando Arquivos Da Pasta De Images

Este não é um passo necessário mas deixa a prática mais elegante.

Com a remoção das imagens feias os nomes dos arquivos não seguirão uma sequencia então vamos renomeá-los numa sequência numérica novamente para isto utilizaremos um script que vamos colocar dentro do diretório onde as imagens se encontram, no caso, "negativas".

Podemos selecionar todas as imagens dentro da pasta e renomeá-las via Windows mesmo ou Linux, selecionando e apertando a tecla F2 irá renomear todas as imagens para o mesmo nome com um incremento numérico. O script abaixo não está tratando quando tenta-se renomear uma imagem para o mesmo nome que ela já possui.

import os

for i, f in enumerate(os.listdir(".")):

f_new = '{}.jpg'.format(i)

os.rename(f, f_new)

print '{}.'.format(i), f, '->', f_new

Após este passo as imagens que estão no diretório "negativas" estarão renomeadas com números em ordem crescente.

Step 5: Gerando Lista De Imagens Negativas

Deve ser gerada uma lista da localização das imagens presentes no diretório de imagens negativas para que esta lista seja utilizada para criar amostras e treinar o algoritmo para gerar o arquivo com as características que devem ser detectadas.

import urllib

import numpy as np

import cv2

import os


for file_type in ['negativas']:

for img in os.listdir(file_type):

if file_type == 'negativas':

line = file_type+'/'+img+'\n'

with open('bg.txt','a') as f:

f.write(line)

elif file_type == 'positivas':

line = file_type+'/'+img+' 1 0 0 150 150\n'

with open('info.dat','a') as f:

f.write(line)

Step 6: Criando Amostras

De posse dos arquivos de imagens negativas, da imagem positiva e da lista de imagens negativas que foi gerado no passo anterior podemos criar amostras de imagens positivas para serem utilizadas no treinamento da imagem.

Abaixo a sintaxe do comando que deve ser utilizado para que sejam geradas as amostras de imagens positivas:

opencv_createsamples -img NOME_DO_ARQUIVO.jpg -bg ARQUIVO_LISTA_DE_IMAGENS_NEGATIVAS.txt -info DIRETÓRIO_SAÍDA/ARQUIVO_LISTA.lst -pngoutput DIRETÓRIO_SAÍDA -maxxangle ÂNGULO_MÁXIMO_EIXO_X -maxyangle ÂNGULO_MÁXIMO_EIXO_Y -maxzangle ÂNGULO_MÁXIMO_EIXO_Z -num NÚMERO_DE_IMAGENS_NEGATIVAS

No exemplo da sintaxe do comando de criar amostra temos a imagem positiva como POS.jpg e 5500 imagens negativas.

opencv_createsamples -img POS.jpg -bg bg.txt -info info/info.lst -pngoutput info -maxxangle 0.5 -maxyangle 0.5 -maxzangle 0.5 -num 5500


OBSERVAÇÃO: Para treinar para mais de uma imagem positiva devem ser geradas amostras para cada. E deve ser gerado um diretório info para cada imagem positiva. (info1, info2 e etc).

Após criada a lista acima devemos gerar um vetor com as imagens e a posição da imagem positiva em cada uma das imagens negativas. Será criado um arquivo de vetores.

opencv_createsamples -info DIRETÓRIO/ARQUIVO_LISTA.lst -num NÚMERO_DE_IMAGENS_NEGATIVAS -w LARGURA_DA_AMOSTRA -h ALTURA_DA_AMOSTRA -vec ARQUIVO_VETORES

No exemplo da sintaxe do comando de criar amostra temos 5500 imagens negativas, altura e largura máxima 20 pixels e arquivo de saída positives.vec.

opencv_createsamples -info info/info.lst -num 5500 -w 20 -h 20 -vec positives.vec


OBSERVAÇÃO: Para treinar para mais de uma imagem positiva devem ser gerados vários vetores, um para cada imagem positiva e depois devemos fundir os vetores gerados.

Para fundir os vetores há um script do GitHub: https://github.com/wulfebw/mergevec

A sintaxe de uso é:

python mergevec.py -v DIRETÓRIO_VETORES -o ARQUIVO_VETOR_SAÍDA

Step 7: Treinando O Algoritmo

Feitos os passos anteriores que são rápidos (exceto o download das imagens mas que também é relativamente rápido perto deste) está na hora do passo mais demorado de todos que é a parte do treinamento do algoritmo. Este passo dependendo do número de imagens negativas/amostras utilizadas pode demorar dias. Como referência tenha que para 1900 imagens e apenas uma positiva é um processo que leva em torno de 2 horas quando desejamos uma taxa de falsos alarmes de 0.5%. Os parâmetros podem fazer com que o tempo demore bastante.

Para realizar o treinamento da imagem deve ser utilizado o seguinte comando:

opencv_traincascade -data PASTA_DESTINO -vec VETOR.vec -bg bg.txt -numPos (NÚMERO_DE_IMAGENS - 200) -numNeg METADE_DO_NUMPOS -numStages NÚMERO_DE_ESTAGIOS_DO_TREINO -w LARGURA_DA_AMOSTRA -h ALTURA_DA_AMOSTRA

Seguindo o Exemplo:

opencv_traincascade -data data -vec positives.vec -bg bg.txt -numPos 5000 -numNeg 2500 -numStages 10 -w 20 -h 20

Lembrando que quanto maior o número de estágios mais precisa será a detecção mas também mais demorado será o processo de treino.

Durante o treino serão gerados diversos arquivos .XML na pasta data um arquivo referente à cada estágio e um aos parâmetros.

Após o treino será gerado um arquivo chamado cascade.xml que será utilizado para a detecção.


OBSERVAÇÃO 1: Caso haja problema em algum estágio, basta utilizar novamente o comando de treinamento que os estágios que já foram feitos permanecerão.

OBSERVAÇÃO 2: Caso esteja fazendo para mais de uma imagem positiva, utilize o vetor resultante da união mencionada no passo anterior e no campo de -numPos utilize o número de imagens negativas vezes número de positivas e subtraia entre 200 e 500 para cada imagem positiva.

Step 8: Detectando Objetos

Deve ser criado um script para detectar objetos, uma vez que foi feito o treinamento do algoritmo e se tem um arquivo .XML referente à detecção do objeto que se deseja. Segue script:

import cv2

import sys

# Define o número da Webcam

NUM_WEBCAM = 0

# Responsável por ler arquivo XML

if len(sys.argv) > 1:

XML_PATH = sys.argv[1]

else:

print "Erro: Caminho para XML não definido"

sys.exit(1)

# Inicializa o Classificador Cascade

faceCascade = cv2.CascadeClassifier(XML_PATH)

# Inicializa a Webcam

vc = cv2.VideoCapture(NUM_WEBCAM)

# Verifica se a Webcam está funcionando

if vc.isOpened():

# Tentar pegar o primeiro frame

retval, frame = vc.read()

else:

# Fechar o programa

sys.exit(1)


i = 0

objetos =[]

# Se a Webcam estiver lendo, repetir indefinidamente

while retval:

# Define o frame que será mostrado

frame_show = frame

if i % 1 == 0:

# Converte o frame para escalas de cinza e descarta os dados de cores

frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# Detecta objetos e retorna o array de objetos

objetos = faceCascade.detectMultiScale(

frame,

scaleFactor=1.2,

minNeighbors=20,

minSize=(50, 50),

flags=cv2.CASCADE_SCALE_IMAGE

)

# Desenha um retângulo em torno do objeto

for (x, y, w, h) in objetos:

cv2.rectangle(frame_show, (x, y), (x+w, y+h), (0, 0, 255), 2)

# Mostra o Frame ao usuário

cv2.imshow("Foto", frame_show)

# Lê o próximo frame

retval, frame =vc.read()

# Fecha o programa se o usuário apertar o botão ESC

if cv2.waitKey(1) == 27:

break

i += 1

Após criar o script salvá-lo como detecta.py, por exemplo, para utilizá-lo basta seguir a sintaxe abaixo:

python detecta.pyARQUIVO_XML

Step 9: Usando O Código - Detectar Ford New Fiesta

Foi treinado um algoritmo para reconhecer um Ford New Fiesta como o da Foto desta seção. Foi utilizada apenas uma imagem para treinar então irá detectar apenas o Ford New Fiesta idêntico ao da foto. O treinamento utilizando apenas uma imagem é ideal para alguma empresa que quer detectar quando vezes a sua logomarca aparece, pois não há muitas variações exceto ângulos.

Foi anexado à este passo o arquivo XML gerado e um vídeo demonstrando o uso deste arquivo XML. Note que a resolução da Webcam faz uma grande diferença no processo dedetecção.