Ir para o conteúdo

Afinal, as tecnologias de reconhecimento facial são realmente acessíveis para desenvolvimento e uso diário?

Qual o problema?

Hoje, para acionar as portas dos andares do prédio da CWI, temos um acionador (isso mesmo — o botãozinho) ou leitores como crachá ou digital.  Essas opções são pouco otimizadas, pois temos o contratempo de apertar o botão, o que não garante a entrada exclusiva de pessoas autorizadas aos andares. 

Leitores inteligentes nos dão essa segunda garantia, porém têm um custo elevado de obtenção e instalação. E se pudéssemos apenas olhar para uma câmera e abrir a porta, com agilidade e segurança de que a porta só abrirá se conhecer nossos rostos?

Planejando a solução

First things first: Qual a diferença entre detecção facial e reconhecimento facial?

Elementar, meu caro Watson: Dentro do âmbito das tecnologias que foram utilizadas neste projeto, detectar um rosto em uma imagem (seja estática ou em streaming) é apenas determinar se há uma ou mais faces na imagem e entre quais pontos estão localizadas. 

Todavia, reconhecer um rosto é fazer uso de fórmulas para determinar a distância entre pontos predeterminados (como distância entre os olhos, nariz, boca) do rosto já detectado, assim podendo comparar com outra foto e dizer, com um certo grau de precisão, se trata-se ou não do mesmo rosto.

Reconhecimento facial vs. Detecção facial

Foto: Reconhecimento dos pontos de referência faciais

Atualmente, Python é a sétima linguagem (entre programação e marcação) mais utilizada no mundo e a terceira mais amada, segundo o Stack Overflow. Essa enorme popularidade não vem à toa: Python é extremamente versátil, multiplataforma e, o melhor de tudo, tem uma vasta gama de libs e frameworks para os mais diversos fins, incluindo análise de imagens.

Dentro das libs disponíveis, a escolhida foi a face_recognition. Alguns dos motivos que embasaram a decisão foram: simplicidade na utilização, com diversos exemplos de uso; possibilidade de implementação em Python; uso direto na linha de comando ou até mesmo instalação no Raspberry Pi; somados à sua natureza open source, o que permitiu maior exploração do código fonte. 

Entretanto, não é multiplataforma — atualmente, só tem suporte oficial no Linux. Porém, alguns aventureiros ousaram tentar instalar no Windows e obtiveram sucesso.

Okay, mas como funciona?

A face_recognition é baseada na união de 3 libs principais: dlib, pillow e numpy.

Dlib é a encarregada do trabalho mais árduo: é ela que detecta o rosto na imagem e gera os encodings, que são codificações únicas pelas quais um rosto pode ser identificado, sendo auxiliada por numpy e o módulo de imagem do pillow para realizar as operações na foto convertida em arrays específicos, por meio de três objetos principais:

1º – Um objeto detector, instância de get_frontal_face_detector, criado pela própria lib, cuja missão é encontrar todos os rostos em uma imagem.

# -*- coding: utf-8 -*-

import dlib
import PIL.Image
import numpy as np

# Instancia o objeto de detecção 
detector = dlib.get_frontal_face_detector()

# Utiliza o Pillow para abrir a imagem
im = PIL.Image.open("BarackObama.jpg")

# Utiliza a imagem já aberta pelo PIL para criar um numpy array
np_array = np.array(im)
  
# Transforma o numpay array em array RGB
rgb = np_array[:, :, ::-1]

# Usa a instância configurada do detector de faces para encontrar no RGB as faces contidas na imagem original
# O número 1 como argumento indica que a imagem vai ser processada uma vez
rects = detector(rgb, 1)

# Resultado: objeto rectangle de 4 pontos indicando a localização do rosto
print(rects)

A detecção facial da dlib se baseia na técnica “Histogram of Oriented Gradients ” (HOG), que tem a filosofia de dividir para conquistar, traçando os cálculos por áreas chamadas de células para ponderar, normalizar e chegar a um resultado final.

2º – Um objeto predictor, construído pelo shape_predictor, recebe em seu construtor um modelo baseado na quantidade de pontos que deverá procurar na imagem, para que a inteligência de detecção HOG seja treinada por isso. Para este projeto, usamos o arquivo dat que contém o treinamento baseado em 5 pontos, treinado por 7198 faces, feito por um entusiasta e que pode ser encontrado aqui.

# -*- coding: utf-8 -*-

import PIL.Image
import numpy as np
import dlib

detector = dlib.get_frontal_face_detector()

#Instancia o objeto responsável pela identificação dos pontos de referência (landmarks)
predictor = dlib.shape_predictor("shape_predictor_5_face_landmarks.dat")

im = PIL.Image.open("BarackObama.jpg")

np_array = np.array(im)

rgb = np_array[:, :, ::-1]
rects = detector(rgb, 0)

print (rects)

# Caso rects seja um retorno vazio, quer dizer que não há nenhuma face na imagem
if not rects:
    print('Nenhuma face encontrada na imagem!')

# Caso haja...
else:
    
    # Define e retorna no objeto shape os 5 pontos de referência dentro do rosto encontrado
    # Pode encontrar um ou mais, porém neste caso tem-se certeza de que a foto tem apenas um rosto
    shape = predictor(rgb, rects[0])

3º – Por último, mas não menos importante, o objeto face_encoder, que é criado a partir do construtor de face_recognition_model_v1, especifica como calcular, utilizando uma deep learning treinada pelo arquivo fornecido (cerca de 3 milhões de rostos), um array de 128 posições contendo uma descrição específica do rosto encontrado pelo predictor. 

Esses encodings são um representante único do rosto mapeado e o resultado final do reconhecimento facial. É por eles que podemos comparar dois diferentes rostos e dizer a proximidade entre seus arrays descritivos.

# -*- coding: utf-8 -*-

import PIL.Image
import numpy as np
import dlib

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_5_face_landmarks.dat")

# Instancia o objeto responsável pela codificação do rosto baseado no modelo de aprendizado
face_encoder = dlib.face_recognition_model_v1("dlib_face_recognition_resnet_model_v1.dat")

im = PIL.Image.open("BarackObama.jpg")

np_array = np.array(im)

rgb = np_array[:, :, ::-1]

rects = detector(rgb, 0)

print (rects)

if not rects:
    print('Nenhuma face encontrada na imagem!')

else:
    shape = predictor(rgb, rects[0])
    
    # Utiliza o face encoder, treinado pelo arquivo dat fornecido, 
    # usando o numpy array RGB e os pontos indicados pelo predictor 
    # para gerar os encodings baseado em 5 pontos faciais: canto dos olhos e base do nariz
    encodings = np.array(face_encoder.compute_face_descriptor(rgb, shape, 1))
    print (encodings) 

Depois de entender passo a passo como funciona a face_recognition por dentro, está na hora de utilizá-la:

Até então, entendemos o que são e como são gerados esses encodings. Agora, vamos entender como eles podem ser comparados entre si. A comparação dos arrays de 128 posições da lib é baseada na distância euclidiana entre dois pontos:

Para este cálculo observamos retornos, em geral, em valores entre 0 e 1: quanto mais alto o valor, mais próximos são os rostos. A lib interpreta este valor dentro de um fator pré estipulado de margem de erro aceitável (0.4), retornando true ou false.

# -*- coding: utf-8 -*-

import face_recognition

image = face_recognition.load_image_file("BarackObama.jpg")

# Array de 4 valores referentes a localização do rosto na imagem
face_locations = face_recognition.face_locations(image)

print(face_locations)

# Reconhece os pontos faciais, como não foi passado o terceiro argumento que define por padrão 
# o tamanho da imagem para large, ele utiliza o padrão de predição de 68 pontos
# o resultado são arrays com 12 tuplas cada que indicam a localização de lábios, olhos, sobrancelhas, nariz e queixo
face_landmarks_list = face_recognition.face_landmarks(image)

print(face_landmarks_list)

# Gera os 128 encodings particulares do rosto
encoding = face_recognition.face_encodings(image)[0]

print(encoding)

Para o projeto, como um dos objetivos era realizar a operação de comparação com a melhor performance possível, abandonamos este método para utilizar uma procedure com um cálculo adaptado no banco de dados PostgreSQL. 

Nessa, removemos a parte de raiz do cálculo para reduzir o custo de processamento da operação, invertendo o fator de comparação pensando em comparações realizadas com uma grande quantidade de rostos.

Final Facts

A tecnologia provou-se muito eficaz em cumprir aquilo que se propõe a fazer: reconhecimento facial com precisão e velocidade satisfatória. Com scripts simples e grande variedade de formas de uso, a lib possibilita uma miríade de possibilidades para aperfeiçoar processos e suprir necessidades já existentes. 

Os exemplos utilizados nas imagens acima podem ser encontrados neste repositório.

Para disponibilizar serviços de reconhecimento facial de forma RESTful foi construída uma API Python utilizando Flask. Foram desenvolvidas duas versões, utilizando duas abordagens diferentes: um aplicativo Android, para também cadastrar os usuários e seus rostos, e a versão final utilizando Raspberry Pi e um monitor que pode ser encontrada de forma itinerante pela CWI, ambos aliando detecção facial ao processo de reconhecimento.

A solução Android foi deixada em segundo plano devido à interação necessária entre servidor de processamento e device, o que torna essa solução mais lenta. Já a segunda proposta se apresenta mais ágil contando com o processamento centralizado no próprio Raspberry Pi, não necessitando de conexão externa e feedback visual via monitor.

Atualmente, o aplicativo desenvolvido é utilizado apenas para fazer o cadastro dos rostos dos usuários que serão autorizados pelo Raspberry Pi a entrar no andar. Os rostos armazenados na base alimentada pelo app são transferidos para o sistema de reconhecimento via integração SSIS (SQL Server Integration Services) toda manhã.

O projeto já tem uma versão piloto que encontra-se atualmente no segundo andar. Para testar, basta se cadastrar usando o tablet do Núcleo de Tecnologia.


Referências:

Face Recognition

Mobile Vision – Detect Faces

Firebase – ML KIT

Samples – Android Vision API

HOG – Histogram of Oriented Gradients

DLib Models

Developer Survey Results — Stack Overflow

SQL Server Integration Services

Thanks to Victor Herzog Damke. 

Outras publicações