Reconhecimento facial em tempo real em vídeo

Este artigo faz parte de uma série de artigos que discutem e demonstram maneiras de se compreender o comportamento de clientes usando seus próprios corpos, como face, micro-expressões e extração de sentimentos e movimentos corporais e suas classificações, tudo isso usando visão computacional.

Nelson Forte
luizalabs
10 min readJul 28, 2021

--

Ela é a Lu. Reconhece esse rosto?

Introdução

A visão é uma das maiores ferramentas evolutivas para a maioria dos seres vivos, e para os seres humanos tem um papel fundamental para sua sobrevivência e compreensão do mundo. Nós, como humanos, aprendemos primeiro a reconhecer nossos pais ou tutores para só depois desenvolver a fala ou posteriormente a capacidade de escrita. E esse reconhecimento nos seres humanos acontece por uma das mais aparentes e singulares regiões do corpo: nossa face.

Claramente pessoas com problemas ou dificuldades visuais acabam usando outros aparatos biométricos para realizar tal reconhecimento como a voz, mas muitos deles usam o tato para reconhecer características faciais, como formato do nariz, da boca, da bochecha, etc. Afinal, são as características que definem a unicidade de um rosto. Quando pensamos em imitar esse tipo de reconhecimento (e conhecimento) em sua forma matemática, consequentemente pensamos na digitalização desse ato, permitindo que computadores façam isso sem intervenção humana. Como ensinar um programa de computador a reconhecer um rosto? Como extrair características faciais de forma precisa contudo veloz? Como calcular as semelhanças entre um rosto de exemplo contra todos os rostos conhecidos por essa aplicação?

Essas perguntas serão respondidas de forma introdutória neste artigo, do ponto de vista matemático e computacional.

Localizando faces

Imagens digitais (ou quadros de um vídeo) são um universo de muitos números (ou escalares) em um tensor (um tipo generalizado de matriz). Para um vídeo em FullHD ou 1920x1080 pixels por quadro, gravado com uma taxa de frequência de 60hz (ou 60 quadros por segundo), temos em um segundo 60 imagens na mesma resolução citada de 1920x1080 pixel. Como estamos falando de um vídeo em cores, suponha que esse vídeo tenha 8 bits de informação de cor por canal (e sendo cada canal as seguintes faixas de frequência de cores: R (red ou vermelho), G (green ou verde) e B (blue ou azul)), ou RGB, totalizando 24 bits (ou 16777216 cores) possíveis para cada pixel. Ou seja, em apenas um segundo, uma aplicação que pretende processar em tempo real esse vídeo precisa processar 373248000 valores numéricos. Isso quer dizer que são muitos dados e pouco tempo para se fazer algo entre cada quadro. A seguir, podemos ter uma ideia (e uma representação matemática) do que é uma imagem.

O tensor parcial de uma imagem ou quadro de um vídeo.

Nesse universo numérico e com o intuito de se reconhecer a localização de rostos, como descobrir isso através desses números? Como eliminar regiões que não sejam de interesse (um pouco mais sobre isso em Encontrando similaridades entre imagens com SIFT)? Afinal, nessa imagem (para o caso de reconhecimento facial) queremos apenas encontrar rostos para reconhecê-los posteriormente, ou seja, uma árvore ou carro ao fundo não tem nenhuma valia para esta tarefa. Para isso, um dos primeiros algoritmos de detecção em tempo real de rostos sugeridos é conhecido hoje como Viola-Jones.

Nesse algoritmo, o tensor da imagem é percorrido por características similares às da Transformada de Haar, que funcionam como simplificações de partes do rosto como nariz, boca ou olhos. Por exemplo, uma dessas características que tenham dois retângulos em branco nas extremidades e um preto no centro serve facilmente como uma característica para se encontrar um nariz, ou uma que seja um quadrado preto no centro com todas as extremidades em branco serviria como uma característica para os olhos e assim por diante. A seguir, algumas características comumente utilizadas para detecção de rostos.

Características similares à Transformada de Haar. É importante salientar que estas características também são tensores, normalmente muito menores do que o tensor da imagem sendo analisada.

Em algum momento você deve ter pensado: “mas esses quadrados não se parecem nada com um nariz ou uma testa!”. Sim, é verdade, mas aí está o papel de uma das fases da detecção de rosto: várias dessas características ruins ou fracas, quando executando juntas, geram características fortes. Na literatura de aprendizado de máquina, esse tipo de abordagem é conhecida como boosting, mas falaremos mais sobre esse algoritmo no decorrer do artigo.

A Lu com características de Haar posicionadas nas regiões de maior probabilidade de alocação. Fica engraçado, né? Lembre-se que a próxima vez que o aplicativo da câmera de seu celular marcar uma caixa nos rostos antes de tirar uma foto, ele está fazendo isso muito rápido, mas sem te mostrar.

As características de Haar corretas para realizar a detecção facial podem ser conseguidas treinando-se um classificador, “mostrando-o” muitas imagens de rosto e também outras imagens não contendo rosto (para aprender o que não é um rosto também). O que este classificador vai aprender é dizer se sim, existe um ou vários rostos na imagem, ou não existe. Classificadores desse tipo são conhecidos como classificadores binários. Como as características de rosto não variam entre imagens coloridas ou tons de cinza ou mesmo no escalonamento, antes do treinamento ou mesmo da detecção dos rostos, alguns passos de pré-processamento acontecem.

O passo mais comum é justamente transformar a imagem colorida para tons de cinza. Para se alcançar isso, basta uma etapa simples: calcular a média dos valores dos canais de cada pixel, e usar o resultado como o valor do pixel na nova imagem em tons de cinza. Isso faz com que a imagem tenha suas dimensões preservadas, porém reduz a dimensionalidade de cada elemento da matriz (ou tensor), saindo de 3 valores para apenas 1.

Transformando o tensor da imagem, de três para um canal (colorido ou tons de cinza).

Dessa forma, cada elemento do tensor resultante representa a intensidade do pixel, onde quanto mais próximo de zero mais próximo do preto e quanto mais próximo de 255, mais próximo do branco (numa imagem tons de cinza de 8 bits). Com isso, é possível ir para o próximo passo: calcular a imagem integral entre a imagem já convertida para tons de cinza e uma característica de Haar, região por região. Para isso, considere a equação a seguir.

Integral I do tensor M. No final, este processo está calculando se uma característica de Haar se “encaixa” numa determinada região. A equação é grande (não coube num bloco do Medium), mas o efeito e velocidade que ela permite são muito maiores.

A intuição relacionada a essa equação é analisar por toda a imagem, região a região, qual característica de Haar é “parecida” com a região em questão, percorrendo o tensor (ou imagem) inteiro. Caso o resultado esteja acima de um determinado valor preestabelecido, isso significa que naquela interação, alguma característica associada a rosto foi encontrada. Porém, pode ocorrer que isso acabe encontrando muitas regiões que resultam em várias integrais acima do valor estabelecido para o mesmo rosto, fazendo com que esse mesmo rosto seja marcado muitas vezes, e isso não é desejado, pois queremos encontrar o mesmo rosto apenas uma vez. Para isso, o próximo passo funciona como uma poda para estes falso-positivos de rosto. Este último passo confirma se uma determinada região contém um rosto analisando uma série de regiões adjacentes. Ter um limite destas regiões adjacentes correspondido faz com que a probabilidade de existir um rosto na região aumente. Por exemplo, imagine que este limite seja 32 regiões adjacentes possíveis de conter um rosto. Caso sejam reconhecidas 32 ou mais regiões internas a uma região maior que as demais, existe um grau de confiança alto atribuído a esta região, que segundo o limite é alto suficiente para confirmar a presença de um rosto.

Várias integrais que resultam num valor acima do limite estabelecido pra se encontrar um rosto. Devido às muitas regiões adjacentes (e sobrepostas), pode se considerar que realmente existe um rosto nesta região. Mais uma vez, a reunião das classificações não “perfeitas” (ou fracas) gera uma classificação sumarizada mais próxima da “perfeição” (ou fortes).

Assim, é possível eliminar todas as caixas menos precisas, preservando apenas a “melhor” na detecção do rosto, gerando um classificador binário para detectar vários rostos numa mesma imagem.

Vários rostos detectados, após a execução de todos os processos comentados anteriormente.

Alinhando faces

Apesar de não ser um passo imprescindível no reconhecimento facial de uma pessoa, alinhar uma face pode ajudar (ou mesmo facilitar) o reconhecimento. Como visto anteriormente, uma característica semelhante a Transformada de Haar pode ser rotacionada, podendo ser construídos usando algum tipo de transformação geométrica. Porém, para evitar tais características sendo extensivamente extraídas (por exemplo, uma característica para cada ângulo de rotação possível), pode-se alinhar o rosto, possibilitando usar na maioria das vezes características sem ângulo de rotação. Para se alinhar um rosto é possível utilizar uma heurística com relação ao ângulos dos olhos. Ela assume alguns parâmetros que podem não estar presentes na imagem, por isso nem sempre é ótima. Isto é, ela exige a presença de dois olhos detectados. Para a detecção dos olhos, pode ser usada a mesma abordagem da detecção de faces, porém usando características apenas relacionadas a olhos.

Detectando os olhos e encontrando o ângulo θ, é possível saber quão “desalinhado” o rosto está.

Uma vez detectados os olhos, basta calcular a reta entre os pontos centrais dos quadrados que contém os olhos (hipotenusa), e calcular seu ângulo θ com relação ao cateto adjacente. Isto pode ser calculado com a seguinte dedução.

Encontrando o ângulo θ entre os tensores A e B, onde A.B representa o produto escalar entre os tensores e A×B é o produto das magnitudes dos mesmos.

Sabendo-se o ângulo, basta comparar a altura do primeiro olho (mais a esquerda) com o segundo olho (mais a direita), para saber se a rotação deve ser horária ou anti-horária, e girar a imagem neste mesmo sentido, com a intenção de tornar o ângulo θ igual a 0.

Rosto alinhado. Bem mais fácil de “encaixar” características de Haar com ângulos retos.

Extraindo características de rosto

Existem muitas maneiras de se extrair características, e ela define a maior parte do sucesso no reconhecimento. Mas antes de mais nada, precisamos entender qual sua finalidade. Você já viu aqueles quadros em programas de TV (majoritariamente nos anos 90) que mostrava partes do rosto de celebridades (ou mesmo as imagens dos seus rosto filtrados com embaçamento ou pixelização), e o objetivo era descobrir qual era a celebridade? Na maioria das vezes, apenas uma parte do rosto já dizia bastante a quem ele pertencia. A largura do queixo, o tom de pele, o formato dos olhos, a sobrancelha, etc, funcionavam como indicativos do rosto completo e seu detentor. Ou seja, existia um mapa de características criado na mente, e todos esses “pedaços” de rosto eram comparados com a imagem mental completa de vários outros rostos, até se encontrar onde cada parte se encaixa melhor, isto é, tinha maior similaridade.

Esse processo mental foi “imitado” na sua implementação computacional (assim como uma parte dos processos já citados anteriormente). Portanto, a extração de características tem como intuito criar este mapa, para posterior comparação de cada característica contra uma base de rostos. Como citado anteriormente, existem muitas formas de se extrair estas características, e esse artigo irá discutir sobre uma específica: o histograma de padrões locais binários (Local Binary Pattern Histogram, LBPH). Esta abordagem consiste em analisar várias regiões da imagem de rosto (pré-processada), verificar binariamente os valores adjacentes ao pixel central dessa região (normalmente uma região quadrada 3x3) com base no valor limite. Está acima do limite? Retorna 1, ou 0 caso contrário. Após isso, converte todos os valores binários das adjacências para decimal concatenando-os, e esse valor é assumido como o valor do pixel central, repetindo isso para toda a imagem. Calma se não ficou claro, a imagem abaixo vai te ajudar.

Os passos da descoberta dos padrões locais binários (antes da geração dos histogramas). Sim, a última imagem parece assustadora, mas é possível perceber que várias características importantes do rosto ficaram“ressaltadas”, como os lábios e o formato dos olhos.

Após esse processo ser feito por toda a imagem e termos como resultado um mapa de características, que nada mais são que as características de rosto “ressaltadas” (demonstrado na imagem acima), temos a divisão do mapa em uma série de regiões. Para cada região é calculado o histograma da distribuição das intensidades dos pixels na região e, após isso, todos os histogramas são inseridos num mesmo tensor contiguamente. Isto faz com que todos os histogramas funcionem como uma descrição (dimensionalmente) resumida de todas as características do mapa.

Dito isso, podemos discutir quais são os hiper-parâmetros do LBPH: a quantidade de vizinhos pra se considerar na descoberta dos padrões binários locais (no exemplo anterior, são considerados oito pixels adjacentes ou vizinhos), o cálculo do padrão (que se estendem além do demonstrado no exemplo, como usar a mediana dos valores ao invés da classificação binária do limite), o tipo de região (muitas vezes ela é circular e não quadrada) e a quantidade de histogramas por mapa (ou seja, a quantidade de regiões ou células para se calcular histogramas).

Finalmente, com os histogramas contíguos, como é calculado a similaridade entre um rosto sendo consultado (e momentaneamente desconhecido) contra uma base (ou contra os histogramas contíguos associados a um rótulo ou nome)? A resposta: distâncias entre os tensores, muito comum em várias aplicações de aprendizado de máquina. Em teoria, pode ser usado qualquer uma das equações possíveis para se calcular a distância entre vetores, porém a mais comum e adequada para o tipo de tensores dos histogramas contíguos é a similaridade dos cossenos, já vista neste artigo na dedução do cálculo do ângulo e definida em linha como cos(θ) = A.B / |A|×|B|, onde A define os histogramas da imagem do rosto sendo consultado, e B de um dos rostos na base. Claramente, caso a base de rostos seja grande o suficiente, essa quantidade de comparações irá crescer substancialmente, tornando a busca por um rosto parecido computacionalmente complexa (com relação ao espaço de busca). Neste caso, usar alguma heurística para reduzir a quantidade de comparações, como o LSH (Locality-sensitive hashing) irá ajudar muito.

Reconhecimento facial em ação

Veja abaixo todos os algoritmos implementados e realizando o reconhecimento em tempo real.

Faces sendo reconhecidas em tempo real, acompanhadas dos rótulos relativos ao rosto.

Conclusão

Nesse artigo, pudemos discutir alguns algoritmos muito utilizados e que possibilitaram o reconhecimento de faces em tempo real. Devido a sua simplicidade e velocidade, esses algoritmos são utilizados em diversos contextos. A pesquisa nessa área ainda vem acontecendo e tem alcançado resultados com um nível de precisão ainda maior que o do ser humano. Nas pesquisas posteriores aos algoritmos apresentados, temos um salto em precisão com o DeepFace, a arquitetura e dataset do VGG-face, além do estado-da-arte como o OpenFace e o ArcFace, que serão demonstrados em artigos posteriores.

Revisores: Panagiotis Kovanis, Débora Garcia e Kelvin Prado.

--

--