자습서: 이미지에서 얼굴을 감지하고 포착하는 WPF 앱 만들기Tutorial: Create a WPF app to detect and frame faces in an image

이 자습서에서는 .NET 클라이언트 라이브러리를 통해 Face 서비스를 사용하는 WPF(Windows Presentation Framework) 응용 프로그램을 만듭니다.In this tutorial, you create a Windows Presentation Framework (WPF) application that uses the Face service through its .NET client library. 앱은 이미지에서 얼굴을 감지하고 각 얼굴 주위에 프레임을 그린 다음, 상태 표시줄에 얼굴에 대한 설명을 표시합니다.The app detects faces in an image, draws a frame around each face, and displays a description of the face on the status bar. 전체 샘플 코드는 Windows의 이미지에서 얼굴 감지 및 포착의 GitHub에서 사용할 수 있습니다.The complete sample code is available on GitHub at Detect and frame faces in an image on Windows.

사각형으로 포착되어 감지된 얼굴을 보여주는 스크린샷

이 자습서에서는 다음을 수행하는 방법에 대해 설명합니다.This tutorial shows you how to:

  • WPF 응용 프로그램 만들기Create a WPF application
  • Face 서비스 클라이언트 라이브러리 설치Install the Face service client library
  • 클라이언트 라이브러리를 사용하여 이미지에서 얼굴 감지Use the client library to detect faces in an image
  • 감지된 각 얼굴 주위에 프레임 그리기Draw a frame around each detected face
  • 상태 표시줄에 얼굴에 대한 설명 표시Display a description of the face on the status bar

필수 조건Prerequisites

Visual Studio 솔루션 만들기Create the Visual Studio solution

다음 단계에 따라 Windows WPF 응용 프로그램 프로젝트를 만듭니다.Follow these steps to create a Windows WPF application project.

  1. Visual Studio를 열고 파일 메뉴에서 새로 만들기, 프로젝트를 차례로 클릭합니다.Open Visual Studio and from the File menu, click New, then Project.
    • Visual Studio 2017에서 설치됨, 다른 언어를 차례로 확장합니다.In Visual Studio 2017, expand Installed, then Other Languages. Visual C#, WPF 앱(.NET Framework) 을 차례로 선택합니다.Select Visual C#, then WPF App (.NET Framework).
    • Visual Studio 2015에서 설치됨, 템플릿을 차례로 확장합니다.In Visual Studio 2015, expand Installed, then Templates. Visual C#, WPF 응용 프로그램을 차례로 선택합니다.Select Visual C#, then WPF Application.
  2. 응용 프로그램 이름을 FaceTutorial로 지정하고 확인을 클릭합니다.Name the application FaceTutorial, then click OK.

Face 서비스 클라이언트 라이브러리 설치Install the Face service client library

클라이언트 라이브러리를 설치하려면 다음 지침을 따르세요.Follow these instructions to install the client library.

  1. 도구 메뉴에서 NuGet 패키지 관리자, 패키지 관리자 콘솔을 차례로 선택합니다.From the Tools menu, select NuGet Package Manager, then Package Manager Console.
  2. 패키지 관리자 콘솔에서 다음을 붙여 넣은 다음, Enter를 누릅니다.In the Package Manager Console, paste the following, then press Enter.

    Install-Package Microsoft.Azure.CognitiveServices.Vision.Face -Version 2.0.0-preview

초기 코드를 추가합니다.Add the initial code

MainWindow.xamlMainWindow.xaml

MainWindow.xaml을 열고(팁: 위/아래 화살표 아이콘을 사용하여 창 교환) 콘텐츠를 다음 코드로 바꿉니다.Open MainWindow.xaml (tip: swap panes using the up/down arrow icon) and replace the contents with the following code. 이 xaml 코드는 UI 창을 만드는 데 사용됩니다.This xaml code is used to create the UI window. 이벤트 처리기, FacePhoto_MouseMoveBrowseButton_Click입니다.Note the event handlers, FacePhoto_MouseMove and BrowseButton_Click.

<Window x:Class="FaceTutorial.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="700" Width="960">
    <Grid x:Name="BackPanel">
        <Image x:Name="FacePhoto" Stretch="Uniform" Margin="0,0,0,50" MouseMove="FacePhoto_MouseMove" />
        <DockPanel DockPanel.Dock="Bottom">
            <Button x:Name="BrowseButton" Width="72" Height="20" VerticalAlignment="Bottom" HorizontalAlignment="Left"
                    Content="Browse..."
                    Click="BrowseButton_Click" />
            <StatusBar VerticalAlignment="Bottom">
                <StatusBarItem>
                    <TextBlock Name="faceDescriptionStatusBar" />
                </StatusBarItem>
            </StatusBar>
        </DockPanel>
    </Grid>
</Window>

MainWindow.xaml.csMainWindow.xaml.cs

MainWindow.xaml을 확장한 다음, MainWindow.xaml.cs를 열고 콘텐츠를 다음 코드로 바꿉니다.Expand MainWindow.xaml, then open MainWindow.xaml.cs, and replace the contents with the following code. 물결 모양의 빨간색 밑줄은 무시하세요. 첫 번째 빌드 후 사라집니다.Ignore the squiggly red underlines; they'll disappear after the first build.

처음 두 줄은 클라이언트 라이브러리 네임스페이스를 가져옵니다.The first two lines import the client library namespaces. 그 다음, MainWindow 생성자에서 Azure 영역을 설정되는 동안 FaceClient가 생성되고 구독 키가 전달됩니다.Next, the FaceClient is created, passing in the subscription key, while the Azure region is set in the MainWindow constructor. 두 메서드 BrowseButton_ClickFacePhoto_MouseMoveMainWindow.xaml에 선언된 이벤트 처리기에 해당합니다.The two methods, BrowseButton_Click and FacePhoto_MouseMove, correspond to the event handlers declared in MainWindow.xaml.

BrowseButton_ClickOpenFileDialog를 만들어, 사용자가 jpg 이미지를 선택할 수 있도록 합니다.BrowseButton_Click creates an OpenFileDialog, which allows the user to select a jpg image. 이미지는 주 창에서 읽히고 표시됩니다.The image is read and displayed in the main window. BrowseButton_Click의 나머지 코드와 FacePhoto_MouseMove의 코드는 이후 단계에 삽입됩니다.The remaining code for BrowseButton_Click and the code for FacePhoto_MouseMove are inserted in subsequent steps.

using Microsoft.Azure.CognitiveServices.Vision.Face;
using Microsoft.Azure.CognitiveServices.Vision.Face.Models;

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace FaceTutorial
{
    public partial class MainWindow : Window
    {
        // Replace <SubscriptionKey> with your valid subscription key.
        // For example, subscriptionKey = "0123456789abcdef0123456789ABCDEF"
        private const string subscriptionKey = "<SubscriptionKey>";

        // Replace or verify the region.
        //
        // You must use the same region as you used to obtain your subscription
        // keys. For example, if you obtained your subscription keys from the
        // westus region, replace "westcentralus" with "westus".
        //
        // NOTE: Free trial subscription keys are generated in the westcentralus
        // region, so if you are using a free trial subscription key, you should
        // not need to change this region.
        private const string baseUri =
            "https://westcentralus.api.cognitive.microsoft.com/face/v1.0";

        private readonly IFaceClient faceClient = new FaceClient(
            new ApiKeyServiceClientCredentials(subscriptionKey),
            new System.Net.Http.DelegatingHandler[] { });

        IList<DetectedFace> faceList;   // The list of detected faces.
        String[] faceDescriptions;      // The list of descriptions for the detected faces.
        double resizeFactor;            // The resize factor for the displayed image.

        public MainWindow()
        {
            InitializeComponent();

            if (Uri.IsWellFormedUriString(baseUri, UriKind.Absolute))
            {
                faceClient.BaseUri = new Uri(baseUri);
            }
            else
            {
                MessageBox.Show(baseUri,
                    "Invalid URI", MessageBoxButton.OK, MessageBoxImage.Error);
                Environment.Exit(0);
            }
        }

        // Displays the image and calls UploadAndDetectFaces.
        private async void BrowseButton_Click(object sender, RoutedEventArgs e)
        {
            // Get the image file to scan from the user.
            var openDlg = new Microsoft.Win32.OpenFileDialog();

            openDlg.Filter = "JPEG Image(*.jpg)|*.jpg";
            bool? result = openDlg.ShowDialog(this);

            // Return if canceled.
            if (!(bool)result)
            {
                return;
            }

            // Display the image file.
            string filePath = openDlg.FileName;

            Uri fileUri = new Uri(filePath);
            BitmapImage bitmapSource = new BitmapImage();

            bitmapSource.BeginInit();
            bitmapSource.CacheOption = BitmapCacheOption.None;
            bitmapSource.UriSource = fileUri;
            bitmapSource.EndInit();

            FacePhoto.Source = bitmapSource;
        }

        // Displays the face description when the mouse is over a face rectangle.
        private void FacePhoto_MouseMove(object sender, MouseEventArgs e)
        {
        }
    }
}

구독 키를 삽입하고 지역을 확인하거나 변경합니다.Insert your subscription key and verify or change the region

  • MainWindow.xaml.cs에서 다음 줄을 찾아 <Subscription Key>를 Face API 구독 키로 바꿉니다.Find the following line in MainWindow.xaml.cs and replace <Subscription Key> with your Face API subscription key:

    private const string subscriptionKey = "<SubscriptionKey>";
    
  • MainWindow.xaml.cs에서 다음 줄을 찾아 구독 키와 연결된 Azure 지역을 바꾸거나 확인합니다.Find the following line in MainWindow.xaml.cs and replace or verify the Azure region associated with your subscription key:

    private const string baseUri =
        "https://westcentralus.api.cognitive.microsoft.com/face/v1.0";
    

    위치가 구독 키를 획득한 위치와 동일한지 확인합니다.Make sure the location is the same as where you obtained your subscription keys. 예를 들어 westus 지역에서 구독 키를 획득한 경우 WestcentralusWestus로 바꿉니다.If you obtained your subscription keys from the westus region, for example, replace Westcentralus with Westus.

    평가판을 사용하여 구독 키를 받은 경우 키의 지역이 westcentralus이므로 변경할 필요가 없습니다.If you received your subscription keys by using the free trial, the region for your keys is westcentralus, so no change is required.

앱 테스트Test the app

메뉴에서 시작을 눌러 앱을 테스트합니다.Press Start on the menu to test your app. 창이 열리면 왼쪽 아래에 있는 찾아보기를 클릭합니다.When the window opens, click Browse in the lower left corner. 파일 열기 대화 상자가 나타나고 여기에서 창에 표시된 사진을 찾아보고 선택할 수 있습니다.A File Open dialog appears where you can browse and select a photo, which is then displayed in the window.

수정되지 않은 얼굴 이미지를 보여주는 스크린샷

얼굴을 감지할 이미지 업로드Upload an image to detect faces

얼굴을 감지하는 가장 간단한 방법은 로컬 이미지를 업로드하기 위해 API 감지 메서드를 래핑하는 FaceClient.Face.DetectWithStreamAsync 메서드를 호출하는 것입니다.The most straightforward way to detect faces is by calling the FaceClient.Face.DetectWithStreamAsync method, which wraps the Detect API method for uploading the local image.

FacePhoto_MouseMove 메서드 아래의 MainWindow 클래스에 다음 코드를 삽입합니다.Insert the following method in the MainWindow class, below the FacePhoto_MouseMove method.

분석할 얼굴 속성 목록이 만들어지고 제출된 이미지 파일은 Stream으로 읽혀집니다.A list of face attributes to analyze is created and the submitted image file is read into a Stream. 둘 다 DetectWithStreamAsync 호출에 전달됩니다.Both are passed to the DetectWithStreamAsync call.

// Uploads the image file and calls DetectWithStreamAsync.
private async Task<IList<DetectedFace>> UploadAndDetectFaces(string imageFilePath)
{
    // The list of Face attributes to return.
    IList<FaceAttributeType> faceAttributes =
        new FaceAttributeType[]
        {
            FaceAttributeType.Gender, FaceAttributeType.Age,
            FaceAttributeType.Smile, FaceAttributeType.Emotion,
            FaceAttributeType.Glasses, FaceAttributeType.Hair
        };

    // Call the Face API.
    try
    {
        using (Stream imageFileStream = File.OpenRead(imageFilePath))
        {
            // The second argument specifies to return the faceId, while
            // the third argument specifies not to return face landmarks.
            IList<DetectedFace> faceList =
                await faceClient.Face.DetectWithStreamAsync(
                    imageFileStream, true, false, faceAttributes);
            return faceList;
        }
    }
    // Catch and display Face API errors.
    catch (APIErrorException f)
    {
        MessageBox.Show(f.Message);
        return new List<DetectedFace>();
    }
    // Catch and display all other errors.
    catch (Exception e)
    {
        MessageBox.Show(e.Message, "Error");
        return new List<DetectedFace>();
    }
}

얼굴 주위에 사각형 그리기Draw rectangles around each face

이미지에서 감지된 각 얼굴 주위에 사각형을 그리는 코드를 추가합니다.Add the code to draw a rectangle around each detected face in the image.

MainWindow.xaml.cs에서 async 한정자를 BrowseButton_Click 메서드에 추가합니다.In MainWindow.xaml.cs, add the async modifier to the BrowseButton_Click method.

private async void BrowseButton_Click(object sender, RoutedEventArgs e)

FacePhoto.Source = bitmapSource 줄 뒤 BrowseButton_Click 메서드의 끝에 다음 코드를 삽입합니다.Insert the following code at the end of the BrowseButton_Click method, after the FacePhoto.Source = bitmapSource line.

감지된 얼굴 목록은 UploadAndDetectFaces에 대한 호출로 채워집니다.The list of detected faces is populated by the call to UploadAndDetectFaces. 그 다음, 각 얼굴 주위에 사각형이 그려지고 수정된 이미지가 주 창에 표시됩니다.Next, a rectangle is drawn around each face, and the modified image is displayed in the main window.

// Detect any faces in the image.
Title = "Detecting...";
faceList = await UploadAndDetectFaces(filePath);
Title = String.Format(
    "Detection Finished. {0} face(s) detected", faceList.Count);

if (faceList.Count > 0)
{
    // Prepare to draw rectangles around the faces.
    DrawingVisual visual = new DrawingVisual();
    DrawingContext drawingContext = visual.RenderOpen();
    drawingContext.DrawImage(bitmapSource,
        new Rect(0, 0, bitmapSource.Width, bitmapSource.Height));
    double dpi = bitmapSource.DpiX;
    resizeFactor = (dpi > 0) ? 96 / dpi : 1;
    faceDescriptions = new String[faceList.Count];

    for (int i = 0; i < faceList.Count; ++i)
    {
        DetectedFace face = faceList[i];

        // Draw a rectangle on the face.
        drawingContext.DrawRectangle(
            Brushes.Transparent,
            new Pen(Brushes.Red, 2),
            new Rect(
                face.FaceRectangle.Left * resizeFactor,
                face.FaceRectangle.Top * resizeFactor,
                face.FaceRectangle.Width * resizeFactor,
                face.FaceRectangle.Height * resizeFactor
                )
        );

        // Store the face description.
        faceDescriptions[i] = FaceDescription(face);
    }

    drawingContext.Close();

    // Display the image with the rectangle around the face.
    RenderTargetBitmap faceWithRectBitmap = new RenderTargetBitmap(
        (int)(bitmapSource.PixelWidth * resizeFactor),
        (int)(bitmapSource.PixelHeight * resizeFactor),
        96,
        96,
        PixelFormats.Pbgra32);

    faceWithRectBitmap.Render(visual);
    FacePhoto.Source = faceWithRectBitmap;

    // Set the status bar text.
    faceDescriptionStatusBar.Text =
        "Place the mouse pointer over a face to see the face description.";
}

이미지의 얼굴 설명Describe the faces in the image

UploadAndDetectFaces 메서드 아래의 MainWindow 클래스에 다음 코드를 추가합니다.Append the following method to the MainWindow class, below the UploadAndDetectFaces method.

메서드는 얼굴 특성을 얼굴을 설명하는 문자열로 변환합니다.The method converts the face attributes into a string describing the face. 이 문자열은 마우스 포인터가 얼굴 사각형을 가리킬 때 표시됩니다.The string is displayed when the mouse pointer hovers over the face rectangle.

// Creates a string out of the attributes describing the face.
private string FaceDescription(DetectedFace face)
{
    StringBuilder sb = new StringBuilder();

    sb.Append("Face: ");

    // Add the gender, age, and smile.
    sb.Append(face.FaceAttributes.Gender);
    sb.Append(", ");
    sb.Append(face.FaceAttributes.Age);
    sb.Append(", ");
    sb.Append(String.Format("smile {0:F1}%, ", face.FaceAttributes.Smile * 100));

    // Add the emotions. Display all emotions over 10%.
    sb.Append("Emotion: ");
    Emotion emotionScores = face.FaceAttributes.Emotion;
    if (emotionScores.Anger >= 0.1f)
        sb.Append(String.Format("anger {0:F1}%, ", emotionScores.Anger * 100));
    if (emotionScores.Contempt >= 0.1f)
        sb.Append(String.Format("contempt {0:F1}%, ", emotionScores.Contempt * 100));
    if (emotionScores.Disgust >= 0.1f)
        sb.Append(String.Format("disgust {0:F1}%, ", emotionScores.Disgust * 100));
    if (emotionScores.Fear >= 0.1f)
        sb.Append(String.Format("fear {0:F1}%, ", emotionScores.Fear * 100));
    if (emotionScores.Happiness >= 0.1f)
        sb.Append(String.Format("happiness {0:F1}%, ", emotionScores.Happiness * 100));
    if (emotionScores.Neutral >= 0.1f)
        sb.Append(String.Format("neutral {0:F1}%, ", emotionScores.Neutral * 100));
    if (emotionScores.Sadness >= 0.1f)
        sb.Append(String.Format("sadness {0:F1}%, ", emotionScores.Sadness * 100));
    if (emotionScores.Surprise >= 0.1f)
        sb.Append(String.Format("surprise {0:F1}%, ", emotionScores.Surprise * 100));

    // Add glasses.
    sb.Append(face.FaceAttributes.Glasses);
    sb.Append(", ");

    // Add hair.
    sb.Append("Hair: ");

    // Display baldness confidence if over 1%.
    if (face.FaceAttributes.Hair.Bald >= 0.01f)
        sb.Append(String.Format("bald {0:F1}% ", face.FaceAttributes.Hair.Bald * 100));

    // Display all hair color attributes over 10%.
    IList<HairColor> hairColors = face.FaceAttributes.Hair.HairColor;
    foreach (HairColor hairColor in hairColors)
    {
        if (hairColor.Confidence >= 0.1f)
        {
            sb.Append(hairColor.Color.ToString());
            sb.Append(String.Format(" {0:F1}% ", hairColor.Confidence * 100));
        }
    }

    // Return the built string.
    return sb.ToString();
}

얼굴 설명 표시Display the face description

FacePhoto_MouseMove 메서드를 다음 코드로 바꿉니다.Replace the FacePhoto_MouseMove method with the following code.

이 이벤트 처리기는 마우스 포인터가 얼굴 사각형을 가리킬 때 얼굴 설명 문자열을 표시합니다.This event handler displays the face description string when the mouse pointer hovers over the face rectangle.

private void FacePhoto_MouseMove(object sender, MouseEventArgs e)
{
    // If the REST call has not completed, return.
    if (faceList == null)
        return;

    // Find the mouse position relative to the image.
    Point mouseXY = e.GetPosition(FacePhoto);

    ImageSource imageSource = FacePhoto.Source;
    BitmapSource bitmapSource = (BitmapSource)imageSource;

    // Scale adjustment between the actual size and displayed size.
    var scale = FacePhoto.ActualWidth / (bitmapSource.PixelWidth / resizeFactor);

    // Check if this mouse position is over a face rectangle.
    bool mouseOverFace = false;

    for (int i = 0; i < faceList.Count; ++i)
    {
        FaceRectangle fr = faceList[i].FaceRectangle;
        double left = fr.Left * scale;
        double top = fr.Top * scale;
        double width = fr.Width * scale;
        double height = fr.Height * scale;

        // Display the face description if the mouse is over this face rectangle.
        if (mouseXY.X >= left && mouseXY.X <= left + width &&
            mouseXY.Y >= top  && mouseXY.Y <= top + height)
        {
            faceDescriptionStatusBar.Text = faceDescriptions[i];
            mouseOverFace = true;
            break;
        }
    }

    // String to display when the mouse is not over a face rectangle.
    if (!mouseOverFace)
        faceDescriptionStatusBar.Text =
            "Place the mouse pointer over a face to see the face description.";
}

앱 실행Run the app

이 응용 프로그램을 실행하고 얼굴이 포함된 이미지를 찾습니다.Run the application and browse for an image containing a face. Face 서비스가 응답할 수 있도록 몇 초 동안 기다립니다.Wait for a few seconds to allow the Face service to respond. 그러면 이미지의 얼굴에 빨간색 사각형이 표시됩니다.After that, you'll see a red rectangle on the faces in the image. 마우스를 얼굴 사각형 위로 이동하면 얼굴에 대한 설명이 상태 표시줄에 나타납니다.By moving the mouse over a face rectangle, the description of that face appears on the status bar.

사각형으로 포착되어 감지된 얼굴을 보여주는 스크린샷

요약Summary

이 자습서에서는 Face 서비스 클라이언트 라이브러리를 사용하는 기본 프로세스를 배웠으며, 이미지에서 얼굴을 표시하고 포착하는 응용 프로그램을 만들었습니다.In this tutorial, you learned the basic process for using the Face service client library, and created an application to display and frame faces in an image.

다음 단계Next steps

얼굴 이정표를 탐지하고 사용하는 방법에 대해 알아보세요.Learn about detecting and using face landmarks.