Gesichtserkennung mit der Cognitive Services Face API

Microsoft Cognitive Services ist eine Sammlung an REST APIs und SDKs womit Entwickler - auch ohne Machine Learning-Experte zu sein - intelligente Funktionen, wie Bild-, Sprach- und Texterkennung, in ihre Anwendungen integrieren können.

In diesem Blogartikel werden wir lernen, wie man Schritt für Schritt eine eigene UWP App bauen kann, welche die Face API für Gesichtserkennung nutzt. Um das Tutorial nachzuvollziehen, könnt ihr auch den Code von GitHub herunterladen.

Als erstes öffnen wir Visual Studio 2015 und erstellen ein neues Projekt. Dafür wählen wir als Vorlage eine leere Universelle Windows App (C#). Ich habe mein Projekt „FaceEmotion“ genannt und auf Enter gedrückt.

Fangen wir nun mit dem Frontend an. Dafür bauen wir eine einfache und schlichte UI, indem wir ein RelativePanel mit zwei Grids namens LogoStackPanel und ImagePanel erstellen.

 <Page
  x:Class="FaceEmotion.MainPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:FaceEmotion"
  xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d">

  <RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

  <Grid Height="90" x:Name="LogoStackPanel" Background="#0078D7"
     RelativePanel.AlignRightWithPanel="True"
     RelativePanel.AlignLeftWithPanel="True">

  <StackPanel Orientation="Vertical"
     HorizontalAlignment="Left"
     Margin="20,4,0,0">
  <Image Height="24"
     Margin="2,0,0,0"
     HorizontalAlignment="Left"
     Source="Images/microsoftLogo.png" />
  <TextBlock Text="Cognitive Serivces"
     FontSize="26"
     Foreground="WhiteSmoke"></TextBlock>
  <TextBlock Text="Face and Emotion Detection"
     FontSize="18"
     Foreground="WhiteSmoke"></TextBlock>
  </StackPanel>
  </Grid>

  <Grid x:Name="ImagePanel"
     RelativePanel.AlignLeftWith="LogoStackPanel"
     RelativePanel.Below="LogoStackPanel">

  <Image x:Name="FacePhoto"
     Width="600"
     Height="800"
     Margin="20,10,40,44"
     Stretch="Uniform"
     VerticalAlignment="Top"
     HorizontalAlignment="Left" />

  <Canvas Name="FacesCanvas"
     Margin="20,10,40,44" />

  <Button x:Name="BrowseButton"
     Margin="20,5,0,10"
     Height="32"
     VerticalAlignment="Bottom"
     Content="Browse..."
     Click="BrowseButton_Click" />
  </Grid>

  </RelativePanel>
</Page>

Das erste Grid ist blau und beinhaltet das Logo, den Titel und den Untertitel der Anwendung.
Als Logo habe ich ein Bild mit dem Microsoft Logo genutzt, das ich in einem neuen Ordner "Images" hinzugefügt habe.
Das zweite Grid  besteht aus einem Knopf, womit die Nutzer später ein Bild hochladen können, dem Bild selbst und einer Canvas, damit wir dann die erkannten Gesichter darauf markieren können.

Als nächstes installieren wir das NuGet Package namens Microsoft.ProjectOxford.Face.

Die Face API erlaubt uns bis zu 64 Gesichter einschließlich bestimmter Merkmale in Bilder zu erkennen.
Die erkannten Gesichter werden als Rechtecke geliefert, die die Position der Gesichter innerhalb des Bildes in Pixelkoordinaten angibt.
Wir können außerdem Informationen über die Haltung, das Geschlecht und das Alter des erkannten Gesichts entnehmen. Natürlich handelt es sich hierbei um Schätzungen aufgrund von Gesichtsmerkmalen und nicht um exakte Daten.

Für unsere App möchten wir die Position des Gesichts, Id, Alter und Geschlecht der erkannten Person wissen. Deswegen erstellen wir eine neue Klasse MyFaceModel innerhalb von einem neuen Ordner namens Models.

In MainPage.xaml.cs referenzieren wir Microsoft.ProjectOxford.Face und wir erstellen einen FaceServiceClient.

 namespace FaceEmotion.ViewModels
{
  class MyFaceModel
  {
    public object Age { get; internal set; }
    public string FaceId { get; internal set; }
    public FaceRectangle FaceRect { get; internal set; }
    public object Gender { get; internal set; }
  }
}
 private readonly FaceServiceClient faceServiceClient = new FaceServiceClient("replacewithyourkey");

Wir implementieren die Methode BrowseButton_Click die auf Knopfdruck reagiert.

 private async void BrowseButton_Click(object sender, RoutedEventArgs e)
{
  //Upload picture
  FileOpenPicker openPicker = new FileOpenPicker();
  openPicker.ViewMode = PickerViewMode.Thumbnail;
  openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
  openPicker.FileTypeFilter.Add(".jpg");
  openPicker.FileTypeFilter.Add(".jpeg");
  openPicker.FileTypeFilter.Add(".png");

  StorageFile file = await openPicker.PickSingleFileAsync();
  var bitmapImage = new BitmapImage();

  var stream = await file.OpenAsync(FileAccessMode.Read);
  bitmapImage.SetSource(stream);

  if (file != null)
  {
    FacePhoto.Source = bitmapImage;
    scale = FacePhoto.Width / bitmapImage.PixelWidth;
  }
  else
  {
    Debug.WriteLine("Operation cancelled.");
  }

  //Remove any existing rectangles from previous events
  FacesCanvas.Children.Clear();

  //DetectFaces
  var s = await file.OpenAsync(FileAccessMode.Read);
  List<MyFaceModel> faces = await DetectFaces(s.AsStream());
  var t = await file.OpenAsync(FileAccessMode.Read);
  DrawFaces(faces);
}

FileOpenPicker ermöglicht dem Benutzer eine Datei auszuwählen. Wir lesen dann die ausgewählte Datei und legen sie als Quellbild fest.

 //Remove any existing rectangles from previous events
FacesCanvas.Children.Clear();

//DetectFaces
var s = await file.OpenAsync(FileAccessMode.Read);
List<MyFaceModel> faces = await DetectFaces(s.AsStream());

DrawFaces(faces);

Wir erstellen nun die Methode DetectFaces, wo wir FaceServiceClient.DetectAsync aufrufen, um die Gesichtserkennung zu starten.
Mögliche Ausnahmen behandeln wir mit einer Try-Catch Anweisung.

 private async Task<List<MyFaceModel>> DetectFaces(Stream imageStream)
{
  var collection = new List<MyFaceModel>();
  var attributes = new List<FaceAttributeType>();
  attributes.Add(FaceAttributeType.Gender);
  attributes.Add(FaceAttributeType.Age);

  try
  {
    using (var s = imageStream)
    {
      var faces = await faceServiceClient.DetectAsync(s, true, true, attributes);
      foreach (var face in faces)
      {
       collection.Add(new MyFaceModel()
       {
       FaceId = face.FaceId.ToString(),
       FaceRect = face.FaceRectangle,
       Gender = face.FaceAttributes.Gender,
       Age = face.FaceAttributes.Age
       });
      }
    }
  }
  catch (InvalidOperationException e)
  {
    Debug.WriteLine("Error Message: " + e.Message);
  }
  return collection;
}

In der Methode DrawFaces prüfen wir, ob Gesichter erkannt wurden und zeichnen dann die Rechtecke auf dem Bild.

 private void DrawFaces(List<MyFaceModel> faces)
{
  //Check if there are any faces in this image
  if (faces != null)
  {
  //For each detected face
   for (int i = 0; i < faces.Count; i++)
   {
    Rectangle faceBoundingBox = new Rectangle();

    //Set bounding box stroke properties
    faceBoundingBox.StrokeThickness = 3;

    //Highlight the first face in the set
    faceBoundingBox.Stroke = (i == 0 ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.DeepSkyBlue));
    if (scale == 0)
    {
     scale = 1;
    }
    faceBoundingBox.Margin = new Thickness(faces[i].FaceRect.Left * scale, faces[i].FaceRect.Top * scale, 
                                           faces[i].FaceRect.Height * scale, faces[i].FaceRect.Width * scale);
    faceBoundingBox.Width = faces[i].FaceRect.Width * scale;
    faceBoundingBox.Height = faces[i].FaceRect.Height * scale;
    TextBlock age = new TextBlock();
    var predictedAge = Convert.ToInt32(faces[i].Age);

    // Acquire keys and sort them.
    var list = detectedEmotion.Keys.ToList();
    list.Sort();

    age.Text = faces[i].Gender + ", " + predictedAge;
    age.Margin = new Thickness(faces[i].FaceRect.Left * scale, (faces[i].FaceRect.Top * scale) - 20, 
                               faces[i].FaceRect.Height * scale, faces[i].FaceRect.Width * scale);
    age.Foreground = (i == 0 ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.DeepSkyBlue));
    //Add grid to canvas containing all face UI objects
    FacesCanvas.Children.Add(faceBoundingBox);
    FacesCanvas.Children.Add(age);
   }
  }
  else
  {
   Debug.WriteLine("No faces identified");
  }
}

Somit haben wir nun unsere App fertig und sind bereit, automatisch Gesichter in Bildern zu erkennen. Als nächsten Schritt können wir die Anwendung mit der Emotion Api erweitern, um auch die Gefühle der identifizierten Menschen zu ermitteln.

FaceEmotion10

Anwendungsbeispiele der Face API:
How-Old.net Project Murphy Celebslike.me Mimiker Alarm

Mehr Informationen dazu: Online Kurs "Einführung in Cognitive Services"