NÃO reinvente a roda! Serviços Cognitivos da Azure: Face

Como já abordado na introdução da série Não reinvente a roda! Serviços Cognitivos do Azure, a Inteligência Artificial é um tema relevante e que contém diversos usos. Um deles, o que trataremos aqui, é o Reconhecimento Facial através dos Serviços Cognitivos da Azure Face.

Reconhecimento Facial no Azure

O serviço de reconhecimento facial do Azure é utilizado para detectar, analisar, reconhecer, agrupar e verificar rostos humanos em imagens, o que é útil em diversos cenários de software, como em quesitos de segurança e logins facilitados.

Nesse artigo, vamos criar uma aplicação que nos acompanhará por toda a série, sempre incorporando mais um dos Serviços Cognitivos do Azure.

Esta primeira parte terá uma opção de detectar quantidade de rostos, emoções, sorrisos, gêneros e idade em URL de fotos, ou arquivos.

Todo o código está disponível no github!

Então, vamos a mão na massa!

1. Crie seu projeto

Utilizarei o Visual Studio Community 2019, baixe também a última versão do .NET Core (também é possível baixá-la junto do Visual Studio).

No Visual Studio baixado crie um projeto do tipo ASP.NET Core Web Application e escolha o template de Web Application para ter o projeto criado voltado para o Razor Pages. Vamos dar o nome do projeto de AzureCognitiveServices.

Com o projeto criado, adicione um Solution Folder a solução e o chame de Vision, que é o nome da categoria que vamos explorar agora.

Dentro dessa Solution Folder, crie um novo Projeto do tipo Class Library (.NET Core) e o nomeie como Face.

Até esse ponto, a estrutura do projeto deve ser a seguinte:

Estrutura inicial do projeto

2. Configure a Azure

Crie uma conta gratuita no Azure e, logo após, crie um recurso para utilizar o serviço de detecção facial e obtenha a chave e o endpoint do recurso, como na imagem a seguir.

Campos da chave e endpoint do recurso

3. Conecte o Serviço de Faces ao seu projeto

Voltando ao projeto no VIsual Studio, vamos renomear o arquivo Class1.cs para algo mais palatável. Renomeio para DeteccaoFacial.cs.

Abra o Package Manager Console e execute o seguinte comando para adicionar as bibliotecas do Serviço Cognitivo de Faces ao projeto que chamamos de Face.

dotnet add Face package Microsoft.Azure.CognitiveServices.Vision.Face --version 2.5.0-preview.1

Não esqueça, também, de conectar o projeto Face ao projeto AzureCognitiveServices clicando com o botão direito em cima do projeto AzureCognitiveServices e indo a opção Add > Reference…, depois adicionando o projeto Face.

Assim o projeto AzureCognitiveServices conseguirá acessar o projeto Face e consumir o que precisar.

Adicionando o projeto Face ao projeto AzureCognitiveServices

4. Implemente a Detecção Facial na classe DeteccaoFacial.cs

Começo colocando todos os Usings necessários para o funcionamento correto da classe:

using Microsoft.Azure.CognitiveServices.Vision.Face;
using Microsoft.Azure.CognitiveServices.Vision.Face.Models;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

Depois adicione as variáveis de instância que utilizaremos nessa classe:

namespace Face
{
    public class DeteccaoFacial
    {
        private const string _chave = \"<sua chave>\";
        private const string _endpoint = \"<seu endpoint>\";
        private IFaceClient _servicoCognitivoDeFace;

Lembre de substituir \"<sua chave>\" e \"<seu endpoint>\" pelos valores obtidos no passo 2. Configure o Azure.
A interface IFaceClient é, justamente, a interface que utilizaremos para chamar os métodos que o Serviço Cognitivo de Face nos disponibiliza.
Crie um método construtor para que, sempre que essa classe for instanciada, a variável _servicoCognitivoDeFace seja instanciada também.

public DeteccaoFacial() =>
            _servicoCognitivoDeFace = new FaceClient(new ApiKeyServiceClientCredentials(_chave)) { Endpoint = _endpoint };

Há duas formas de se detectar rostos: por URL, ou por arquivo. Vamos implementar ambas as formas. Para isso, crie os métodos abaixo e não se importe com o método RetornarInformacoesDosRostosDetectados, voltaremos nele após a explicação desse código.

public async Task<ICollection<string>> DetectarRostosPorUrl(string url)
{
    var rostosDetectados = await _servicoCognitivoDeFace.Face.DetectWithUrlAsync(url,
            returnFaceAttributes: new List<FaceAttributeType> { FaceAttributeType.Age, FaceAttributeType.Emotion,
        FaceAttributeType.Gender, FaceAttributeType.Smile });
    return RetornarInformacoesDosRostosDetectados(rostosDetectados);
}

Temos a necessidade de fazer com que o retorno do método que criamos seja async Task<ICollection<string>>, pois o método utilizado pela biblioteca do Serviço de Face é assíncrona, mas retornaremos uma string contendo as informações encontradas na análise da imagem.

A chamada do método da biblioteca é muito simples. Passamos, primeiro, a url onde a imagem se encontra e, após isso, uma lista de atributos faciais que desejamos verificar.

Restringimos a análise apenas para idade, emoção, gênero e sorriso.

public async Task<ICollection<string>> DetectarRostosPorArquivo(string localDoArquivo)
{
    var arquivo = new FileStream(localDoArquivo, FileMode.Open);
    var rostosDetectados = await _servicoCognitivoDeFace.Face.DetectWithStreamAsync(arquivo,
        returnFaceAttributes: new List<FaceAttributeType> { FaceAttributeType.Age, FaceAttributeType.Emotion,
        FaceAttributeType.Gender, FaceAttributeType.Smile });
    return RetornarInformacoesDosRostosDetectados(rostosDetectados);
}

O método que detecta os rostos por arquivo tem apenas duas diferenças do outro. Primeiro, ele pega a string que é o local físico do arquivo e o cria uma instância de um FileStream, para poder ler o arquivo e, no lugar do URL, manda este arquivo para a análise. E o método chamado na biblioteca é o DetectWithStreamAsync.
Por fim, crie o método RetornarInformacoesDosRostosDetectados e lembre de fechar todas as chaves da classe.

private static ICollection<string> RetornarInformacoesDosRostosDetectados(IList<DetectedFace> rostosDetectados)
{
    if (rostosDetectados.Count == 0)
        return new List<string> { \"Nenhum rosto encontrado na imagem.\" };
    var informacoesDeRetorno = new List<string>(rostosDetectados.Count)
    {$\"Foram encontrados {rostosDetectados.Count} rosto(s) na imagem:\" };
    var posicaoDoRosto = 0;
    foreach (var rosto in rostosDetectados)
    {
        var informacoesDoRosto = $\"{++posicaoDoRosto}º - Aparenta ter {rosto.FaceAttributes.Age} anos, \";
        var tipoDeEmocao = string.Empty;
        var porcentagemDaEmocao = 0.0;
        var emocao = rosto.FaceAttributes.Emotion;
        if (emocao.Anger > porcentagemDaEmocao) { porcentagemDaEmocao = emocao.Anger; tipoDeEmocao = \"com raiva\"; }
        if (emocao.Contempt > porcentagemDaEmocao) { porcentagemDaEmocao = emocao.Contempt; tipoDeEmocao = \"com desprezo\"; }
        if (emocao.Disgust > porcentagemDaEmocao) { porcentagemDaEmocao = emocao.Disgust; tipoDeEmocao = \"com nojo\"; }
        if (emocao.Fear > porcentagemDaEmocao) { porcentagemDaEmocao = emocao.Fear; tipoDeEmocao = \"com medo\"; }
        if (emocao.Happiness > porcentagemDaEmocao) { porcentagemDaEmocao = emocao.Happiness; tipoDeEmocao = \"feliz\"; }
        if (emocao.Neutral > porcentagemDaEmocao) { porcentagemDaEmocao = emocao.Neutral; tipoDeEmocao = \"neutro\"; }
        if (emocao.Sadness > porcentagemDaEmocao) { porcentagemDaEmocao = emocao.Sadness; tipoDeEmocao = \"triste\"; }
        if (emocao.Surprise > porcentagemDaEmocao) { tipoDeEmocao = \"surpreso\"; }
        var possivelGenero = \"female\".Equals(rosto.FaceAttributes.Gender.ToString().ToLower())
            ? \"uma mulher\"
            : \"um homem\";
        informacoesDoRosto += $\"deve estar {tipoDeEmocao}, baseado na aparência, há a possibilidade de ser {possivelGenero}, porcentagem de sorriso é de: {rosto.FaceAttributes.Smile * 100}%\";
        informacoesDeRetorno.Add(informacoesDoRosto);
    }
    return informacoesDeRetorno;
}
}
}

Repare que para ambos os métodos de análise (DetectWithUrlAsync e DetectWithStreamAsync) o retorno é o mesmo, o que facilita o entendimento dos dados.
Nesse método, pegamos informações dos rostos detectados na imagem e, de acordo com os parâmetros que pedimos para identificar, escrevemos as informações obtidas.
Começamos verificando a quantidade de rostos, passamos pela idade, pela emoção, pelo possível gênero, e finalizamos com o sorriso.
Note que, com exceção da idade e do gênero, todas as outras informações são retornadas com um valor número de 0 a 1. Quanto maior esse número, maior a probabilidade daquela informação ser a certa.
A classe inteira pode ser visualizada no github.

5. Crie o arquivo Face.cshtml no projeto AzureCognitiveServices

Dentro do projeto AzureCognitiveServices crie uma pasta dentro da pasta Pages chamada Vision. Nesta pasta criada, adicione uma Razor Page e a chame de Face.cshtml.
Nela, criaremos dois formulários, um para enviar um link para o servidor e outro para enviar um arquivo.
Vamos, primeiro, editar o código do servidor. Para isso, clique com o botão direito no arquivo aberto e vá em Go to PageModel ou aperte o F7.
Esse arquivo é responsável pelo código C# desta página. Comece colocando os usings.

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.IO;

Adicione agora as variáveis que utilizaremos na classe.

namespace AzureCognitiveServices
{
    public class FaceModel : PageModel
    {
        public ICollection<string> Mensagens { get; private set; }
        private IWebHostEnvironment _environment;
        [BindProperty]
        public IFormFile Arquivo { get; set; }

Temos três variáveis. A variável ICollection<string> Mensagens será responsável por capturar e exibir as informações dos rostos que analisamos no arquivo anterior. Já a IWebHostEnvironment _environment receberá informações sobre o ambiente que a aplicação está hospedada. Por último temos a interface IFormFile Arquivo com o atributo [BindProperty]. Essa interface será o arquivo que poderemos enviar para análise e está marcado com o atributo para podermos ligá-lo a um campo no HTML.
Crie o método construtor.

public FaceModel(IWebHostEnvironment environment) =>
    _environment = environment;

Aqui, no método construtor, estamos usando injeção de dependência para capturarmos as informações de ambiente.

Agora criaremos o método post do servidor para capturar os envios do formulário HTML.

public void OnPost()
{
    if (Arquivo is null)
    {
        var url = Request.Form[\"url\"];
        Mensagens = new Face.DeteccaoFacial().DetectarRostosPorUrl(url).Result;
    }
    else
    {
        var pasta = Path.Combine(_environment.ContentRootPath, \"imagens\");
        if (!Directory.Exists(pasta))
            Directory.CreateDirectory(pasta);
        var caminhoDoArquivo = Path.Combine(_environment.ContentRootPath, \"imagens\", Arquivo.FileName);
        using (var fileStream = new FileStream(caminhoDoArquivo, FileMode.Create))
            Arquivo.CopyTo(fileStream);
        Mensagens = new Face.DeteccaoFacial().DetectarRostosPorArquivo(caminhoDoArquivo).Result;
        Directory.Delete(pasta, true);
    }
}
}
}

Como teremos dois formulários no HTML, primeiro verificamos qual deles estamos usando para poder chamar o método correto da classe que analisa as imagens.

Portanto, caso a interface IFormFile Arquivo seja nula, significa que o usuário optou por enviar uma URL, então simplesmente capturamos a informação enviada pelo campo url do formulário HTML e o enviamos para a análise através da url.

Caso a interface não seja nula, temos alguns passos a mais. Primeiro começamos copiando a imagem enviada e colocando no servidor, numa pasta própria. Depois pegamos o caminho do arquivo criado e enviamos para análise através do método que analisa um arquivo, lembrando que este método pega o caminho e o converte para um FileStream. Por último deletamos a pasta criada e todo seu conteúdo.

Isso encerra o código da parte do servidor. Vamos voltar a parte do HTML.

A classe HTML terá apenas isso de código.

@page
@model AzureCognitiveServices.FaceModel
@{
    ViewData[\"Title\"] = \"Face\";
}
<h1>Face</h1>

Coloque o seguinte código no restante do arquivo:

<div>
    <div>
        <fieldset>
            <legend>Testar com uma URL</legend>
            <form method=\"post\">
                <div>
                    <label for=\"url\">URL de uma imagem contendo rostos:</label>
                    <input type=\"url\" name=\"url\" id=\"url\" />
                </div>
                <button type=\"submit\">Detectar faces</button>
            </form>
        </fieldset>
    </div>
    <div>
        <fieldset>
            <legend>Testar com um arquivo</legend>
            <form method=\"post\" enctype=\"multipart/form-data\">
                <div>
                    <label for=\"arquivo\">Arquivo de uma imagem contendo rostos:</label>
                    <input type=\"file\" asp-for=\"Arquivo\" id=\"arquivo\" />
                </div>
                <button type=\"submit\">Detectar faces</button>
            </form>
        </fieldset>
    </div>
</div>

@if (!(Model.Mensagens is null))
{
    @foreach (var mensagem in Model.Mensagens)
    {
        <div role=\"alert\">
            @mensagem
            <button type=\"button\" data-dismiss=\"alert\" aria-label=\"Close\">
                <span aria-hidden=\"true\">&times;</span>
            </button>
        </div>
    }
}

Isso é apenas código HTML simples, porém com algumas modificações, pois estamos utilizando o Razor Pages.

A principal diferença está no último bloco de código, onde começamos verificando se a variável Mensagens, da parte do servidor, está nula. Se não estiver, significa que o usuário já fez o envio do conteúdo e, agora, estamos lendo a resposta do servidor com as informações capturadas.

Também temos um foreach para criar um pequeno texto para cada uma das informações obtidas.

Os arquivos inteiros podem ser visualizados no github, tanto o arquivo de HTML, quando a classe do servidor.

6. Adicione ao menu a página criada

Agora que tudo está criado, chegamos ao momento de adicionar ao menu a página criada. Para isso, vá na pasta Shared e abra o arquivo _Layout.cshtml. Esse arquivo tem todo o layout que é compartilhado por todas as páginas, o que se repete em todas, como menu e footer, por exemplo.

Procure o trecho de código similar a isso:

<div>
    <ul>
        <li>
            <a asp-area=\"\" asp-page=\"/Index\">Home</a>
        </li>
        <li>
            <a asp-area=\"\" asp-page=\"/Privacy\">Privacy</a>
        </li>
    </ul>
</div>

Esse trecho é o que cria o menu. Adicione o seguinte trecho antes da última tag </div>

<li>
    <a href=\"#\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">
        Vision
    </a>
    <div aria-labelledby=\"navbarDropdownMenuLink\">
        <a asp-page=\"/Vision/Face\">Face</a>
    </div>
</li>

Isso fará com que o menu tenha mais uma opção, similar a imagem a seguir.

\"exemplo-de-menu\"
Exemplo do menu com a opção de ir à página Face

Conclusão

Criamos, desde baixar a IDE, até implementarmos nosso primeiro uso dos Serviços Cognitivos da Azure Face. No final, a estrutura do seu projeto deve se parecer com isso.

Estrutura atual do projeto

Vimos a facilidade de utilizar um serviço de Inteligência Artificial de forma eficaz e, com isso, temos uma economia de tempo de desenvolvimento e deixamos a responsabilidade do poder computacional em outro lugar. Tudo isso faz com que os custos diminuam e possam ser alocados em outros locais.

Todo o projeto está disponível no github com uma licença que possibilita colaboração. Sinta-se a vontade.

Siga-nos em nossas redes sociais!

Posts recentes

Ultimas do blog