quinta-feira, 3 de maio de 2018

Reconhecimento Facial na prática

Bem provável que você já tenha ouvido falar bastante deste termo. Não a toa, é hoje uma das aplicações muito utilizadas na área de visão computacional. Podendo ser aplicado em diversos eixos como sistemas de segurança, sistemas para, entrenenimento, etc. No entanto, trata-se de uma atividade não tão trivial quando estamos falando dos computadores, pois o mesmo não entende uma imagem como nós seres humanos, através de nossa visão compreendemos, para o computador, a imagem nada mais é do que um conjunto de pixels, onde se deve realizar cálculos matemáticos para extrair alguma informação, ou realizar algum processamento em uma imagem. Neste artigo vamos nos restringir a um case sobre reconhecimento facial utilizando alguns recursos da biblioteca DLib. Trata-se de uma biblioteca recente, que já contém ótimos exemplos de reconhecimento facial, detecção de objetos, etc. A biblioteca está disponível nas linguagem C++ e Python. Antes de comentar sobre o nosso projeto, vamos falar um pouco sobre dois algoritmos de reconhecimento facial muito popular, disponíveis no OpenCV, apesar de não ser o que estaremos utilizando no projeto, é interessante falarmos um pouco sobre eles, para fins de contextualização.
  • Algoritmo Eigenfaces: Esse algoritmo utiliza a técnica PCA (Principal Component Analysis - Análise dos Componentes Principais) para realizar a redução de dimensionalidade, com o intuito de passar informações mais discrimantes da face, para a avaliação do algoritmo no reconhecimento das faces. Por utilizar o PCA, estaremos trabalhando nesse algoritmo, com aprendizagem não-supervisionada, ou seja, não precisaremos informar os dados de saída, nesse caso, os labels para cada imagem de treinamento que nosso algoritmo for submetido.
  • Algoritmo Fisherfaces: Esse algoritmo trabalha o seu aprendizado de forma supervisionada, precisamos apresentar os labels ou classes das imagens de treinamento para o algoritmo. A extração de características é realizada separadamente, ou seja, a iluminação de uma face, não afetará as demais faces.
Se temos algoritmos já consagrados para trabalhar com reconhecimento facial, qual o propósito desse artigo trazendo mais um algoritmo? Por que nesse caso, estaremos falando de CNN (Convolution Neural Network, ou Rede Neural Convolucional), é o que se tem de mais avançado quando estamos tratando de visão computacional. Apenas a título de conhecimento, o CNN é uma rede neural profunda com algumas peculiaridades, como: a convolução e o pooling (agrupamento).

 Perceba que temos uma imagem de entrada e como saída teremos um conjunto de classes para que a nossa rede vai realize a associação, geralmente essa camada, geralmente utiliza a função de ativação softmax que calcula as probabilidades entre as diferentes classes. Mas em comparação com uma rede neural profunda, não temos nenhuma diferença, a diferença ocorre pela existência da convolução, onde será aplicado um filtro numa imagem que estará percorrendo-a a fim de encontrar as características da imagem. A sacada é que após a convolução, ocorre o subsampling, também chamado de pooling, isso acontece por que a CNN requer muito esforço computacional, quanto maior a imagem, mais filtros serão aplicado à imagem, para otimizar o tempo de processamento de uma CNN, podemos utilizar o pooling, como o max pooling, que vai realizar um agrupamento dos maiores valores de um filtro que foi utilizado na convolução. É perceptível pela imagem, que à medida que aplicamos convolução e subsampling, a profundidade do conjunto de dados aumentará, ao mesmo tempo que estaremos reduzindo a largura e altura. Feito essa explicação, vamos comentar um pouco sobre o projeto que estaremos mostrando aqui nesse artigo. A sequência de passos será o seguinte:
  1. Detecção de faces: O primeiro passo para reconhecer faces, é que consigamos inicialmente detectá-las. Para isso, vamos utilizar uma função disponível na biblioteca do DLib.
  2. Detecção de pontos faciais: Antes do reconhecimento facial, precisamos utilizar técnicas que permitam realizar a detecção das regiões de interesse em uma imagem, como estaremos trabalhando com detecção de faces, um exemplo de região de interesse pode ser detectar pontos faciais.
  3. Utilização de CNN: Por último, faremos o treinamento de um modelo de CNN pré-treinado para treinar nossas fotos a fim de estarmos aptos para realizar o reconhecimento facial.

Nas etapas 2 e 3, estaremos alguns modelos já disponibilizados na internet, para facilitar o processo de reconhecimento facial. Esses modelos, poderão ser encontrados nesse repositório do github. Vamos utilizar como exemplo nesse projeto, algumas imagens da série Silicon Valley. Vamos então à primeira etapa, utilizaremos a seguinte imagem: 
Posteriormente, utilizaremos essa mesma imagem, como imagem de teste para verificar se ela irá reconhecer a face do Richard, esse personagem ao meio. Como comentamos, vamos utilizar DLib, nosso código ficará assim:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import cv2
import dlib

imagem = cv2.imread("fotos/equipe.8.jpg")
detector = dlib.get_frontal_face_detector()
facesDetectadas = detector(imagem, 3)
print(facesDetectadas)
print("Faces detectadas: ", len(facesDetectadas))
for face in facesDetectadas:
    e, t, d, b = (int(face.left()), int(face.top()), int(face.right()), int(face.bottom()))
    cv2.rectangle(imagem, (e, t), (d, b), (0, 255, 255), 2)

cv2.imshow("Detector hog", imagem)
cv2.waitKey(0)
cv2.destroyAllWindows()

Esse exemplo utiliza as bibliotecas do Opencv e do DLib. Na linha 4, apresentamos a imagem que estaremos realizando a detecção facial. Na linha 5, utilizamos um dos recursos disponíveis do DLib que é o detector de faces, através do método get_frontal_face_detector(). Existem outros algoritmos para detectar faces como o haarcascades que é baseado no Adaboost. Já esse algoritmo do DLib, é baseado no HOG (Histograma de Gradientes Orientados). A sua caixa delimitadora (bounding box) é construída utilizando os parâmetros left, top, right e bottom. Continuando no código, na linha 6, chamamos o detector passando como parâmetro a imagem que queremos detectar e o tamanha da escala. E nas linhas 7 a linha 15, são procedimentos padrões, inclusive bem similares aos que é feito com o algoritmo haarcascade, percorremos pixel a pixel e desenhamos um retângulo sob a face detectada em cada foto. A atenção especial aqui, é lembrar que o HOG trabalha com os parâmetros left, top, right e bottom. Executando o código, teremos o seguinte resultado:
Legal, temos o resultado esperado. Vamos avançar agora à 2º etapa, agora que temos a bounding box, vamos extrair a região de interesse. Para isso, utilizaremos um dos recursos presente no código do Github que eu coloquei o link aqui no artigo. Esse recurso é um preditor específico para trabalhar com pontos faciais, seu objetivo, é detectar 68 pontos faciais. Vamos então utilizá-lo.

 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
import dlib
import cv2
import numpy as np
 
def imprimeLinhas(imagem, pontosFaciais):
    p68 = [[0, 16, False], # linha do queixo
           [17, 21, False], # sombrancelha direita
           [22, 26, False], # sombancelha esquerda
           [27, 30, False], # ponte nasal
           [30, 35, True], # nariz inferior
           [36, 41, True], # olho esquerdo
           [42, 47, True], # olho direito
           [48, 59, True], # labio externo
           [60, 67, True]] # labio interno
    for k in range(0, len(p68)):
        pontos = []
        for i in range(p68[k][0], p68[k][1] + 1):
            ponto = [pontosFaciais.part(i).x, pontosFaciais.part(i).y]
            pontos.append(ponto)
        pontos = np.array(pontos, dtype=np.int32)
        cv2.polylines(imagem, [pontos], p68[k][2], (255, 0, 0), 2)

imagem = cv2.imread("fotos/equipe.8.jpg")

detectorFace = dlib.get_frontal_face_detector()
detectorPontos = dlib.shape_predictor("recursos/shape_predictor_68_face_landmarks.dat")
facesDetectadas = detectorFace(imagem, 2)
for face in facesDetectadas:
    pontos = detectorPontos(imagem, face)
    imprimeLinhas(imagem, pontos)

cv2.imshow("Pontos faciais", imagem)
cv2.waitKey(0)
cv2.destroyAllWindows()
Nesse exemplo, acrescentamos a biblioteca do numpy, pois precisamos trabalhar com arrays para conseguirmos desenhar a região de interesse em cada face. Criamos a função imprimeLinhas que será responsável por desenhar os pontos faciais à imagem. Após isso, na linha 23, informamos a imagem que estaremos utilizando, na linha 25 fazemos o processo da detecção de faces, precisamos delimitar aonde que estaremos aplicando a nossa região de interesse. Na linha 26, apilcamos o preditor com 68 pontos faciais, esse preditor pode ser encontrado no link do github. Por fim, faremos um loop pelas faces detectadas, e em cima delas, criaremos a nossa região de interesse, e por último, chamamos a função imprimeLinhas para desenhar esses pontos na tela. O resultado será:

Ótimo! Veja que essa segunda etapa foi concluída como esperávamos. Agora, partimos para a terceira etapa. Para isso, iremos utilizar o personagem Richard, que é o personagem do meio nesta foto, vamos treinar o nosso algoritmo com três fotos dele, e depois faremos testes nas imagens que contém toda a sua equipe, para ver se o nosso algoritmo reconhece o Richard. Antes de começar os trabalhos, faço aqui uma ressalva, caso você prefira utilizar algoritmos como Fisherfaces ou Eigenfaces, saiba que seu algoritmo precisará de muito mais fotos do que apenas três. Vamos usar as seguintes fotos no treinamento:


Nosso código de treinamento das imagens será:

 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
import os
import glob
import _pickle as cPickle
import dlib
import cv2
import numpy as np

detectorFace = dlib.get_frontal_face_detector()
detectorPontos = dlib.shape_predictor("recursos/shape_predictor_68_face_landmarks.dat")
reconhecimentoFacial = dlib.face_recognition_model_v1("recursos/dlib_face_recognition_resnet_model_v1.dat")

indice = {}
idx = 0
descritoresFaciais = None

for arquivo in glob.glob(os.path.join("fotos/treinamento", "*.jpg")):
    imagem = cv2.imread(arquivo)
    facesDetectadas = detectorFace(imagem, 1)
    
    for face in facesDetectadas:
        pontosFaciais = detectorPontos(imagem, face)
        descritorFacial = reconhecimentoFacial.compute_face_descriptor(imagem, pontosFaciais)
        listaDescritorFacial = [df for df in descritorFacial]
        npArrayDescritorFacial = np.asarray(listaDescritorFacial, dtype=np.float64)
        npArrayDescritorFacial = npArrayDescritorFacial[np.newaxis, :]
        if descritoresFaciais is None:
            descritoresFaciais = npArrayDescritorFacial
        else:
            descritoresFaciais = np.concatenate((descritoresFaciais, npArrayDescritorFacial), axis=0)

        indice[idx] = arquivo
        idx += 1

np.save("recursos/descritores_r.npy", descritoresFaciais)
with open("recursos/indices_r.pickle", 'wb') as f:
    cPickle.dump(indice, f)
A execução desse script irá culminar na criação de dois arquivos: o descritores_r.npy e o indices_r.pickle. Como pode ser visto da linha 34 a linha 36. No início do script, na linha 08 e 09, vemos a primeira e segunda etapa sendo feita, para que consigamos realizar o reconhecimento facial. Para isso, utilizaremos um modelo de CNN treinado (está disponível no link do Github), a vantagem é que podemos utilizar várias imagens faciais agora, sem ter a preocupação de ter de retreinar o modelo, pois o mesmo custaria um esforço computacional. Criamos um loop para que sejam utilizadas todas as fotos que estiverem do diretório fotos/treinamento, e que sejam do formato jpg. Após isso, aplicaremos outro loop pelas imagens em que a face foi detectada, a partir daí aplicamos a detecção de pontos faciais, e vamos aplicar também nosso reconhecedor facial, através do método compute_face_descriptor(). Após isso, precisamos fazer algumas conversões para que os valores encontrados, fique em um formato de array do numpy, o que vai facilitar a comparação com novas imagens que passarmos para testes. E por último, fazemos a iteração de cada índice relacionado às imagens que passamos de treinamento. Por fim, vamos colocar o nosso algorito a prova e ver se ele foi capaz de reconhecer o Richard em algumas fotos que ele está junto com sua equipe, mas antes, vamos ao código que realiza o teste de reconhecimento facial:


 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
import os
import glob
import _pickle as cPickle
import dlib
import cv2
import numpy as np
detectorFace = dlib.get_frontal_face_detector()
detectorPontos = dlib.shape_predictor("recursos/shape_predictor_68_face_landmarks.dat")
reconhecimentoFacial = dlib.face_recognition_model_v1("recursos/dlib_face_recognition_resnet_model_v1.dat")
indices = np.load("recursos/indices_r.pickle")
descritoresFaciais = np.load("recursos/descritores_r.npy")
limiar = 0.5

for arquivo in glob.glob(os.path.join("fotos", "*.jpg")):
    imagem = cv2.imread(arquivo)
    facesDetectadas = detectorFace(imagem, 2)
    for face in facesDetectadas:
        e, t, d, b = (int(face.left()), int(face.top()), int(face.right()), int(face.bottom()))
        pontosFaciais = detectorPontos(imagem, face)
        descritorFacial = reconhecimentoFacial.compute_face_descriptor(imagem, pontosFaciais)
        listaDescritorFacial = [fd for fd in descritorFacial]
        npArrayDescritorFacial = np.asarray(listaDescritorFacial, dtype=np.float64)
        npArrayDescritorFacial = npArrayDescritorFacial[np.newaxis, :]

        distancias = np.linalg.norm(npArrayDescritorFacial - descritoresFaciais, axis=1)
        print("Distâncias: {}".format(distancias))
        minimo = np.argmin(distancias)
        print(minimo)
        distanciaMinima = distancias[minimo]
        print(distanciaMinima)

        if distanciaMinima <= limiar:
            nome = os.path.split(indices[minimo])[1].split(".")[0]
        else:
            nome = ' '

        cv2.rectangle(imagem, (e, t), (d, b), (0, 255, 255), 2)
        texto = "{} {:.4f}".format(nome, distanciaMinima)
        cv2.putText(imagem, texto, (d, t), cv2.FONT_HERSHEY_COMPLEX_SMALL, 0.5, (0, 255, 255))

    cv2.imshow("Reconhecimento facial", imagem)
    cv2.waitKey(0)
cv2.destroyAllWindows()

Perceba que esse código é bem similar ao anterior. Porém, temos algumas especificidades aqui, a primeira delas, é exportar os arquivos que gravamos no treinamento, que foram os índices e descritores. Definimos um valor de limiar também, por que agora, vamos comparar os valores de cada imagem de teste, com os valores dos descritores faciais que foram gerados pela imagem de treinamento. Nesse caso, a distância entre esses valores, deve ser menor ou igual ao que definimos no limiar, para que então, possamos certificar de que a face é de Richard. E por último, vamos ao que interessa. Vamos ver se como ficou nosso teste de reconhecimento facial nas imagens.
Veja que nessa primeira foto, apesar de Richard está com a face meio virada, ainda assim o algoritmo o reconheceu com aproximadamente 0.48 de distância. Lembre-se, como definimos o limiar de 0.50, os valores acima, não serão considerados como Richard. Vamos para próxima foto:
Essa segunda foto, também nosso algoritmo conseguiu reconhecer o Richard com um pouco mais de precisão, 0.42. Note que o reconhecimento se dá pelo texto que é anexado ao lado da face, e por fins de conhecimento, deixamos visível  a bounding box (caixa delimitadora) de detecção de faces, nas demais pessoas também. Vamos a próxima foto:
Note que nessa terceira imagem, também o Richard foi reconhecido corretamente. Até agora três imagens que submetemos a teste, as três, obtivemos resultado desejado. Vamos a quarta foto:
 Veja que nessa foto também nosso algoritmo conseguiu reconhecer o Richard e com uma precisão muito boa, inclusive.
 Outra foto, e tivemos sucesso também no reconhecimento facial de Richard, perceba que tivemos uma melhor precisão nessa imagem.
Achei essa foto bem legal por dois motivos: Ela reconheceu o Richard através de sua face pessoal, e também através da imagem que está no poster ao fundo. E a outra é por ter detectado a face que está na camisa do Erlich.
Nem tudo são mar de rosas na  vida. Veja que nessa imagem, o nosso algoritmo sequer detectou a face de Richard, logo ele não poderia reconhecê-lo. Essa imagem, apresenta uma dificuldade muito alta, pois a sua face está totalmente invertida.
Outra imagem que conseguimos obter êxito no reconhecimento de Richard, mesmo pelo fato de que sua face esteja um pouco curvada para baixo.
E por último, essa imagem nos mostra que o algoritmo conseguiu detectar a face de Richard, porém a distância entre os descritores faciais de treinamento e a imagem de teste foi maior que nosso limiar (0.5), nesse caso basta aumentar um pouco o nosso limiar, e teremos também o reconhecimento facial de Richard nessa imagem.Concluí-se então que tivemos uma ótima experiência com o algoritmo utilizado. De 9 imagens de teste, 7 conseguimos reconhecer o nosso alvo. E ainda vai um acréscimo, utilizamos apenas 3 imagens para treinamento, repito, apenas 3. Quando temos um dataset, é muito comum divirmos nossos dados em 75/25, sendo 75% treinamento e 25% teste, aqui fizemos o contrário, apenas para fins de avaliação mesmo, dedicamos mais imagens para teste do que para treinamento, e concluímos que nosso algoritmo se saiu muito bem. Espero que o artigo tenha sido útil e que de alguma forma possa contribuir positivamente para o conhecimento dos que vierem lê-lo. Até a próxima!

Amazon Rekognition na prática

Olá pessoal. Muito provável que você já tenha visto essa logo aí antes. A Amazon é uma das empresas que vem se consagrando cada vez mais,...