Reconhecimento facial com Azure Cognitive Services, Angular e Node.js – Parte 1

Faala aí galera! Tudo certo?

Hoje vamos fazer uma aplicação para realizar reconhecimento facial e identificação de pessoas utilizando os serviços cognitivos do Azure. Como é um serviço exposto via HTTP, podemos fazer a aplicação com a linguagem que quisermos; nesse exemplo vamos utilizar Node.js e Angular. Bora lá!

O post vai ser dividido em duas partes:

  • Parte 1 (esse post): vamos ver um pouco de como funciona o Azure Cognitive Services, como ele reconhece faces, como utilizar e criar esse serviço no portal do Azure e por fim vamos cadastrar faces em um “banco de faces” para serem reconhecidas posteriormente.
  • Parte 2 (aqui): vamos enviar uma face para o serviço de reconhecimento e ver a mágica acontecer.

O que são os serviços cognitivos do Azure?

Não vou me alongar muito para explicar tudo, mas basicamente os serviços cognitivos do Azure são algoritmos de inteligência artificial encapsulados e expostos em endpoints via HTTP. Para mais informações, veja a documentação aqui.

Como funciona o reconhecimento facial?

Devemos seguir alguns passos após já termos o nosso serviço criado no portal do Azure:

  1. Criar uma lista de faces;
  2. Inserir faces na lista de faces que criamos;
  3. Treinar a lista de faces;
  4. Reconhecer faces (esse aqui vai ficar na parte 2);

Ou seja, com 4 chamadas de API já temos o nosso reconhecimento facial funcionando!

fluxo

Como criar um serviço de reconhecimento facial?

No portal do Azure (https://portal.azure.com) vamos até o menu da esquerda, e clicamos na opção “Create a resource”, logo após em “AI + Machine Learning” e por fim em “Face”:

001

Após isso, preenchemos os dados necessários, como nome, local, grupo de recursos e a opção de cobrança:

002

*existe uma opção FREE, que tem o limite de 1 conta por assinatura, já estou utilizando ela por isso não coloquei aqui na demo, mas estou utilizando e tá bem tranquilo.

Após a criação, vamos precisar pegar a chave para utilizarmos nas chamadas dos endpoints, o serviço nos disponibiliza duas chaves, na demo vamos utilizar a chave 1:

003

Mãos na massa!

Agora com a conta criada é só mandar bala! 😀
Criei um cadastro de clientes, para exemplificarmos o reconhecimento facial. Como a ideia é mostrar a funcionalidade do Azure, não vou explicar os detalhes de funcionalidades do Angular e do Node.js, caso tenham alguma dúvida podem me chamar que a gente troca uma ideia.

Mas vamos lá, a tela de cadastro de clientes é bem simples, com algumas informações básicas e uma foto:

004

O layout é baseado no material design e é um componente que criamos na SMN chamado smn-ui, ele é open-source e está disponível para download e contribuições aqui no GitHub (https://github.com/smn-official/ng-smn-ui).

O código dessa tela ficou mais ou menos dessa forma:

HTML

<div class="ui-s480">
<form #formCliente="ngForm" class="ui-validate on-dirty on-submit" (submit)="onSubmit(formCliente)">
<ui-card class="elevate-on-toolbar" [class.loading]="loading">
<div class="ui-progress accent" [class.hide]="!loading">
<div class="indeterminate"></div>
</div>
<ui-toolbar class="flat">
<button class="ui-button flat icon" type="button" uiRipple (click)="_location.back()">
<i class="material-icons">arrow_back</i>
</button>
<span class="title">{{newRegister ? 'Novo cliente' : (loading ? 'Carregando' : 'Alterando ' + (info.nome || 'cliente'))}}</span>
</ui-toolbar>
<fieldset [disabled]="saving || loading">
<ui-card-content>
<div>
<input type="file" name="input" uiInputFile [(ngModel)]="info.imagemPath" [read]="changeImagem.bind(this)" [error]="changeImagemError.bind(this)"
#inputNovaImagem #fieldNovaImagem="ngModel" accept="jpg,jpeg,png" max-file-size="15MB" hidden>
<div class="picture">
<div *ngIf="!info.imagem && !info.novaImagem" (click)="inputNovaImagem.click()">
<span *ngIf="info.nome">{{info.nome.substring(0, 1)}}</span>
<i *ngIf="!info.nome" class="material-icons">assignment_ind</i>
</div>
<div *ngIf="info.imagem || info.novaImagem" [style.background-image]="'url(' + (info.novaImagem || info.imagem) + ')'"></div>
<button type="button" class="ui-button icon raised accent" uiRipple [uiMenuTrigger]="menuPicture" align="left">
<i class="material-icons">photo_camera</i>
</button>
</div>
</div>
<div class="ui-flex-container">
<ui-input-container>
<input #fieldNome="ngModel" type="text" [(ngModel)]="info.nome" uiInput name="nome" required uiMaxlength="50">
<label>Nome</label>
<div class="ui-messages">
<div *ngIf="fieldNome.errors && fieldNome.dirty">
<div class="ui-message error" [hidden]="!fieldNome.pristine && !fieldNome.errors.required">
Nome é obrigatório
</div>
<div class="ui-message counter error" [hidden]="!fieldNome.errors.uiMaxlength">
{{info.nome ? info.nome.length : 0}}/50
</div>
</div>
</div>
</ui-input-container>
</div>
<div class="ui-flex-container">
<ui-input-container>
<input #fieldCpf="ngModel" id="cpf" type="text" [(ngModel)]="info.cpf" uiInput uiMaskCpf name="cpf" required >
<label>CPF</label>
<div class="ui-messages">
<div *ngIf="fieldCpf.errors && fieldCpf.dirty">
<div class="ui-message error" [hidden]="!fieldCpf.pristine && !fieldCpf.errors.required">
CPF é obrigatório
</div>
<div class="ui-message error"
[hidden]="!fieldCpf.pristine && !fieldCpf.errors.duplicate">
Já exisite um cliente com esse CPF
</div>
</div>
</div>
</ui-input-container>
</div>
<div class="ui-flex-container">
<ui-input-container>
<input #fieldDataNascimento="ngModel" id="dataNascimento" type="text" [(ngModel)]="info.dataNascimento" uiInput uiMaskDate name="dataNascimento" required >
<label>Data de Nascimento</label>
<div class="ui-messages">
<div *ngIf="fieldDataNascimento.errors && fieldDataNascimento.dirty">
<div class="ui-message error" [hidden]="!fieldDataNascimento.pristine && !fieldDataNascimento.errors.required">
Data de nascimento é obrigatório
</div>
</div>
</div>
</ui-input-container>
</div>
<div class="ui-flex-container">
<ui-input-container>
<input #fieldEmail="ngModel" id="email" type="text" [(ngModel)]="info.email" uiInput name="email" required >
<label>E-mail</label>
<div class="ui-messages">
<div *ngIf="fieldEmail.errors && fieldEmail.dirty">
<div class="ui-message error" [hidden]="!fieldEmail.pristine && !fieldEmail.errors.required">
E-mail é obrigatório
</div>
</div>
</div>
</ui-input-container>
</div>
<div class="ui-flex-container">
<ui-input-container>
<input #fieldTelefone="ngModel" id="telefone" type="text" [(ngModel)]="info.telefone" uiInput uiMaskPhone name="telefone" required >
<label>Telefone</label>
<div class="ui-messages">
<div *ngIf="fieldTelefone.errors && fieldTelefone.dirty">
<div class="ui-message error" [hidden]="!fieldTelefone.pristine && !fieldTelefone.errors.required">
Telefone é obrigatório
</div>
</div>
</div>
</ui-input-container>
</div>
</ui-card-content>
</fieldset>
</ui-card>
<div class="ui-fab-container">
<button class="ui-button success fab" uiRipple [class.hide]="loading">
<ui-progress-radial class="indeterminate" *ngIf="saving"></ui-progress-radial>
<i class="material-icons">check</i>
</button>
</div>
</form>
<ui-menu #menuPicture>
<div class="ui-menu-content size-2x">
<div class="ui-menu-item" uiRipple (click)="inputNovaImagem.click()">
<i class="icon material-icons">add_a_photo</i>
{{!info.imagem && !info.novaImagem ? 'Selecionar imagem' : 'Nova imagem'}}
</div>
<div class="ui-menu-item" uiRipple *ngIf="info.imagem || info.novaImagem" (click)="info.novaImagem = null; info.imagem = null; inputNovaImagem.value = ''">
<i class="icon material-icons">delete</i>
Remover
</div>
</div>
</ui-menu>
</div>

view raw
cliente.html
hosted with ❤ by GitHub

Submit do formulário

onSubmit(form) {
if (!this.saving) {
for (const control in form.controls) {
if (form.controls.hasOwnProperty(control)) {
form.controls[control].markAsTouched();
form.controls[control].markAsDirty();
}
}
if (!form.valid) {
this.element.nativeElement.querySelectorAll('form .ng-invalid')[0].focus();
return false;
}
this.saving = true;
this.clienteService.cadastrar(this.info).subscribe(data => {
this.saving = false;
UiSnackbar.show({
text: `Cliente cadastrado com sucesso.`
});
this.router.navigate(['/cliente'], { replaceUrl: true });
}, e => {
this.saving = false;
if(e.error.statusCode == 406){
form.controls.cpf.setErrors({ duplicate: true });
this.element.nativeElement.querySelector('#cpf').focus();
} else {
UiSnackbar.show({
text: 'Ocorreu um erro interno, tente novamente mais tarde.'
});
}
});
}
}

view raw
cliente.ts
hosted with ❤ by GitHub

Agora, a parte onde toda a mágica acontece, na API onde cadastramos o cliente, vamos passar cada método passo-a-passo:

async function inserirCliente(req, res) {
let clienteDb = await repository.verificaExisteCliente(req.body.cpf);
if (clienteDb)
return res.error('Já existe um cliente com esse CPF', 406);
let cliente = await repository.inserirCliente(req.body);
await _atualizarReconhecimentoFacial(cliente, req.body.novaImagem);
res.ok(cliente);
}

view raw
clienteController.js
hosted with ❤ by GitHub

Vejam que o controller está bem simples, e tem as seguintes funcionalidades:

  • Verificar se existe um outro cliente com o mesmo CPF;
  • Inserir um cliente no banco de dados;
  • Inserir a imagem do cliente em um banco de imagens;

E é esse o nosso foco, vamos lá!

O método “_atualizarReconhecimentoFacial” faz as seguintes tarefas:

async function _atualizarReconhecimentoFacial(idCliente, imagem) {
let imageName = `${idCliente}.jpg`;
await azureStorage.upload('cliente', imageName, imagem);
let face = await reconhecimentoFacial.uploadImagem(imageName);
let faceId = face.persistedFaceId;
await repository.atualizarFaceId(idCliente, faceId);
reconhecimentoFacial.treinarReconhecimento();
}

Esse método faz basicamente:

  • Insere a imagem do cliente em um container utilizando o Azure Blob Storage, tem um post explicando sobre isso aqui;
  • Faz upload da imagem para o grupo em que escolhi (calma aí que já explico isso daqui a pouco)
  • Atualiza o cliente com o identificador da face
    • Isso vai servir para reconhecermos a face na nossa base de dados posteriormente
  • Treina o reconhecimento no grupo
    • Esse método de treinar é assíncrono, e tem uma outra rota no serviço para verificarmos o status do treinamento. Ele varia de tempo de acordo com a quantidade de imagens no grupo.

Considerem que a variável “faceApi” tenha o seguinte valor pois nosso serviço foi criado com a localização do centro-sul dos Estados Unidos:

let faceApi = "https://southcentralus.api.cognitive.microsoft.com/face/v1.0"

E falando no grupo ou lista de faces, a seguir vamos ver como criar um grupo, vejam:

async function criarLargeFaceList() {
let config = {
method: 'PUT',
uri: `${faceApi}/largefacelists/minha-lista`,
headers: {
'Ocp-Apim-Subscription-Key': faceApiKey
},
json: true,
body: {
name: 'minha-lista',
userData: "Minha lista de faces"
}
};
let response = await request(config);
return response;
};

view raw
criarFaceList.js
hosted with ❤ by GitHub

O método acima criou a lista de faces chamada “minha-lista”, e nessa lista vamos inserir todas as imagens de clientes que cadastrarmos.

Para inserir uma imagem nessa lista, vamos utilizar o seguinte método:

async function uploadImagem(imageName) {
let config = {
method: 'POST',
uri: `${faceApi}/largefacelists/minha-lista/persistedfaces`,
headers: {
'Ocp-Apim-Subscription-Key': faceApiKey
},
json: true,
body: {
url: `${blobUrl}/${imageName}`
}
};
let response = await request(config);
return response;
};

view raw
uploadImagem.js
hosted with ❤ by GitHub

Vejam que no corpo da requisição estamos enviando um atributo chamado “url”, essa é a url da imagem que estamos enviando para a lista, a API irá nos responder com um status code 200, e o seguinte corpo com o identificador da face que foi inserida na lista de faces:

{
    "persistedFaceId": "43897a75-8d6f-42cf-885e-74832febb055"
}

e por fim, vamos treinar a nossa lista. O treinamento serve para podermos fazer o reconhecimento facial, ele deve ser feito toda vez que uma nova face for adicionada a lista. O treino pode variar de tempo de acordo com a quantidade de imagens na lista e por isso é uma tarefa assíncrona.

async function treinarReconhecimento() {
let config = {
method: 'POST',
uri: `${faceApi}/largefacelists/minha-lista/train`,
headers: {
'Ocp-Apim-Subscription-Key': faceApiKey
}
};
let response = await request(config);
return response;
};

view raw
treino.js
hosted with ❤ by GitHub

Pronto! A parte 1 está concluída, ou seja, estamos inserindo clientes e os vinculando com uma imagem em nossa lista de imagens.

Após isso treinamos a lista para poder reconhecer as faces posteriormente, que é o que vamos fazer na parte 2 desse post!

Se tiverem qualquer dúvida sobre essa parte, se algo ficou muito vago ou confuso, me enviem feedbacks que irei dar um jeito 😉

Os códigos utilizados estão disponíveis no GitHub:

Por hoje é só isso, qualquer dúvida ou sugestão, estou à disposição! Até mais 😀

4 thoughts on “Reconhecimento facial com Azure Cognitive Services, Angular e Node.js – Parte 1

  1. Douglas Pacor 04/10/2018 / 09:02

    Nice cara, parabéns, muito instrutivo. continue com os ótimos posts.

    Gostar

Deixe uma Resposta para viniciusmussak Cancelar resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão /  Alterar )

Google photo

Está a comentar usando a sua conta Google Terminar Sessão /  Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão /  Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão /  Alterar )

Connecting to %s