티스토리 뷰
윈도우 10 앱 개발(Windows 10 UWP app)
지난 포스트에 그룹을 만들고, 사람을 등록하고, 사람에 사진을 추가한 후에 Training을 시켜서 인식을 할 수 있는 기본 단계까지 작업을 진행 했습니다. 이번 회는 인식작업을 수행하는 과정을 살펴 보도록 하겠습니다.
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)에 언제나 문의 주세요
그럼 다음에는 또 다른 셈플 앱을 만들어 보도록 하겠습니다.
'UWP & Windows App > Beginner' 카테고리의 다른 글
Windows Template Studio를 이용해서 UWP 앱 개발 시작하기 (1) | 2017.07.31 |
---|---|
Template10을 이용해서 UWP 앱 개발하기 Part2 MVVM (0) | 2017.01.09 |
Face API 사용해보기 part2 - Identify, Template 10 (0) | 2016.11.09 |
Template10을 이용해서 UWP 앱 개발하기 Part1 (0) | 2016.11.07 |
Tile과 Toast Notification 사용하기 (0) | 2016.10.17 |
- Total
- Today
- Yesterday
- visual studio 2019
- WPF
- IOT
- #prism
- uno-platform
- Microsoft
- Build 2016
- kiosk
- windows 11
- Bot Framework
- #MVVM
- UWP
- LINQ
- uno platform
- Always Encrypted
- PRISM
- Windows 10
- ComboBox
- dotNETconf
- XAML
- ef core
- MVVM
- Cross-platform
- .net 5.0
- .net
- C#
- #Windows Template Studio
- Behavior
- #uwp
- Visual Studio 2022
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |