티스토리 뷰

반응형

윈도우 10 앱 개발(Windows 10 UWP app)


지난 포스트에 그룹을 만들고, 사람을 등록하고, 사람에 사진을 추가한 후에 Training을 시켜서 인식을 할 수 있는 기본 단계까지 작업을 진행 했습니다. 이번 회는 인식작업을 수행하는 과정을 살펴 보도록 하겠습니다.


Face API 사용해보기

Face API 사용해보기 part2

Face API 사용해보기 part3


1. 참고


How to Identify Faces in Image



2. NuGet Package 추가


WriteableBitmapEx 를 추가합니다. 이미지에 빨간 테두리, 퍼런 테두리 등의 작업을 하는데 필요합니다~

자세한 사항은 Face API 사용해 보기 포스트를 보시면 됩니다.



3. IdentifyPage.xaml 추가


사진을 로드하고, 서버에 보내서 탐지 및 인식하는 작업을 수행하는 화면입니다.


<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:FaceIdentify.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="using:Template10.Controls"
    xmlns:ViewModels="using:FaceIdentify.ViewModels"
    x:Class="FaceIdentify.Views.IdentifyPage"
    mc:Ignorable="d">

    <Page.DataContext>
        <ViewModels:IdentifyPageViewModel />
    </Page.DataContext>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="0.7*" />
            <RowDefinition Height="0.3*" />
        </Grid.RowDefinitions>

        <!--페이지 해더-->
        <controls:PageHeader x:Name="pageHeader" Text="Identify Page">
            <!--인식할 사진 추가-->
            <AppBarButton Icon="Pictures" Label="Open Picture" Command="{Binding OpenPictureCommand}" />
        </controls:PageHeader>

        <!--이미지 보기-->
        <Image x:Name="image" Grid.Row="1" Source="{Binding BitmapSource}" />

        <!--인식된 이미지 보기-->
        <ListView Grid.Row="2" ItemsSource="{Binding IdentifyFaceList}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="1*" />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Image Grid.RowSpan="2" Width="40" Height="40"
                               Source="{Binding FaceImage}" Margin="4" />
                        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}"
                                   Style="{StaticResource BodyTextBlockStyle}" />
                        <TextBlock Grid.Row="1" Grid.Column="1" Style="{StaticResource BodyTextBlockStyle}">
                            <Run Text="{Binding Confidence}" />%
                        </TextBlock>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>


디자인 타임에는 아래와 같이 표시 됩니다.




4. IdentifyPageViewModel.cs


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Windows.Input;
using Windows.ApplicationModel;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI;
using Windows.UI.Popups;
using Windows.UI.Xaml.Media.Imaging;
using FaceIdentify.Models;
using FaceIdentify.Services;
using FaceIdentify.Views;
using Template10.Mvvm;

namespace FaceIdentify.ViewModels
{
    /// <summary>
    ///     인식 페이지
    /// </summary>
    public class IdentifyPageViewModel : ViewModelBase
    {
        private BitmapSource _bitmapSource;
        private IList<PersonModel> _identifyFaceList;

        /// <summary>
        ///     기본 생성자
        /// </summary>
        public IdentifyPageViewModel()
        {
            if (DesignMode.DesignModeEnabled)
            {
                //디자인 타임 데이터
            }
            else
            {
                Init();
            }
        }

        /// <summary>
        ///     비트맵 소스
        /// </summary>
        public BitmapSource BitmapSource
        {
            get { return _bitmapSource; }
            set { Set(ref _bitmapSource, value); }
        }

        /// <summary>
        ///     인식한 얼굴 목록
        /// </summary>
        public IList<PersonModel> IdentifyFaceList
        {
            get { return _identifyFaceList; }
            set { Set(ref _identifyFaceList, value); }
        }

        /// <summary>
        ///     사진 열기 커맨드
        /// </summary>
        public ICommand OpenPictureCommand { get; set; }

        /// <summary>
        ///     초기화
        /// </summary>
        private void Init()
        {
            IdentifyFaceList = new ObservableCollection<PersonModel>();
            OpenPictureCommand = new DelegateCommand(OpenPictureCommandHandler);
        }

        /// <summary>
        ///     파일 선택 창 열기
        /// </summary>
        private async void OpenPictureCommandHandler()
        {
            //FileOpePicker 설정
            var openDlg = new FileOpenPicker();
            openDlg.FileTypeFilter.Add(".jpg");
            openDlg.FileTypeFilter.Add(".jpeg");
            openDlg.FileTypeFilter.Add(".png");
            //파일 선택 창 출력
            var file = await openDlg.PickSingleFileAsync();
            if (file == null) return;
            //파일 기본 속성 조회
            var basic = await file.GetBasicPropertiesAsync();
            //4MB 이상이면 메시지 출력
            if (basic.Size > 4*1024*1024)
            {
                var msg = new MessageDialog("Please select a picture below 4MB");
                await msg.ShowAsync();
                return;
            }

            IdentifyFaceList.Clear();

            Busy.SetBusy(true, "Identifying...");

            //처음 화면에 출력용
            var bm = new BitmapImage();
            var wbm = new WriteableBitmap(100, 100);
            //IRandonAccessStream으로 파일을 열고 읽어 들이기
            using (var stream = await file.OpenAsync(FileAccessMode.Read))
            {
                await bm.SetSourceAsync(stream);
                wbm = await BitmapFactory.New(1, 1)
                    .FromStream(stream, BitmapPixelFormat.Bgra8);
            }
            BitmapSource = bm;

            //얼굴인식하고 테두리 표시하기
            //Stream으로 파일을 열어서기
            using (var stream = await file.OpenStreamForReadAsync())
            {
                //이미지에서 얼굴 탐지
                var faces = await FaceAPIHelper.Instance.GetDetectAsync(stream);
                if (faces == null) goto ExitRtn;
                //탐지된 얼굴 표시
                foreach (var face in faces)
                {
                    wbm.DrawRectangle(face.FaceRectangle.Left, face.FaceRectangle.Top,
                        face.FaceRectangle.Left + face.FaceRectangle.Width,
                        face.FaceRectangle.Top + face.FaceRectangle.Height, Colors.Red);
                }

                //한번에 인식 가능한 얼굴의 수10개로 제한
                for (var i = 0; i < faces.Length; i = i + 10)
                {
                    var faceIds = faces.Select(p => p.FaceId)
                        .Skip(i*10)
                        .Take(10)
                        .ToArray();
                    if (!faceIds.Any()) continue;
                    //인식 시작
                    var results = await FaceAPIHelper.Instance.IdentifyAsync(faceIds);
                    //결과를 루프를 돌면서 사용자 정보를 불러 옮
                    foreach (var identifyResult in results)
                    {
                        var identifyPerson = new PersonModel();
                        //탐색된 얼굴 정보 조회
                        var face = faces.First(p => p.FaceId == identifyResult.FaceId);
                        //탐색된 얼굴 이미지(빈 통)
                        var faceImage = new WriteableBitmap(face.FaceRectangle.Width, face.FaceRectangle.Height);
                        //원본 이미지에서 얼굴 부분만 복사
                        faceImage.Blit(new Rect(0, 0, faceImage.PixelWidth, faceImage.PixelHeight)
                            , wbm,
                            new Rect(face.FaceRectangle.Left, face.FaceRectangle.Top, face.FaceRectangle.Width,
                                face.FaceRectangle.Height));
                        //얼굴 추가
                        identifyPerson.AddFace(face.FaceId, faceImage);

                        if (identifyResult.Candidates.Length == 0)
                        {
                            //찾은 사람이 없음
                            identifyPerson.Name = "No one identified";
                        }
                        else
                        {
                            //가장 비슷한 사람 아이디를 반환
                            var candidate = identifyResult.Candidates.First();
                            var candidateId = candidate.PersonId;
                            //사람 정보 조회
                            var person = await FaceAPIHelper.Instance.GetPersonAsync(candidateId);

                            identifyPerson.Name = person.Name;
                            identifyPerson.PersonId = person.PersonId;
                            identifyPerson.Confidence = candidate.Confidence;

                            wbm.DrawRectangle(face.FaceRectangle.Left, face.FaceRectangle.Top,
                                face.FaceRectangle.Left + face.FaceRectangle.Width,
                                face.FaceRectangle.Top + face.FaceRectangle.Height, Colors.Blue);
                        }
                        //목록에 추가
                        IdentifyFaceList.Add(identifyPerson);
                    }
                }
            }
            BitmapSource = wbm;
            ExitRtn:
            Busy.SetBusy(false);
        }
    }
}



5. PersonModel 수정했습니다.


굵은 글씨 부분의 프로퍼티를 추가했습니다.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml.Media.Imaging;
using Microsoft.ProjectOxford.Face.Contract;

namespace FaceIdentify.Models
{
    public class PersonModel : Person, INotifyPropertyChanged
    {
        /// <summary>
        ///     Guid -> Face Guid, BitmapSource -> 사진
        /// </summary>
        public IDictionary<Guid, BitmapSource> Faces = new Dictionary<Guid, BitmapSource>();

        /// <summary>
        ///     바인딩을 하기 위한 프로퍼티
        /// </summary>
        public IList<BitmapSource> FaceImages
        {
            get { return Faces.Select(p => p.Value).ToList(); }
        }

        /// <summary>
        ///     첫번째 사진 무조건 반환, 없으면 말고;;
        /// </summary>
        public BitmapSource FaceImage
        {
            get { return Faces.Values.FirstOrDefault(); }
        }

        /// <summary>
        ///     첫번째 아이디 반환
        /// </summary>
        public string FaceId
        {
            get { return Faces.Keys.FirstOrDefault().ToString(); }
        }

        /// <summary>
        ///     정확도
        /// </summary>
        public double Confidence { get; set; }

        /// <summary>
        ///     얼굴 추가
        /// </summary>
        /// <param name="faceId"></param>
        /// <param name="bs"></param>
        public void AddFace(Guid faceId, BitmapSource bs)
        {
            Faces.Add(faceId, bs);
            //얼굴 추가 메소드를 이용해서 프로퍼티 체인지 이벤트를 강제로 발생시킴
            OnPropertyChanged("FaceImages");
        }

        /// <summary>
        ///     얼굴 삭제
        /// </summary>
        /// <param name="faceId"></param>
        public void RemoveFace(Guid faceId)
        {
            if (!Faces.ContainsKey(faceId)) return;
            Faces.Remove(faceId);
            OnPropertyChanged("FaceImages");
        }

        #region INotifyPropertyChanged, Person 모델을 상속 받았기 때문에 인터페이스를 직접 구현

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}



6. Shell.xaml


메인 메뉴에 새로 추가한 페이지로 이동하는 버튼을 추가했습니다.


            <!--  IdentifyPage button  -->
            <Controls:HamburgerButtonInfo PageType="views:IdentifyPage">
                <StackPanel Orientation="Horizontal">
                    <SymbolIcon Width="48" Height="48"
                                Symbol="Contact2" />
                    <TextBlock Margin="12,0,0,0" VerticalAlignment="Center"
                               Text="Identify" />
                </StackPanel>
            </Controls:HamburgerButtonInfo>



7. 실행


실행한 후 새로 추가한 버튼을 눌러서 Identify Page로 이동 했습니다.



사진을 선택하면 자동으로 작업을 수행합니다.



짜잔 최종 결과입니다. 이 사진에서 성소는 파란색 테두리가 그려져있는 얼굴이고 0.6정도의 컨피던스를 가지고 있내요..



사진 인식하기 참 쉽죠? 후후후후 그럼 이 셈플앱 + 아이디어를 추가해서 더 좋은 앱을 만들어 보시면 좋겠습니다.

그리고, 마이크로소프트 코그니티브 서비스는 아이폰, 안드로이드를 모두 지원합니다~


궁금하신 사항은 윈도우 10 앱 개발(Windows 10 UWP app)에 언제나 문의 주세요

그럼 다음에는 또 다른 셈플 앱을 만들어 보도록 하겠습니다.



FaceIdentify_part3.zip


반응형
댓글