Outubro de 2016

Volume 31 – Número 10

Serviços Cognitivos - Reconhecimento facial e de emoções no Xamarin.Forms com os Serviços Cognitivos da Microsoft

Por Alessandro Del Del

Na conferência Build 2016, a Microsoft anunciou a primeira prévia dos Serviços Cognitivos (microsoft.com/cognitive-services), um conjunto abrangente de APIs RESTful de plataforma cruzada que você pode usar para criar a nova geração de aplicativos baseados na interação natural do usuário para qualquer plataforma em qualquer dispositivo. Os Serviços Cognitivos, também conhecidos como “Projeto Oxford”, se baseiam em aprendizado de máquina e se encaixam perfeitamente à filosofia de conversa como plataforma que a Microsoft pretende trazer para o ecossistema dos aplicativos. Em nível mais alto, as APIs de Serviços Cognitivos estão disponíveis através de serviços RESTful e oferecem, no momento, as seguintes categorias de APIs:

  • Pesquisa Visual: Os serviços de Pesquisa Visual oferecem APIs que permitem a análise de imagens e vídeos para identificar rostos e emoções e detectar informações acionáveis. Essa categoria inclui as APIs de Pesquisa Visual Computacional, Detecção Facial, Detecção de Emoções e Vídeo.
  • Fala: Os serviços de Fala oferecem APIs que tornam mais fácil implementar texto para fala, reconhecer fala natural e até reconhecer quem está falando com o serviço de reconhecimento de falante. Incluem as APIs de Fala do Bing, do Serviço Inteligente de Reconhecimento Personalizado e de Reconhecimento de Falante.
  • Linguagem: Os serviços de Linguagem são orientados para o entendimento de linguagem natural, o que significa detectar e corrigir erros de ortografia, compreender comandos de voz e analisar textos complexos, incluindo sentimentos e frases-chave. Incluem as APIs de Verificação Ortográfica do Bing, do Serviço Inteligente de Reconhecimento Vocal, de Análise Linguística, Análise de Texto e Modelo de Linguagem da Web.
  • Conhecimento: Os serviços de Conhecimento ajudam o aplicativo a ampliar o conhecimento dos consumidores ao encontrar recomendações personalizadas de produtos, eventos, lugares e artigos ou publicações acadêmicas. Incluem as APIs de Conhecimento Acadêmico, do Serviço de Inteligência de Vinculação de Entidade, do Serviço de Exploração de Conhecimento e de Recomendações.
  • Search: Os serviços de Pesquisa estão baseados no Bing e permitem que você implemente ferramentas de pesquisa poderosas em aplicativos. Os nomes dos serviços incluídos são autoexplicativos: APIs de Sugestão Automática do Bing, Pesquisa de Imagens do Bing, Pesquisa de Notícias do Bing, Pesquisa de Vídeos do Bing e Pesquisa da Web do Bing.

Neste artigo, vou explicar como combinar as APIs de Detecção Facial e de Emoções para recuperar detalhes faciais e emoções em fotos que você pode fazer com uma câmera ou selecionar em um álbum no disco em um aplicativo Xamarin.Forms criado com C# e Visual Studio 2015 em execução no Android, iOS ou Windows 10. A Figura 1 mostra os resultados do tutorial do artigo. É importante mencionar que, embora o Xamartin.Forms tenha sido usado para este artigo, a mesma coisa pode ser feita com aplicativos Xamarin tradicionais e também em qualquer outra plataforma que suporte REST. Estou assumindo que você tem conhecimento básico sobre a criação de aplicativos Xamarin.Forms e sobre os conceitos relacionados a compartilhamento de código; se não tiver, não deixe de ler meus artigos anteriores: “Build a Cross-Platform UX with Xamarin.Forms” (“Crie um UX de plataforma cruzada com Xamarin.Forms”, somente em inglês) (msdn.com/magazine/mt595754) e “Compartilhar código de interface de usuário entre plataformas móveis com Xamarin.Forms” (msdn.com/magazine/dn904669).

Reconhecimento facial e de emoções em um aplicativo de plataforma cruzada com Xamarin.Forms
Figura 1 Reconhecimento facial e de emoções em um aplicativo de plataforma cruzada com Xamarin.Forms (dispositivo Android à esquerda, desktop com Windows 10 à direita)

Como assinar as APIs de Serviços Cognitivos

Para compilar aplicativos que se aproveitem dos Serviços Cognitivos, você precisa se inscrever no serviço que lhe interessa. No momento, a Microsoft está oferecendo avaliações gratuitas que podem ser ativadas na página de assinaturas (bit.ly/2b2rKDO), mas os planos atuais podem sofrer alterações no futuro. Quando estiver na página, registre-se com uma conta da Microsoft, depois clique em “Request new trials” (solicitar novas avaliações). Você verá uma lista de serviços disponíveis; não se esqueça de selecionar as visualizações gratuitas das APIs de Detecção Facial e de Emoções. Neste momento, sua página de assinaturas exibirá a lista de serviços ativos; você deve ver as assinaturas das APIs de Detecção Facial e de Emoções. A Figura 2 mostra um exemplo baseado em minhas assinaturas. Note que há duas chaves secretas para cada serviço ativo. Você precisará de uma delas para invocar as APIs. Por ora, mantenha-as ocultas. Você vai exibir a chave ao criar o aplicativo do Xamarin.Forms.

Como ativar as assinaturas para as APIs de Detecção Facial e de Emoções
Figura 2 Como ativar as assinaturas para as APIs de Detecção Facial e de Emoções

Em termos gerais, os Serviços Cognitivos fornecem APIs RESTful, logo, é possível interagir com esses serviços por meio de solicitações HTTP em qualquer plataforma e qualquer linguagem que tenha suporte a REST. Por exemplo, a solicitação HTTP POST a seguir demonstra como enviar uma imagem para o serviço de reconhecimento de emoções para detecção da emoção:

POST https://api.projectoxford.ai/emotion/v1.0/recognize HTTP/1.1
Content-Type: application/json
Host: api.projectoxford.ai
Content-Length: 107
Ocp-Apim-Subscription-Key: YOUR-KEY-GOES-HERE
{ "url": "http://www.samplewebsite.com/sampleimage.jpg" }

Você precisa, é claro, substituir Ocp-Apim-Subscription-Key por uma de suas chaves e a URL da imagem falsa por um endereço de imagem verdadeiro. Em troca, o serviço de reconhecimento de emoções enviará de volta o resultado da detecção como uma resposta JSON, como mostrado na Figura 3.

Figura 3 Resposta da detecção do serviço de reconhecimento de emoções

[
  {
    "faceRectangle": {
      "height": 70,
      "left": 26,
      "top": 35,
      "width": 70
    },
    "scores": {
      "anger": 2.012591E-11,
      "contempt": 1.95578984E-10,
      "disgust": 1.02281912E-10,
      "fear": 1.16242682E-13,
      "happiness": 1.0,
      "neutral": 9.79047E-09,
      "sadness": 2.91102975E-10,
      "surprise": 1.71011272E-09
    }
  }
]

A resposta de exemplo na Figura 3 mostra que o serviço de Emoções retornou o retângulo em que um rosto foi detectado e uma matriz chamada scores contendo uma lista de emoções e um valor entre 0 e 1, que indica a probabilidade da emoção ser verdadeira. Em geral, enviar solicitações HTTP aos serviços RESTful e esperar uma resposta JSON é uma abordagem comum a todos os Serviços Cognitivos. No entanto, para desenvolvedores .NET trabalhando com C#, a Microsoft também oferece bibliotecas de cliente portáteis que você pode baixar do NuGet e que facilitam a interação com serviços em código gerenciado e de forma totalmente orientada ao objeto. Este é o caso das APIs de Detecção Facial e de Emoções, como você verá em breve. Não se esqueça de conferir o documento oficial, que traz exemplos baseados na abordagem REST e também em bibliotecas de cliente, quando disponíveis (bit.ly/2b2KJrB). Agora que você se registrou em ambos os serviços e já tem suas chaves, é hora de criar uma aplicativo de plataforma cruzada com o Xamarin.Forms e o Microsoft Visual Studio 2015.

Como criar um aplicativo Xamarin.Forms

Como você sabe, é possível criar um aplicativo de plataforma cruzada com o Xamarin.Forms escolhendo o modelo de projeto Portable (portátil) ou Shared (compartilhado). Já que vou explicar como aproveitar bibliotecas de cliente nas APIs de Serviços Cognitivos, o aplicativo de exemplo é baseado no modelo de Biblioteca de Classes Portátil (PCL). No Visual Studio 2015, selecione File | New Project (Arquivo | Novo Projeto). Se já tiver instalado as atualizações mais recentes do Xamarin (xamarin.com/download), você vai encontrar um novo modelo de projeto chamado Blank Xaml App (Xamarin.Forms portátil) no nó Visual C#, Cross-Platform (Plataforma Cruzada) da caixa de diálogo New Project (Novo Projeto). Esse é um modelo interessante, que fornece uma página XAML vazia e evita a necessidade de se criar uma manualmente. A Figura 4 mostra o novo modelo.

Chame a solução de FaceEmotionRecognition e clique em OK. Durante a geração da solução, você precisará especificar a versão de destino mínima para o projeto da Plataforma Universal do Windows (UWP). A escolha é sua, mas eu recomendo direcionar para a maior versão disponível.

Como criar um novo aplicativo Xamarin.Forms
Figura 4 Como criar um novo aplicativo Xamarin.Forms

Apresentando plug-ins para Xamarin

O aplicativo de exemplo vai usar as APIs de Serviços Cognitivos para reconhecer detalhes faciais e emoções a partir de imagens usando fotos existentes no dispositivo ou fazendo novas fotos com a câmera. Isso implica que o aplicativo precisará de acesso à Internet para se conectar aos serviços e terá de fornecer a capacidade de fazer e selecionar fotos. Embora um aplicativo consiga se conectar facilmente a uma rede, cabe a você, enquanto desenvolvedor, verificar a disponibilidade de rede. Na verdade, recursos como verificar a disponibilidade de rede e fazer fotografias exigem a escrita de códigos específicos nos projetos para Android, iOS e Windows. Por sorte, o Xamarin dá suporte a plug-ins que podem ser usados no Xamarin.Forms e instalados no projeto PCL para fazer o trabalho por você. Um plug-in é uma biblioteca instalada do NuGet que encapsula as APIs nativas em uma implementação de código comum e invocada no projeto PCL. Existe um grande número de plug-ins disponíveis – alguns desenvolvidos e com suporte no Xamarin, outros criados e publicados pela comunidade de desenvolvedores. Os plug-ins são todos de código aberto e estão listados no GitHub em bit.ly/29XZ3VM. Neste artigo eu vou mostrar como usar os plug-ins Connectivity (conectividade) e Media (mídia).

Instalando os pacotes NuGet

Quando a solução estiver pronta, a primeira coisa a fazer é instalar os seguintes pacotes NuGet:

  • Microsoft.ProjectOxford.Face: Instala a biblioteca de cliente para as APIs de Detecção Facial e deve ser instalado apenas no projeto PCL.
  • Microsoft.ProjectOxford.Emotion: Instala a biblioteca de cliente para as APIs de Detecção de Emoções, como a API de Detecção Facial, e deve ser instalado apenas no projeto PCL.
  • Xam.Plugin.Connectivity: Contém o plug-in de conectividade para o Xamarin.Forms e deve ser instalado em todos os projetos da solução.
  • Xam.Plugin.Media: Contém o plug-in de mídia para o Xamarin.Forms e, como a API de Conectividade, deve ser instalado em todos os projetos da solução.

Depois de instalar os pacotes NuGet necessários, é preciso compilar a solução antes de escrever o código, para que todas as referências sejam atualizadas.

Projetando a interface do usuário

A interface do usuário do aplicativo de exemplo tem uma única página. Em nome da simplicidade, vou usar o arquivo MainPage.xaml, gerado automaticamente. Essa página define dois botões, um para fazer uma fotografia com a câmera e outro para carregar uma imagem existente, um controle ActivityIndicator, que vai mostrar o status ocupado enquanto estiver esperando uma resposta do serviço, um controle Image, que vai exibir a imagem selecionada, vários rótulos, dentro dos painéis StackLayout, vinculados por dados a uma classe personalizada que vai conter o resultado das detecções feitas na imagem selecionada. A Figura 5 mostra o código XAML completo para a página.

Figura 5 Interface do usuário para a página principal

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:local="clr-namespace:FaceEmotionRecognition"
  xmlns:conv="clr-namespace:FaceEmotionRecognition. 
    Converters;assembly=FaceEmotionRecognition"
    x:Class="FaceEmotionRecognition.MainPage">
  <StackLayout Orientation="Vertical">
    <Button x:Name="TakePictureButton" Clicked="TakePictureButton_Clicked"
      Text="Take from camera"/>
    <Button x:Name="UploadPictureButton" Clicked="UploadPictureButton_Clicked"
      Text="Pick a photo"/>
    <ActivityIndicator x:Name="Indicator1" IsVisible="False" IsRunning="False" />
    <Image x:Name="Image1" HeightRequest="240" />
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Gender: "/>
      <Label x:Name="GenderLabel" Text="{Binding Path=Gender}" />
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Age: "/>
      <Label x:Name="AgeLabel" Text="{Binding Path=Age}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Emotion: "/>
      <Label x:Name="EmotionLabel" Text="{Binding Path=Emotion}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Smile: "/>
      <Label x:Name="SmileLabel"
        Text="{Binding Path=Smile}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Glasses: "/>
      <Label x:Name="GlassesLabel" Text="{Binding Path=Glasses}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Beard: "/>
      <Label x:Name="BeardLabel"
        Text="{Binding Path=Beard}"/>
    </StackLayout>
    <StackLayout Orientation="Horizontal" Padding="3">
      <Label Text="Moustache: "/>
      <Label x:Name="MoustacheLabel"
        Text="{Binding Path=Moustache}"/>
    </StackLayout>
  </StackLayout>
</ContentPage>

O próximo passo é preparar um local para armazenar o resultado da detecção facial e de emoções.

Armazenando os resultados da detecção em uma classe

Em vez de preencher rótulos manualmente na interface de usuário com os resultados da detecção facial e de emoções, a melhor prática é criar uma classe personalizada. Esta abordagem não só é mais orientada ao objeto, como também permite a vinculação de dados da instância da classe à interface do usuário. Isso posto, vamos criar uma nova classe chamada FaceEmotionDetection:

public class FaceEmotionDetection
{
  public string Emotion { get; set; }
  public double Smile { get; set; }
  public string Glasses { get; set; }
  public string Gender { get; set; }
  public double Age { get; set; }
  public double Beard { get; set; }
  public double Moustache { get; set; }
}

Cada propriedade tem um nome autoexplicativo e vai armazenar informações provenientes da combinação das APIs de Detecção Facial e de Emoções.

Declarando os clientes de serviço

Antes de escrever qualquer outro código, é recomendável adicionar o seguinte, usando diretivas:

using Microsoft.ProjectOxford.Emotion;
using Microsoft.ProjectOxford.Emotion.Contract;
using Microsoft.ProjectOxford.Face;
using Microsoft.ProjectOxford.Face.Contract;
using Plugin.Connectivity;
using Plugin.Media;

Elas vão simplificar a invocação de nomes de objeto para as APIs de Serviços Cognitivos e para os plug-ins. As APIs de Detecção Facial e de Emoções fornecem as classes Microsoft.ProjectOxford.Face.Face­ServiceClient e Microsoft.ProjectOxford.Emotion.Emotion­ServiceClient, que se conectam aos Serviços Cognitivos e retornam informações sobre detalhes faciais e de emoções, respectivamente. O que você precisa fazer primeiro é declarar uma instância de ambas, passando a chave secreta para o construtor, como mostrado aqui:

private readonly IFaceServiceClient faceServiceClient;
private readonly EmotionServiceClient emotionServiceClient;
public MainPage()
{
  InitializeComponent();
  // Provides access to the Face APIs
  this.faceServiceClient = new FaceServiceClient("YOUR-KEY-GOES-HERE");
  // Provides access to the Emotion APIs
  this.emotionServiceClient = new EmotionServiceClient("YOUR-KEY-GOES-HERE");
}

Note que você precisa fornecer suas próprias chaves secretas. As chaves secretas das APIs de Detecção Facial e de Emoções podem ser encontradas na página de assinaturas do portal de Serviços Cognitivos da Microsoft (bit.ly/2b2rKDO), conforme mostrado na Figura 2.

Capturando e carregando imagens

No Xamarin.Forms, os acessos à câmera e ao sistema de arquivos exigem a escrita de código específico para a plataforma. Um abordagem mais simples seria usar o plug-in de Mídia para o Xamarin.Forms, que permite selecionar fotos e vídeos do disco e fazer fotos e vídeos com a câmera no projeto PCL com apenas algumas linhas de código. Este plug-in expõe uma classe chamada CrossMedia, que expõe os seguintes membros:

  • Current (atual): Retorna uma instância singleton da classe CrossMedia.
  • IsPickPhotoSupported e IsPickVideoSupported: Propriedades booleanas que retornam verdadeiro se houver suporte no dispositivo atual à seleção de imagens e vídeos do disco.
  • PickPhotoAsync e PickVideoAsync: Métodos que invocam a interface do usuário específica da plataforma para selecionar uma imagem ou vídeo local, respectivamente, e retornam um objeto do tipo MediaFile.
  • IsCameraAvailable: Propriedade booleana que retorna verdadeiro se o dispositivo tiver uma câmera embutida.
  • IsTakePhotoSupported e IsTakeVideoSupported: Propriedades booleanas que retornam verdadeiro se houver suporte no dispositivo atual para fotos e vídeos feitos com a câmera.
  • TakePhotoAsync e TakeVideoAsync: Métodos que inicializam a câmera embutida para fazer fotos ou vídeos, respectivamente, e retorna um objeto do tipo MediaFile.

Não se esqueça de definir as permissões apropriadas no manifesto do aplicativo para acessar a câmera. Por exemplo, em um projeto UWP, você precisa de permissões da Webcam e da Biblioteca de Imagens, enquanto no Android você precisa das permissões CAMERA, READ_EXTERNAL_STORAGE, e WRITE_EXTERNAL_STORAGE. Se as permissões necessárias não tiverem sido definidas, ocorrerão exceções de tempo de execução. Agora, vamos escrever o manipulador de eventos Clicked para UploadPictureButton, que é exibido na Figura 6.

Figura 6 Selecionando uma imagem do disco

private async void UploadPictureButton_Clicked(object sender, EventArgs e)
{
  if (!CrossMedia.Current.IsPickPhotoSupported)
  {
    await DisplayAlert("No upload", "Picking a photo is not supported.", "OK");
    return;
  }
  var file = await CrossMedia.Current.PickPhotoAsync();
  if (file == null)
    return;
  this.Indicator1.IsVisible = true;
  this.Indicator1.IsRunning = true;
  Image1.Source = ImageSource.FromStream(() => file.GetStream());
  this.Indicator1.IsRunning = false;
  this.Indicator1.IsVisible = false;
}

O code first verifica se há suporte para seleção de imagens e mostra uma mensagem de erro se IsPickPhotoSupported retornar falso. PickPhoto­Async (bem como PickVideoAsync) retorna um objeto do tipo Media­File, que é uma classe definida no namespace Plugin.Media e que representa o arquivo selecionado. Você deve invocar o método GetStream para retornar uma transmissão que pode ser usada como fonte para o controle Image por meio do método FromStream. Fazer uma foto com a câmera também é muito fácil, como mostrado na Figura 7.

Figura 7 Fazendo uma foto com a câmera

private async void TakePictureButton_Clicked(object sender, EventArgs e)
{
  await CrossMedia.Current.Initialize();
  if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.
    IsTakePhotoSupported)
  {
    await DisplayAlert("No Camera", "No camera available.", "OK");
    return;
  }
  var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
  {
    SaveToAlbum = true,
    Name = "test.jpg"
  });
  if (file == null)
    return;
  this.Indicator1.IsVisible = true;
  this.Indicator1.IsRunning = true;
  Image1.Source = ImageSource.FromStream(() => file.GetStream());
  this.Indicator1.IsRunning = false;
  this.Indicator1.IsVisible = false;
}

O ponto de interesse aqui é que TakePhotoAsync assume um parâmetro do tipo StoreCameraMediaOptions, um objeto que permite especificar onde e como salvar uma foto. Você pode definir a propriedade SaveToAlbum como verdadeira se quiser que a foto seja salva no rolo da câmera local, ou definir a propriedade Directory se quiser salvar em uma pasta diferente. Como você pode ver, com muito pouco esforço e algumas linhas de código, o aplicativo pode facilmente se aproveitar de uma importante funcionalidade de todas as plataformas para as quais há suporte.

Detectando emoções e implementando o reconhecimento facial

Agora é hora de implementar o reconhecimento facial e de emoções. Como este é um artigo introdutório, vou me concentrar na simplicidade. Vou mostrar como implementar a detecção de um único rosto na imagem e descrever os objetos e membros mais importantes nas APIs. Também vou dar sugestões sobre como implementar detecções mais detalhadas, quando for o caso. Partindo dessas premissas, vamos começar a escrever um método assíncrono que realiza detecções. A primeira parte trata de detecção de emoções e tem a seguinte aparência:

private async Task<FaceEmotionDetection> DetectFaceAndEmotionsAsync(MediaFile inputFile)
{
  try
  {
    // Get emotions from the specified stream
    Emotion[] emotionResult = await
      emotionServiceClient.RecognizeAsync(inputFile.GetStream());
    // Assuming the picture has one face, retrieve emotions for the
    // first item in the returned array
    var faceEmotion = emotionResult[0]?.Scores.ToRankedList();

O método recebe o MediaFile produzido ao selecionar ou fazer uma foto. Detectar as emoções dos rostos de uma foto é simples, porque você simplesmente invoca o método RecognizeAsync da instância da classe EmotionServiceClient. Esse método recebe uma transmissão ou uma URL como argumento. Neste caso, ele recebe a transmissão do objeto MediaFile. RecognizeAsync retorna uma matriz de objetos Emotion. Cada objeto Emotion da matriz armazena emoções detectadas em um único rosto em uma foto. Ao assumir que a foto selecionada tem apenas um rosto, o código recupera o primeiro item da matriz. O tipo Emotion expõe uma propriedade chamada Scores, que contém uma lista de oito nomes de emoções e seu valor aproximado. Mais especificamente, você obtém um IEnumerable<string, float>. Ao invocar o método ToRankedList, você obtém uma classificação das emoções detectadas. As APIs não conseguem detectar uma única emoção precisamente. Em vez disso, detectam uma quantidade de emoções possíveis. O valor mais alto retornado é, aproximadamente, a emoção de fato no rosto, mas ainda há outros valores que podem ser verificados.  O valor mais alto da classificação representa a emoção com o mais alto nível de probabilidade estimada, que possivelmente é a emoção de fato no rosto. Para entender melhor, considere a seguinte classificação de emoções recuperadas com a ajuda de dicas de dados do depurador, que se baseia na imagem de exemplo exibida na Figura 1:

[0] = {[Happiness, 1]}
[1] = {[Neutral, 1.089301E-09]}
[2] = {[Surprise, 7.085784E-10]}
[3] = {[Sadness, 9.352855E-11]}
[4] = {[Disgust, 4.52789E-11]}
[5] = {[Contempt, 1.431213E-11]}
[6] = {[Anger, 1.25112E-11]}
[7] = {[Fear, 5.629648E-14]}

Com você pode ver, Happiness (felicidade) tem valor 1, que é o mais alto da lista e é a probabilidade estimada da emoção verdadeira. O próximo passo é detectar atributos faciais. A classe FaceServiceClient expõe o método DetectAsync, que é extremamente poderoso. Ela não só consegue recuperar atributos faciais como gênero, idade e sorriso, mas também consegue reconhecer pessoas, retornar o retângulo facial (a área da foto em que o rosto foi detectado) e 27 pontos de referência no rosto que permitem que o aplicativo identifique informações como a posição de nariz, boca, orelhas e olhos na imagem. O DetectAsync tem a seguinte assinatura:

Task<Contract.Face[]> DetectAsync(Stream imageStream,
  bool returnFaceId = true, bool returnFaceLandmarks = false,
  IEnumerable<FaceAttributeType> returnFaceAttributes = null);

Na invocação mais básica, o DetectAsync requer uma transmissão que aponte para uma imagem ou URL e retorne o retângulo facial, enquanto os parâmetros opcionais returnFaceId e returnFaceLandmarks permitem identificar uma pessoa e retornar pontos de referência do rosto, respectivamente. As APIs de Detecção Facial permitem que você crie grupos de pessoas e atribua uma identificação a cada uma, para fazer o reconhecimento facilmente. Os pontos de referência faciais, por sua vez, são úteis para identificar as características de um rosto e estarão disponíveis por meio da propriedade FaceLandmarks do objeto Face. A identificação e os pontos de referência estão além do escopo deste artigo, mais você pode saber mais sobre esses tópicos em bit.ly/2adPvoP e bit.ly/2ai9WjV, respectivamente. Da mesma forma, não vou mostrar a você como usar os pontos de referência no rosto, mas eles estão armazenados na propriedade FaceLandmarks do objeto Face. No cenário de exemplo atual, o objetivo é recuperar atributos faciais. A primeira coisa de que você precisa é uma matriz da enumeração FaceAttributeType, que define a lista de atributos que você deseja recuperar:

// Create a list of face attributes that the
// app will need to retrieve
var requiredFaceAttributes = new FaceAttributeType[] {
  FaceAttributeType.Age,
  FaceAttributeType.Gender,
  FaceAttributeType.Smile,
  FaceAttributeType.FacialHair,
  FaceAttributeType.HeadPose,
  FaceAttributeType.Glasses
  };

Em seguida, invoque DetectAsync, passando a transmissão da imagem e a lista de atributos faciais. Os argumentos returnFaceId e returnFaceLandmarks são falsos porque as informações relacionadas são desnecessárias neste momento. A invocação do método tem a seguinte aparência:

// Get a list of faces in a picture
var faces = await faceServiceClient.DetectAsync(inputFile.GetStream(),
  false, false, requiredFaceAttributes);
// Assuming there is only one face, store its attributes
var faceAttributes = faces[0]?.FaceAttributes;

O DetectAsync retorna uma matriz de objetos Face, cada um representando um rosto na foto. O código pega o primeiro item da matriz, que representa um único rosto, e recupera os atributos faciais. Veja como a última linha usa o operador condicional nulo (?), introduzido com o C# 6, que retorna nulo se o primeiro elemento na matriz também for nulo, em vez de gerar a exceção NullReferenceException. Mais informações sobre esse operador podem ser encontradas em bit.ly/2bc8VZ3. Agora que já tem informações faciais e de emoções, você pode criar uma instância da classe FaceEmotionDetection e preencher as propriedades, conforme demonstrado no código a seguir:

FaceEmotionDetection faceEmotionDetection = new FaceEmotionDetection();
faceEmotionDetection.Age = faceAttributes.Age;
faceEmotionDetection.Emotion = faceEmotion.FirstOrDefault().Key;
faceEmotionDetection.Glasses = faceAttributes.Glasses.ToString();
faceEmotionDetection.Smile = faceAttributes.Smile;
faceEmotionDetection.Gender = faceAttributes.Gender;
faceEmotionDetection.Moustache = faceAttributes.FacialHair.Moustache;
faceEmotionDetection.Beard = faceAttributes.FacialHair.Beard;

Alguma considerações são necessárias neste momento:

  • O valor mais alto na lista de emoções é obtido ao invocar FirstOrDefault sobre o resultado da invocação ao método Scores.ToRankedList, que retorna um IEnumerable<string, float>.
  • O valor retornado por FirstOrDefault é um objeto do tipo KeyValuePair<string, float> e a Chave do tipo cadeia de caracteres armazena o nome da emoção em um texto legível para humanos que será exibido na interface do usuário.
  • Glasses (óculos) é uma enumeração que especifica se o rosto detectado está usando óculos e de que tipo. Para simplificar, o código invoca ToString, mas você pode implementar um conversor para um formato de cadeia de caracteres diferente.

O bloco final no corpo do método retorna a instância da classe FaceEmotionDetection e implementa o tratamento de exceção:

return faceEmotionDetection;
  }
  catch (Exception ex)
  {
    await DisplayAlert("Error", ex.Message, "OK");
    return null;
  }
}

A última coisa que você precisa fazer é invocar o método personalizado DetectFaceAndEmotionAsync. Você pode fazer isso dentro de ambos os manipuladores de eventos Clicked, logo antes de definir como falsas as propriedades IsRunning e IsVisible do controle ActivityIndicator:

FaceEmotionDetection theData = await DetectFaceAndEmotionsAsync(file);
this.BindingContext = theData;
this.Indicator1.IsRunning = false;
this.Indicator1.IsVisible = false;

A propriedade BindingContext da página recebe uma instância da classe FaceEmotionDetection como fonte de dados e os controles filho vinculados por dados exibirão automaticamente as informações relacionadas. Com padrões como Model-View-ViewModel, você encapsularia o resultado com uma classe ViewModel. Depois de muito trabalho, você está pronto para testar o aplicativo.

Testando o aplicativo

Selecione sua plataforma preferida e pressione F5. Se usar os emuladores da Microsoft, você pode se aproveitar das ferramentas do emulador para selecionar uma webcam física para fazer as fotos e pode simular um cartão SD para carregar arquivos. A Figura 1 mostra o resultado da detecção em uma foto minha, em um dispositivo Android e no Windows 10 em execução em modo desktop.

As APIs de Detecção Facial e de Emoções fizeram um trabalho incrível, porque os valores retornados são muito próximos da verdade, embora ainda aproximados. Vale mencionar que a classe FaceEmotionDetection tem algumas propriedades do tipo duplo, tais como Smile (sorriso), Beard (barba) e Moustache (bigode). Elas retornam valores numéricos que podem não fazer muito sentido para o usuário final em um aplicativo do mundo real. Assim, caso você queira converter esses valores numéricos em cadeias de caracteres legíveis para humanos, considere implementar conversores de valor e a interface IValueConverter (bit.ly/2bZn01J).

Implementando a verificação de conectividade de rede

Um aplicativo bem projetado que precisa acessar recursos na Internet deve verificar a disponibilidade de conexão logo de início. Para acessar a câmera e o sistema de arquivos, a verificação de disponibilidade de conexão no Xamarin.Forms requer um código específico para a plataforma. Felizmente, o plug-in Connectivity está aí para ajudar, fornecendo uma maneira compartilhada de realizar essa verificação diretamente do projeto PCL. O plug-in oferece uma classe chamada CrossConnectivity com a propriedade Current, que representa uma instância singleton da classe. Ela expõe uma propriedade booleana chamada IsConnected que simplesmente retorna verdadeiro se houver uma conexão disponível. Para verificar a disponibilidade de conexão no aplicativo de exemplo, basta colocar o código a seguir após a declaração do método DetectFaceAndEmotionAsync:

private async Task<FaceEmotionDetection>
  DetectFaceAndEmotionsAsync(MediaFile inputFile)
{
  if(!CrossConnectivity.Current.IsConnected)
  {
    await DisplayAlert("Network error",
      "Please check your network connection and retry.", "OK");
    return null;
  }

A classe também expõe os seguintes membros de interesse:

  • ConnectivityChanged: Um evento que é acionado quando o estado de conexão muda. Você pode assinar esse evento e obter informações sobre o status da conectividade através de um objeto do tipo ConnectivityChangedEventArgs.
  • BandWidths: Uma propriedade que retorna uma lista de larguras de banda disponíveis para a plataforma atual.

Informações adicionais sobre o plug-in Connectivity podem ser encontradas em bit.ly/2bbU7wu.

Conclusão

Os Serviços Cognitivos da Microsoft fornecem serviços RESTful e APIs cheias de recursos baseadas em aprendizado de máquina que permitem criar aplicativos de última geração. Ao combinar o poder desses serviços com o Xamarin, você conseguirá trazer a interação natural do usuário a seus aplicativos de plataforma cruzada para Android, iOS e Windows, oferecendo uma experiência incrível aos consumidores.


Alessandro Del Sole é Microsoft MVP desde 2008. Premiado cinco vezes como MVP of the Year, é autor de muitos livros, eBooks, vídeos instrutivos e artigos sobre o desenvolvimento em .NET com o Visual Studio. Del Sole trabalha como desenvolvedor especialista em soluções para a Brain-Sys (brain-sys.it), com foco em desenvolvimento, treinamento e consultoria em .NET. Você pode segui-lo no Twitter: @progalex.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: James McCaffrey e James Montemagno