Octubre de 2016

Volumen 31, número 10

Cognitive Services: reconocimiento de caras y emociones en Xamarin.Forms con Microsoft Cognitive Services

Por Alessandro Del Del

En el congreso Build 2016, Microsoft anunció una primera versión preliminar de Cognitive Services  (microsoft.com/cognitive-services), un completo conjunto de API RESTful multiplataforma que se pueden usar para crear la nueva generación de aplicaciones basadas en la interacción natural del usuario para cualquier plataforma y en cualquier dispositivo. Cognitive Services, también conocido como "Project Oxford", se basa en el aprendizaje automático y encaja perfectamente en la filosofía de conversación como una plataforma que Microsoft pretende introducir en el ecosistema de las aplicaciones. A mayor escala, las API de Cognitive Services están disponibles a través de los servicios RESTful y actualmente ofrecen las siguientes categorías de API:

  • Vista: Los servicios de Vista ofrecen API que permiten analizar imágenes y vídeos para identificar caras y emociones, así como para detectar información práctica. Esta categoría incluye las API Computer Vision API, Face API, Emotion API y Video API.
  • Voz: Los servicios de Voz ofrecen API que facilitan la implementación de texto a voz, el reconocimiento de voz natural e incluso el reconocimiento de la persona que habla con el servicio de reconocimiento del orador. Se incluyen las API Bing Speech API, Custom Recognition Intelligent Service y Speaker Recognition API.
  • Lenguaje: Los servicios de Lenguaje están orientados a la comprensión del lenguaje natural, lo que implica la detección y corrección de errores ortográficos, la comprensión de comandos de voz y el análisis de texto complejo, incluidos sentimientos y expresiones clave. Se incluyen las API Bing Spell Check API, Language Understanding Intelligent Service, Linguistic Analysis API, Análisis de texto y Web Language Model API.
  • Información: Los servicios de Información ayudan a las aplicaciones a ampliar el conocimiento de los clientes mediante recomendaciones de productos personalizadas, eventos, ubicaciones e informes y documentos académicos. Se incluyen las API Academic Knowledge API, Entity Linking Intelligence Service API, Knowledge Exploration Service API y Recommendations API.
  • Search: Los servicios de Búsqueda se basan en Bing y permiten implementar herramientas de búsqueda eficaces en las aplicaciones. Los nombres de los servicios incluidos son realmente explícitos: Bing Autosuggest API, Bing Image Search API, Bing News Search API, Bing Video Search API y Bing Web Search API.

En este artículo, explicaré cómo combinar Face API y Emotion API para recuperar los detalles de caras y emociones de las fotos tomadas con una cámara o de un álbum del disco en una aplicación de Xamarin.Forms creada con C# y Visual Studio 2015 que se ejecute en Android, iOS o Windows 10. En la Figura 1 se muestran los resultados del tutorial del artículo. Es importante mencionar que, a pesar de usar Xamarin.Forms para este artículo, lo mismo se puede hacer con aplicaciones de Xamarin tradicionales, así como con cualquier otra plataforma compatible con REST. Supongo que cuenta con conocimientos básicos sobre la creación de una aplicación de Xamarin.Forms y que conoce los conceptos de uso compartido de código; de lo contrario, asegúrese de leer mis artículos anteriores: "Crear una experiencia de usuario multiplataforma con Xamarin.Forms" (msdn.com/magazine/mt595754) y "Compartir código de la interfaz de usuario en plataformas móviles con Xamarin.Forms" (msdn.com/magazine/dn904669).

Reconocimiento de caras y emociones en una aplicación multiplataforma con Xamarin.Forms
Figura 1 Reconocimiento de caras y emociones en una aplicación multiplataforma con Xamarin.Forms (dispositivo Android a la izquierda y Windows 10 de escritorio a la derecha)

Suscripción de las API de Cognitive Services

Para compilar aplicaciones que usen Cognitive Services, debe suscribirse al servicio de su interés. En este momento, Microsoft ofrece pruebas gratuitas que puede activar en la página de suscripciones (bit.ly/2b2rKDO), aunque los planes actuales pueden estar sujetos a cambios en el futuro. Cuando esté en la página, regístrese con una cuenta de Microsoft y haga clic en "Request new trials" (Solicitar nuevas pruebas). Se mostrará una lista de los servicios disponibles; asegúrese de seleccionar las vistas previas gratuitas de Face API y Emotion API. Llegado este punto, la página de suscripciones le mostrará la lista de servicios activos; debería ver las suscripciones a Face API y Emotion API. En la Figura 2 se muestra un ejemplo basado en mis suscripciones. Observe que, por cada servicio activo, existen dos claves secretas. Necesitará una para invocar las API. Por ahora, manténgalas ocultas. Necesitará mostrar la clave cuando cree una aplicación de Xamarin.Forms.

Activación de suscripciones para Face API y Emotion API
Figura 2 Activación de suscripciones para Face API y Emotion API

En términos generales, Cognitive Services proporciona las API RESTful, lo que significa que puede interactuar con estos servicios a través de solicitudes HTTP en cualquier plataforma y con cualquier lenguaje que sea compatible con REST. Por ejemplo, la siguiente solicitud POST de HTTP demuestra cómo enviar una imagen al servicio de reconocimiento de emociones para la detección de la emoción:

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" }

Por supuesto, debe reemplazar la clave Ocp-Apim-Subscription-Key por una de las suyas y la URL de imagen ficticia por una dirección de imagen real. A cambio, el servicio de reconocimiento de Emotion devolverá el resultado de la detección como una respuesta JSON, tal como se muestra en la Figura 3.

Figura 3 Respuesta de detección del servicio de reconocimiento de Emotion

[
  {
    "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
    }
  }
]

La respuesta de ejemplo de la Figura 3 muestra cómo el servicio de Emotion devolvió el rectángulo en el que se detectó una cara y una matriz denominada scores que contiene una lista de emociones y un valor de 0 a 1 que indica la probabilidad de que la emoción sea real. En general, el envío de solicitudes HTTP a servicios RESTful y la espera de una respuesta JSON es un enfoque común con Cognitive Services. No obstante, para los desarrolladores de .NET que trabajan con C#, Microsoft también ofrece bibliotecas de cliente portables que puede descargar de NuGet y que facilitan la interacción con los servicios en el código administrado de manera totalmente orientada a objetos. Este es el caso de Face API y Emotion API, como podrá ver en breve. No olvide consultar la documentación oficial, que contiene ejemplos basados en el enfoque de REST y en las bibliotecas de cliente cuando están disponibles (bit.ly/2b2KJrB). Ahora que está registrado en ambos servicios y que tiene sus claves, llegó el momento de crear una aplicación multiplataforma con Xamarin.Forms y Microsoft Visual Studio 2015.

Creación de una aplicación de Xamarin.Forms

Como sabe, puede crear una aplicación multiplataforma con Xamarin.Forms mediante la elección de la plantilla de proyecto Portable o Shared. Dado que explicaré cómo aprovechar las bibliotecas de cliente para las API de Cognitive Services, la aplicación de ejemplo se basa en el modelo Biblioteca de clases portable (PCL). En Visual Studio 2015, seleccione Archivo | Nuevo proyecto. Si instaló las actualizaciones más recientes desde Xamarin (xamarin.com/download), encontrará una nueva plantilla de proyecto denominada Blank Xaml App (Xamarin.Forms Portable) debajo de Visual C# en el nodo Multiplataforma del cuadro de diálogo Nuevo proyecto. Esta es una plantilla interesante que proporciona una página XAML en blanco y evita tener que crear una manualmente. En la Figura 4 se muestra la plantilla nueva.

Llame a la solución FaceEmotionRecognition y haga clic en Aceptar. Durante la generación de la solución, se le pedirá que especifique la versión de destino mínima del proyecto de la Plataforma universal de Windows (UWP). Esta se deja a su elección, pero se recomienda usar la versión más alta disponible.

Creación de una nueva aplicación de Xamarin.Forms
Figura 4 Creación de una nueva aplicación de Xamarin.Forms

Presentación de los complementos de Xamarin

La aplicación de ejemplo usará las API de Cognitive Services para reconocer los detalles de caras y las emociones de las fotos, para lo cual empleará las fotos existentes del dispositivo o tomará nuevas fotos con la cámara. Esto implica que la aplicación necesitará acceso a Internet para conectarse a los servicios y que deberá ofrecer la posibilidad de tomar y seleccionar fotos. Si bien una aplicación puede conectarse fácilmente a una red, es su responsabilidad, como desarrollador, comprobar la disponibilidad de la red. En realidad, características como la comprobación de la disponibilidad de la red y la captura de fotos pueden requerir la escritura de código específico en los proyectos de Android, iOS y Windows. Afortunadamente, Xamarin es compatible con los complementos que puede usar en Xamarin.Forms y que puede instalar en el proyecto PCL, de modo que estos harán el trabajo por usted. Un complemento es una biblioteca instalada desde NuGet que encapsula las API nativas en una implementación de código común y que se invoca en el proyecto PCL. Existe una gran cantidad de complementos. Xamarin desarrolla y admite algunos de estos complementos, mientras que otros los crea y publica la comunidad de desarrolladores. Todos los complementos son de código abierto y se enumeran en GitHub en bit.ly/29XZ3VM. En este artículo, mostraré cómo usar los complementos Connectivity y Media.

Instalación de paquetes NuGet

Cuando la solución está lista, lo primero que debe hacer es instalar los siguientes paquetes NuGet:

  • Microsoft.ProjectOxford.Face: instala la biblioteca de cliente para Face API y debe instalarse solo en el proyecto PCL.
  • Microsoft.ProjectOxford.Emotion: instala la biblioteca de cliente para Emotion API y, del mismo modo que para Face API, debe instalarse solo en el proyecto PCL.
  • Xam.Plugin.Connectivity: contiene el complemento Connectivity de Xamarin.Forms y debe instalarse en todos los proyectos de la solución.
  • Xam.Plugin.Media: contiene el complemento Media de Xamarin.Forms y, del mismo modo que para Connectivity API, debe instalarse en todos los proyectos de la solución.

Después de instalar los paquetes NuGet necesarios, asegúrese de compilar la solución antes de escribir el código. De este modo, se actualizarán todas las referencias.

Diseño de la interfaz de usuario

La interfaz de usuario de la aplicación de ejemplo consta de una sola página. Por motivos de simplicidad, usaré el archivo MainPage.xaml generado automáticamente. Esta página define dos botones, uno para tomar una foto desde la cámara y otro para cargar una foto existente; un control ActivityIndicator que mostrará un estado ocupado mientras se espera una respuesta del servicio; un control Image que mostrará la imagen seleccionada; y varias etiquetas, en los paneles StackLayout, enlazadas a datos para una clase personalizada que contendrá el resultado de las detecciones sobre la foto seleccionada. En la Figura 5 se muestra el código XAML completo de la página.

Figura 5 Interfaz de usuario de la 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>

El paso siguiente consiste en preparar un lugar para almacenar el resultado de la detección de caras y emociones.

Almacenamiento de los resultados de la detección con una clase

En lugar de rellenar manualmente las etiquetas en la interfaz de usuario con los resultados de la detección de caras y emociones, se recomienda crear una clase personalizada. Este enfoque no solo está más orientado a objetos, sino que también permite el enlace de datos de la instancia de clase a la interfaz de usuario. Dicho esto, vamos a crear una nueva clase denominada 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 propiedad tiene un nombre explícito y almacenará información procedente de la combinación de Face API y Emotion API.

Declaración de los clientes de servicios

Antes de escribir ningún otro código, es una buena idea agregar lo siguiente mediante directivas:

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

Estas simplificarán la invocación para los nombres de objeto de las API de Cognitive Services y los complementos. Face API y Emotion API proporcionan las clases Microsoft.ProjectOxford.Face.Face­ServiceClient y Microsoft.ProjectOxford.Emotion.Emotion­ServiceClient, que se conectan a Cognitive Services y devuelven información sobre los detalles de caras y emociones, respectivamente. Lo primero que debe hacer es declarar una instancia de ambas y pasar la clave secreta al constructor, como se muestra a continuación:

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");
}

Observe que debe proporcionar sus propias claves secretas. Ambas claves secretas de Face API y Emotion API se pueden encontrar en la página de suscripciones del portal de Microsoft Cognitive Services (bit.ly/2b2rKDO), como se muestra en la Figura 2.

Captura y carga de imágenes

En Xamarin.Forms, el acceso a la cámara y al sistema de archivos requeriría la escritura de código específico de la plataforma. Un enfoque más simple es usar el complemento Media para Xamarin.Forms, que permite seleccionar fotos y vídeos del disco, así como tomar fotos y grabar vídeos con la cámara desde el proyecto PCL con tan solo unas pocas líneas de código. Este complemento expone una clase denominada CrossMedia, que expone los miembros siguientes:

  • Current: devuelve una instancia singleton de la clase CrossMedia.
  • IsPickPhotoSupported e IsPickVideoSupported: propiedades bool que devuelven true si el dispositivo actual admite la selección de fotos y vídeos del disco.
  • PickPhotoAsync y PickVideoAsync: métodos que invocan la interfaz de usuario específica de la plataforma para seleccionar una foto o un vídeo local, respectivamente, y devuelven un objeto de tipo MediaFile.
  • IsCameraAvailable: propiedad bool que devuelve true si el dispositivo tiene una cámara integrada.
  • IsTakePhotoSupported e IsTakeVideoSupported: propiedades bool que devuelven true si el dispositivo actual admite la toma de fotos y la grabación de vídeos desde la cámara.
  • TakePhotoAsync y TakeVideoAsync: métodos que inician la cámara integrada para tomar una foto o grabar un vídeo, respectivamente, y devuelven un objeto de tipo MediaFile.

No olvide establecer los permisos adecuados en el manifiesto de la aplicación para acceder a la cámara. Por ejemplo, en un proyecto de UWP necesita ambos permisos para usar la cámara web y la biblioteca de imágenes, mientras que en Android necesita los permisos CAMERA, READ_EXTERNAL_STORAGE y WRITE_EXTERNAL_STORAGE. Si olvida establecer los permisos necesarios, se producirán excepciones en tiempo de ejecución. Vamos a escribir el controlador de eventos Clicked para el botón UploadPictureButton, que se muestra en la Figura 6.

Figura 6 Selección de una foto del 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;
}

El código comprueba primero si se admite la selección de fotos y muestra un mensaje de error si IsPickPhotoSupported devuelve false. PickPhoto­Async (así como PickVideoAsync) devuelve un objeto de tipo Media­File, que es una clase definida en el espacio de nombres Plugin.Media y que representa el archivo seleccionado. Debe invocar el método GetStream correspondiente para devolver una secuencia que se pueda usar como el origen del control Image a través del método FromStream correspondiente. Tomar una foto con la cámara también es muy fácil, como se muestra en la Figura 7.

Figura 7 Toma de una foto con la cámara

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;
}

Aquí, el foco de interés es que TakePhotoAsync toma un parámetro de tipo StoreCameraMediaOptions, un objeto que permite especificar dónde y cómo guardar una foto. Puede establecer la propiedad SaveToAlbum en true si quiere que la foto se guarde en el álbum de cámara local, o bien establecer la propiedad Directory si prefiere guardarla en otra carpeta. Como puede ver, con muy poco esfuerzo y algunas líneas de código, su aplicación puede aprovechar fácilmente una funcionalidad importante de todas las plataformas compatibles.

Detección de emociones e implementación del reconocimiento de caras

Llegó el momento de implementar el reconocimiento de caras y emociones. Dado que este es un artículo introductorio, me centraré en la simplicidad. Mostraré cómo implementar la detección en una sola cara de una foto y describiré los objetos y miembros más importantes de las API. También le proporcionaré sugerencias para implementar detecciones más detalladas cuando sea necesario. De acuerdo con estas suposiciones, vamos a escribir un método asincrónico que realice detecciones. La primera parte está relacionada con la detección de emociones y tiene este aspecto:

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();

El método recibe el objeto MediaFile generado mediante la selección o toma de una foto. La detección de emociones en caras de una foto es directa, ya que solo debe invocar el método RecognizeAsync desde la instancia de la clase EmotionServiceClient. Este método puede recibir una secuencia o una URL como un argumento. En este caso, obtiene una secuencia de un objeto MediaFile. RecognizeAsync devuelve una matriz de objetos Emotion. Cada objeto Emotion de la matriz almacena las emociones detectadas en una sola cara de una foto. Suponiendo que la foto seleccionada solo tiene una cara, el código recupera el primer elemento de la matriz. El tipo Emotion expone una propiedad denominada Scores, que contiene una lista de ocho nombres de emoción y su valor aproximado. Más específicamente, obtiene IEnumerable<string, float>. Al invocar su método ToRankedList, puede obtener una lista ordenada de emociones detectadas. Las API no pueden detectar una sola emoción de forma precisa. En su lugar, detectan varias emociones posibles. El mayor valor devuelto se acerca a la emoción real de la cara, pero quedan otros valores que se pueden comprobar.  El valor más alto de la lista representa la emoción con el mayor nivel de probabilidad estimada, que posiblemente es la emoción real de una cara. Para comprenderlo más fácilmente, considere la posibilidad de seguir la lista clasificada de emociones recuperada con la ayuda de las sugerencias de datos del depurador, que se basa en la foto de muestra de la 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]}

Como puede observar, el valor de Happiness es 1, el más alto de la lista y la probabilidad estimada de la emoción real. El siguiente paso es detectar los atributos de cara. La clase FaceServiceClient expone el método DetectAsync, que es extremadamente eficaz. No solo es capaz de recuperar los atributos de cara, tales como el género, la edad y la sonrisa, sino que puede reconocer personas, devolver el rectángulo de la cara (el área de la foto donde se detectó la cara) y 27 puntos de referencia de la cara que permiten que una aplicación identifique información, como la posición de la nariz, la boca, las orejas y los ojos en la foto. DetectAsync presenta la signatura siguiente:

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

En su invocación más básica, DetectAsync requiere una secuencia que apunte a una foto o una URL y devuelve un rectángulo de cara, mientras que los parámetros returnFaceId y returnFaceLandmarks opcionales, respectivamente, permiten identificar una persona y devuelven puntos de referencia de su cara. Face API permite crear grupos de personas y asignar un identificador a cada una de ellas para facilitar su reconocimiento. En cambio, los puntos de referencia de cara resultan útiles para identificar las características de un rostro y estarán disponibles a través de la propiedad FaceLandmarks del objeto Face. Tanto la identificación como los puntos de referencia están fuera del ámbito de este artículo, pero puede encontrar más información sobre estos temas en bit.ly/2adPvoP y bit.ly/2ai9WjV, respectivamente. De manera parecida, no mostraré cómo usar los puntos de referencia de cara, pero están almacenados en la propiedad FaceLandmarks del objeto Face. En el escenario de muestra actual, el objetivo es recuperar los atributos de cara. Lo primero que necesita es una matriz de la enumeración FaceAttributeType, que define la lista de atributos que quiere 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
  };

A continuación, invoque DetectAsync y pase la secuencia de imagen y la lista de atributos de cara. Los argumentos returnFaceId y returnFaceLandmarks son false porque la información relacionada es innecesaria en este momento. La invocación de métodos tiene el aspecto siguiente:

// 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;

DetectAsync devuelve una matriz de objetos Face, cada uno de los cuales representan una cara en la foto. El código toma el primer elemento de la matriz, que representa una sola cara, y recupera sus atributos de cara. Observe cómo la última línea usa el operador condicional null (?), introducido con C# 6, que devuelve null si el primer elemento de la matriz también es null, en lugar de lanzar una excepción NullReferenceException. Puede encontrar más información sobre este operador en bit.ly/2bc8VZ3. Ahora que tiene la información sobre caras y emociones, puede crear una instancia de la clase FaceEmotionDetection y rellenar sus propiedades, como se demuestra en el código siguiente:

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;

Algunas consideraciones llegado este punto:

  • El valor más alto de la lista de emociones se obtiene al invocar FirstOrDefault sobre el resultado de la invocación del método Scores.ToRankedList, que devuelve IEnumerable<string, float>.
  • El valor que aquí devuelve FirstOrDefault es un objeto de tipo KeyValuePair<string, float> y la clave de tipo cadena almacena el nombre de emoción en texto en lenguaje natural que se mostrará en la interfaz de usuario.
  • Glasses es una enumeración que especifica si la cara detectada lleva gafas y de qué tipo. El código invoca ToString con fines de simplicidad, pero sin duda podría implementar un convertidor para los formatos de cadena diferentes.

El bloque final del cuerpo del método devuelve la instancia de la clase FaceEmotionDetection e implementa el control de excepciones:

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

Lo último que debe hacer es invocar el método DetectFaceAndEmotionAsync personalizado. Puede hacerlo dentro de ambos controladores de eventos Clicked, justo antes de establecer en false las propiedades IsRunning e IsVisible del control ActivityIndicator:

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

La propiedad BindingContext de la página recibe una instancia de la clase FaceEmotionDetection, mientras que el origen de datos y los controles secundarios enlazados a datos mostrarán automáticamente la información relacionada. Con patrones como Model-View-ViewModel, encapsularía el resultado con una clase ViewModel. Después de mucho trabajo, está listo para probar la aplicación.

Prueba de la aplicación

Seleccione la plataforma deseada y presione F5. Si usa los emuladores de Microsoft, puede aprovechar las herramientas de emulación para seleccionar una cámara web física para tomar fotos y puede simular una tarjeta SD para cargar archivos. En la Figura 1 se muestra el resultado de la detección en una foto mía, en un dispositivo Android y en Windows 10 ejecutándose en modo de escritorio.

Face API y Emotion API realizaron un trabajo increíble, ya que los valores devueltos se acercan mucho a la verdad, aunque son aproximados. Vale la pena mencionar que la clase FaceEmotionDetection tiene algunas propiedades de tipo doble, como Smile, Beard y Moustache. Devuelven valores numéricos, que podrían no tener mucho sentido para el usuario final en una aplicación real. Por tanto, si quiere convertir esos valores numéricos en cadenas en texto natural, debería considerar la posibilidad de implementar convertidores de valores y la interfaz IValueConverter (bit.ly/2bZn01J).

Implementación de una comprobación de conectividad de red

Una aplicación bien diseñada que necesite acceder a recursos de Internet siempre debería comprobar primero la disponibilidad de la conexión. Igual que para acceder a la cámara y al sistema de archivos, la comprobación de la disponibilidad de la conexión en Xamarin.Forms debería requerir código específico de la plataforma. Afortunadamente, el complemento Connectivity se incluye para facilitar la operación, con un método para realizar esta comprobación directamente desde el proyecto PCL. El complemento ofrece una clase denominada CrossConnectivity con la propiedad Current correspondiente, que representa una instancia de singleton de la clase. Expone una propiedad bool denominada IsConnected que simplemente devuelve true si existe una conexión disponible. Para comprobar la disponibilidad de la red en la aplicación de muestra, solo tiene que colocar el código siguiente después de la declaración del 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;
  }

La clase también expone los siguientes miembros de interés:

  • ConnectivityChanged: evento que se genera cuando cambia el estado de la conexión. Puede suscribir este evento y obtener información sobre el estado de la conectividad a través de un objeto de tipo ConnectivityChangedEventArgs.
  • BandWidths: propiedad que devuelve una lista de anchos de banda disponibles para la plataforma actual.

Puede encontrar información adicional sobre el complemento Connectivity en bit.ly/2bbU7wu.

Resumen

Microsoft Cognitive Services proporciona servicios RESTful y API avanzadas que se basan en el aprendizaje automático que permite crear la nueva generación de aplicaciones. Al combinar la eficacia de estos servicios con Xamarin, podrá introducir la interacción natural del usuario en sus aplicaciones multiplataforma para Android, iOS y Windows, para ofrecer a los clientes una experiencia increíble.


Alessandro Del Sole ha sido MVP de Microsoft desde el año 2008. Alessandro ha sido proclamado MVP del año en cinco ocasiones y es el autor de numerosos libros, eBooks, vídeos didácticos y artículos sobre desarrollo .NET con Visual Studio. Del Sole trabaja como experto en desarrollo de soluciones en Brain-Sys (brain-sys.it); su trabajo se centra principalmente en las áreas de desarrollo en .NET, formación y consultoría. Puede seguirlo en Twitter: @progalex.

Gracias a los siguientes expertos técnicos por revisar este artículo: James McCaffrey y James Montemagno