Chcete si vytvořit aplikaci, která bude vyhledávat obličeje na fotografii, rozpoznávat emoce, barvu vlasů, věk, pohlaví a zda daná osoba nosí brýle? Tento návod vás provede celým postupem tvorby algoritmu na rozpoznávání obličeje pomocí Azure.
Obsah
Požadavky
- Visual Studio
- Účet na Azure portálu
- Základy programování
- (celý tento program je případně dostupný na GitHubu)
Přidání služby Face
V následujících několika krocích si ukážeme, jak si přidat službu Face i na váš účet.
Přihlásíte se na stránce portal.azure.com a kliknete na tlačítko Create a resource.
Vyhledáme si službu Face.
Doplníme potřebné údaje.
Instalace Visual Studia
Pokud ještě nemáte Visual Studio nainstalované, navštivte náš článek, ve kterém je celý postup pospaný.
Programovací část
Připravení složek a tříd
Spustíme si Visual Studio a založíme si složku Models, do které přidáme tři třídy, ve kterých budeme později pracovat.
Struktura:
- Models
- FaceDetails
- FaceDetectionHelper
- ImageHelper
A jak si vlastně vytvořit složku a přidat do ní třídy?
- Krok 1: Pravé kliknutí do panelu s názvem průzkumník řešení -> přidat -> nová složka
- Krok 2: Pravé kliknutí na složku MODELS -> přidat -> nový předmět (opakujeme 3x)
NuGet
NuGet je balíček, který obsahuje opakovaný, nebo hodně používaný kód. Má za úkol zjednodušit programovací proces. Pokud chcete zjistit více, klikněte na následující odkaz dokumentace.
- Krok 1: Pravé kliknutí na náš project -> Spravovat balíček NuGet
- Krok 2: Vyhledáme si balíček Microsoft.Azure.CognitiveServices.Vision.Face a nainstalujeme si ho
Připojení API k naší aplikaci
Přejdeme do classy FaceDetectionHelper, kde si vytvoříme proměnné _endpoint a _secret typu string, který je určen pro textové řetězce. Do těchto proměnných uložíme hodnoty z naší založené služby.
private string _endpoint = ""
; <- vložte zde endpoint
private string _secret = ""
; <- vložte zde klíč k naší službě
Přidání ovládacích komponentů
Popis kódu: Rozdělíme si okno (grid) na dvě části, konkrétně pomocí ColumnDefinition.
Přidáme si Button, neboli tlačítko, přes které budeme do našeho program nahrávat obrázek nebo fotku na rozpoznání obličeje. Dále Image, do kterého budeme vykreslovat zvolený obrázek. Jako poslední ListBox, do kterého budeme vykreslovat rozpoznané obličeje + informace.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions>
<Image Stretch="Uniform" x:Name="FaceImage" HorizontalAlignment="Left" Grid.Column="0" Grid.Row="0"/>
<Button x:Name="BtnUpload" VerticalAlignment="Bottom" Content="Select image file" Margin="20,0,20,5" Height="20" Click="BtnUpload_Click" />
<ListBox x:Name="FaceDetails" Grid.Column="1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<DockPanel HorizontalAlignment="Stretch" >
<Image Margin="3" Source="{Binding Image}" Width="100" Height="100"/>
<StackPanel Orientation="Vertical">
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Age: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Age}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Gender: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Gender}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Glasses: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Glasses}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<Label Content="Dominant hair colour: "/>
<TextBlock Margin="3" Text="{Binding MainHairColor}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Emotion: "/>
<StackPanel Orientation="Vertical">
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Anger: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Emotion.Anger}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Contempt: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Emotion.Contempt}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Disgust: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Emotion.Disgust}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Fear: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Emotion.Fear}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Happiness: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Emotion.Happiness}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Neutral: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Emotion.Neutral}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch" >
<Label Content="Sadness: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Emotion.Sadness}"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<Label Content="Surprise: "/>
<TextBlock Margin="3" Text="{Binding Attributes.Emotion.Surprise}"/>
</DockPanel>
</StackPanel>
</DockPanel>
</StackPanel>
</DockPanel>
<Separator />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
MainWindow.xaml.cs
Vytvoříme si metodu na nahrání obrázku, která se vyvolá v moment kliknutí na tlačítko. V MainWindow.xaml.cs si klikneme 2x na tlačítko, nebo přidáme Event handler – CLICK.
Dále si do ní vložíme jednoduchý OpenFileDialog, který nám otevře klasické okno průzkumníka pro zvolení našeho obrázku.
Popis kódu: Až se obrázek nahraje, je na čase rozpoznat obličeje. S tím nám pomůže metoda DetectFace v třídě FaceDetectionHelper. Poté všechny obličeje projedeme foreachem, který nám vykreslí na hlavním obrázku bílé kruhy okolo obličejů. Dále si všechny obličeje ořízne a vloží je do listboxu, kde má každý obličej vlastní řádek s oříznutým obličejem a vlastnostmi.
facesDetails.Add(new Models.FaceDetails()
{
Image = croppedFace,
Attributes = faceDetails
});
private async void BtnUpload_Click(object sender, RoutedEventArgs e)
{
var openFileDialog = new OpenFileDialog()
{
Filter = "Image files (*.png;*.jpeg)|*.png;*.jpeg|All files (*.*)|*.*"
};
if (openFileDialog.ShowDialog() == true)
{
var filePath = openFileDialog.FileName;
var imageBitmap = new BitmapImage(new Uri(filePath));
FaceImage.Source = imageBitmap;
var drwVisual = new DrawingVisual();
var drwContext = drwVisual.RenderOpen();
drwContext.DrawImage(imageBitmap, new Rect(0, 0, imageBitmap.Width, imageBitmap.Height));
var dpi = imageBitmap.DpiX;
var resizeFactor = 96 / dpi;
var faceHelper = new FaceDetectionHelper(filePath, resizeFactor);
var faces = await faceHelper.DetectFaces();
var facesDetails = new List<FaceDetails>();
foreach (var face in faces)
{
var rectangleDetails = face.FaceRectangle;
faceHelper.DrawFaceCircle(rectangleDetails, drwContext);
var croppedFace = faceHelper.GetCroppedImage(rectangleDetails);
var faceDetails = face.FaceAttributes;
facesDetails.Add(new Models.FaceDetails()
{
Image = croppedFace,
Attributes = faceDetails
});
}
FaceDetails.ItemsSource = facesDetails;
drwContext.Close();
var renderToImageCtrl = new RenderTargetBitmap((int)(imageBitmap.PixelWidth * resizeFactor), (int)(imageBitmap.PixelHeight * resizeFactor), 96, 96, PixelFormats.Pbgra32);
renderToImageCtrl.Render(drwVisual);
FaceImage.Source = renderToImageCtrl;
}
}
FaceDetectionHelper.cs
Popis kódu: Zde máme metodu, která nám určuje, jaká data chceme získat z API, konkrétně jaké vlastnosti obličeje chceme vypsat. Dále si tu ještě pomáháme se změnou velikosti jednotlivých obrázků a pozicování kruhů kolem tváří.
private readonly string _imageSrc;
private readonly double _resizeFactor;
public FaceDetectionHelper(string imageFilePath, double resizeFactor)
{
_imageSrc = imageFilePath;
_resizeFactor = resizeFactor;
}
private IFaceClient GetClient()
{
return new FaceClient(new ApiKeyServiceClientCredentials(this._secret)) { Endpoint = this._endpoint, };
}
public async Task<IEnumerable<DetectedFace>> DetectFaces()
{
var client = GetClient();
var requiredFaceAttributes = new FaceAttributeType[] {
FaceAttributeType.Age,
FaceAttributeType.Gender,
FaceAttributeType.Smile,
FaceAttributeType.FacialHair,
FaceAttributeType.Hair,
FaceAttributeType.HeadPose,
FaceAttributeType.Glasses,
FaceAttributeType.Emotion,
};
try
{
using (var imgStream = File.OpenRead(_imageSrc))
{
var faces = await client.Face.DetectWithStreamAsync(imgStream, returnFaceAttributes: requiredFaceAttributes);
return faces;
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw e;
}
}
public Rect GetFaceRectangle(FaceRectangle faceRectangle)
{
var rectangle = new Rect(faceRectangle.Left * _resizeFactor, faceRectangle.Top * _resizeFactor, faceRectangle.Width * _resizeFactor, faceRectangle.Height * _resizeFactor);
return rectangle;
}
public Point GetCenter(double x, double y, double radius)
{
var center = new Point(x + radius, y + radius);
return center;
}
public void DrawFaceCircle(FaceRectangle faceRectangle, DrawingContext drawingContext)
{
var diameter = faceRectangle.Width;
var radius = diameter / 2;
var xPosition = faceRectangle.Left * _resizeFactor;
var yPosition = faceRectangle.Top * _resizeFactor;
var center = GetCenter(xPosition, yPosition, radius);
drawingContext.DrawEllipse(Brushes.Transparent,
new Pen(Brushes.White, 7),
center,
diameter * _resizeFactor,
diameter * _resizeFactor);
}
public BitmapImage GetCroppedImage(FaceRectangle faceRectangle)
{
var xCenter = (int)(faceRectangle.Left * _resizeFactor);
var yCenter = (int)(faceRectangle.Top * _resizeFactor);
var center = new System.Drawing.Point(xCenter, yCenter);
var diameter = (int)(faceRectangle.Width * _resizeFactor);
var size = new System.Drawing.Size(diameter, diameter);
var rectangleToCrop = new System.Drawing.Rectangle(center, size );
var image = System.Drawing.Image.FromFile(_imageSrc);
var origBitmap = new System.Drawing.Bitmap(_imageSrc);
var croppedImage = ImageHelper.CropAtRect(origBitmap, rectangleToCrop);
var imageSource = ImageHelper.BitmapToImageSource(croppedImage);
return imageSource;
}
FaceDetails.cs
Popis kódu: Tuto třídu máme vytvořenou pouze pro získávání určitých dat z API. Pro to využíváme tzv. getter, setter, neboli vlastnosti.
public FaceAttributes Attributes { get; set; }
public BitmapImage Image { get; set; }
public string MainHairColor => this.Attributes.Hair.HairColor.FirstOrDefault().Color.ToString();
ImageHelper.cs
Popis kódu: Zde máme několik metod na ořezávání, načítání a pozicování obrázků.
public static Image CropImage(Image img, Rectangle cropArea)
{
Bitmap bmpImage = new Bitmap(img);
return bmpImage.Clone(cropArea, bmpImage.PixelFormat);
}
public static Bitmap CropAtRect(this Bitmap b, Rectangle r)
{
Bitmap nb = new Bitmap(r.Width, r.Height);
Graphics g = Graphics.FromImage(nb);
g.DrawImage(b, -r.X, -r.Y);
return nb;
}
public static BitmapImage BitmapToImageSource(Bitmap bitmap)
{
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
memory.Position = 0;
BitmapImage bitmapimage = new BitmapImage();
bitmapimage.BeginInit();
bitmapimage.StreamSource = memory;
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
bitmapimage.EndInit();
return bitmapimage;
}
}
Závěr
Doufáme, že se vám bude aplikace líbit. Rozpoznávání obličeje pomocí Azure je jen jednou z možností jak lze umělou inteligenci využívat. Kdyby se vám aplikace líbila, ale netroufli byste si na programování, nezoufejte, protože tento program je dostupný na našem GitHubu.