블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/ kaki104

카테고리

List All (551)
Xamarin Forms (4)
Bot Framework (19)
Azure (9)
Windows 10 (35)
Facebook News & Tips (158)
Windows App(Universa.. (83)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
WPF (1)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total521,316
Today35
Yesterday108



Windows 10 IoT Core에 음성으로 명령을 내려서 음악을 재생, 다음곡, 이전곡, 장르 검색의 기능을 수행하는 UWP 앱을 개발하는 과정을 동영상으로 만들었습니다. 개발 소스, 동영상, ppt를 참고하시면 여러분도 멋진 앱을 만드실 수 있을 것이라고 생각합니다.


감상하시기 전에 유튜브 채널 구독 신청 부탁드립니다~. 유튜브 채널 바로가기



완성된 앱 시연 영상




Part 1


* 환경 및 준비
* 참고 자료
* Media Player
* SRGS
* IoT Player with voice commands
* Begin Playback
* PC 환경 설정 확인
* 앱 개발 순서
* 뮤직 플레이 페이지 구현 로직




Part 2


* 프로젝트 추가
* package.appmanifest 설정
* 앱 초기화 모듈 구현
- 외장 디스크 연결 확인
- 일단 음악 재생이 되도록 작업
* MediaPlayerViewModel.cs
- Navigation_Navigated 구현
- 음성 인식 초기화
- MediaPlayerElement 초기화




Part 3


* SRGS 만들기
- begin playback
* MediaPlayerViewModel.cs
- SRGS 파일 불러와서 음성 인식 컴파일
* 시작 페이지를 MediaPlayerPage로 변경
* MediaPlayerViewModel.cs
- 시작하면서 음성 인식 시작 하도록
- 음성 인식 후 넘어오는 데이터 확인
- tag에 ECMAScript를 이용해서 반환 결과 변경
- 음성 인식 결과 확인
- 음성 인식 결과를 action에 넣어서 어떤 명령인지 구분해서 명령 실행
- BeginPlaybackAsync() 실행
- DispatcherHelper 초기화, 사용
- 결과 확인




Part 4


* 음성 입력 대기 상태 구현 및 음성 명령 종류에 따른 분기 처리 시작
* SRGS에 기능 추가
- pause playback
- MediaPlayerViewModel에도 pause playback 구현
- stop playback
- next playback
- previous playback
* MediaPlayerPage.xaml 디자인 작업
- Binding




Part 5


* Begin playback 구현
* MediaPlayerViewModel.cs
- InitPlaylistAsync()
- 외장 드라이브에서 파일 목록 가지고오도록...
- BeginPlaybackAsync() 수정
- GetMusicPropertiesAsync()를 사용해서 타이틀, 앨범, 아티스트, 이미지 출력
- PosterSource 수정
* MediaPlayerBehavior 추가 및 구현
- CurrentPlaybackState 추가
* MediaPlayerBehavior를 MediaPlayerElement와 연결 - blend
- Binding
* MediaPlayerViewModel.cs
- Init() 수정 - PropertyChanged 추가
* MediaPlayerBehavior 수정
- MediaPlayer를 MediaPlayerElement에 추가




Part 6


* 외장 드라이브에서 파일 목록 가지고 오는 것 포기!! 그래서 Music Library에서 파일 목록을 가지고 오는 방법으로 변경
- SD 카드에 Music Library에 파일 복사하기..
* MediaPlayerViewModel.cs
- CreateFileQueryWithOptions()를 이용해서 파일 필터링과 목록 조회를 동시에
* Pause playback 구현
* CommandMediaPlayer enum 추가
- Messenger를 이용해서 MediaPlayerBehavior에 명령 전달
* MediaPlayerBehavior 수정
- Messenger 구독
- Source 프로퍼티 추가
- ExecuteCommandMediaPlayer 구현




Part 7


** Next, Previous 기능 구현 동영상은 생략 합니다.

(편집했는데..날려서..ㅜㅜ 혹~~~~~~시라도..필요하다고 하시는 분이 계시면..한 10분 정도;; 그러면 다시 편집해서 추가하도록 하겠습니다~)


* 음악 검색 기능 구현
- Filter 추가
* MediaPlayerViewModel.cs
- SelectSourceAsync()
* SRGS.xml 파일에 장르 추가
- 재생 완료 후 다음 곡으로 넘어가는 기능 구현
- ContinuousRecognitionSession_Completed 이벤트 발생 이유에 대해..
* 존재하지 않는 장르를 이야기하면 미친듯이 돌아가는 버그!! 잡아주세요




PPT


iot-mediaplayer.pptx








Posted by MVP kaki104



IoT Core에서 동작하는 Speech Recognition에 대한 내용입니다.


ppt작성, 코딩, 촬영, 편집까지.. 시간이 많이 걸리내요.. 아침에 일어나서 바로 시작한 작업인데..밤새고 새벽에..쿨럭..

음성 인식에 대해서 처음 접하시는 분들이 보시고 이해하실 수 있으면 좋겠습니다. 


앞으로 지속적으로 음성 인식과 연결된 주제에 대해서 동영상을 만들려고 합니다. 그 끝에는 어떤 것이 있을까요?

높은 산의 꼭데기만 보고 뛰어서 올라가면 힘이 마니 듭니다. 하지만, 산 아래에서 부터 천천히 꾸준히 올라간다면, 아무리 높은 산이라도 정복 할 수 있습니다. 그러니, 모두 함께 천천히 올라갔으면 좋겠습니다.


마지막, 한가지 작은 부탁이라면, 유튜브 구독자가 좀더 늘었으면 하는..후후

유튜브 채널 바로가기






Part 1


* 환경 및 준비
* 참고 자료
* Speech Recognition Constraints
* Speech Recognition 기초
* SpeechRecognizer
* List of speech recognition supported languages




Part 2


* 영어 말하기 연습 앱 개발
* PC 환경 설정 확인
* 앱 개발 순서
* 프로젝트 생성
* 세팅




Part 3


* 영어 단문을 CSV 파일로 생성
* 생성한 파일 프로젝트 추가하고 빌드시 앱 설치 폴더에 복사되도록 설정
* 파일을 읽어서 ShellPage CaseList 프로퍼티에 추가
* GitHub에 Project생성
* To-do 등록 - issues로 변경

* 브랜치 생성

* PR 생성




Part 4


* 기본 연습 페이지 구현 시작
* MainPage.xaml.cs 코딩
- SpeechAndTTS 셈플 프로젝트 중 Scenario_ListConstraint의 코드를 복사해서 붙여 넣는 방법으로 기본 작업
- OnNavigateTo 코딩
- AudioCapturePermissions 클래스 복사
- InitializeRecognizerAsync 코딩
- CommonHelper 클래스 추가, ShowMessageAsync 추가




Part 5


* 기본 연습 페이지 화면 구현 시작 MainPage.xaml
* MainPage.xaml.cs
- ButtonBase_OnClick 구현
- _speechRecognizer_StateChanged 구현
* 화면 테스트
- ButtonBase_OnClick 수정 : 실패인 경우 메시지 추가
* 라즈베리파이3에 배포 테스트



Part 6

* 심화 연습 페이지 구현
* MainPage.xaml.cs에
- OnNavigatedFrom 구현
- Cleanup 수정
* PredefindedPage.xaml
- MainPage.xaml 디자인 복사
* PredefindedPage.xaml.cs
- field 복사
- MainPage.xaml.cs 코드 복사 후 수정
- InitializeRecognizerAsync 수정
- ButtonBase_OnClick 수정
- ContinuousRecognitionSession_Completed 구현
- _speechRecognizer_HypothesisGenerated 구현
- ContinuousRecognitionSession_ResultGenerated 구현



Part 7

* 심화 연습 페이지 구현
* PredefindedPage.xaml.cs
- _timer 추가 구현
* 테스트
* 라즈베리파이3에 배포 테스트
* 버그 수정
* 마침말



PPT




Posted by MVP kaki104



이번 포스트 주제는 Windows 10 IoT Core - Hello World 입니다.

IoT를 처음 시작하시려는 분들을 위한 포스트로, 자세하게 설명하고 있습니다. 앞으로 동영상의 방향은 IoT를 이용한 여러가지 앱을 만들어서 응용하는 방향으로 진행할 예정입니다.


많은 구독 신청 부탁드립니다.



Part1


*Why Windows 10 IoT Core?
Windows 10 IoT Core Compatible Boards
Raspberry Pi 3 kits
What can you make?
라즈베리파이에 윈도우 설치 및 설정
해상도 설정 참고 자료





Part2


Hello World Windows 10 IoT Core




PPT


iot-helloworld.pptx


Posted by MVP kaki104

작년에 Windows 10 IoT가 Raspberry Pi를 지원한다는 뉴스를 듣고 참 관심이 많은 분야였던, IoT를 이제 시작해 보려고 합니다. IoT 초보이기 때문에 천천히 진행할 예정이며, LED에 불 깜박이는 것 보다 좀더 윈도우 기능을 이용하는 부분에 대해서 도전을 하려고 합니다. 


우선 첫번째 목표는 대략적인 모양은 그려 놓았는데, 진짜로 잘 동작이 되는지는 저도 해봐야하기에.. 쇼핑몰에서 라즈베리 파이 3 모델 B(너무 길어서 라파로 줄여서 이야기 하겠습니다)를 하나 구입했습니다. 

 

온라인 쇼핑몰의 기본은 총알배송이네요 ㅎㅎ 구입한지 2일만에 물건을 받았습니다.

 

 

제일 먼저 해야할 일은 SD카드에 Windows 10 IoT를 설치 하고, 그 걸 라파에 꼽아서 부팅을 해주어야 합니다. 아마 이런 내용들은 대부분 아실 것이라 생각하고 자세하게 다루지는 않겠습니다. 그동안 제가 궁금했던 것은 윈도우를 라파에 어떻게 설치하는지, 어떻게 앱을 배포하는지, 어떤 기능까지가 가능한지 였기 때문에 그 부분에 집중 하겠습니다. 

 

 

1. Windows 10 IoT core dashboard 사용하기

 

https://developer.microsoft.com/en-us/windows/iot/downloads

 

한글 설치 페이지가 있을까 해서 ko-kr로 변경해서 입력했지만, 메뉴만 한글로 나오는 군요;; 그냥 영어로 진행 합니다.

 

Get Windows 10 IOT Core Dashboard를 클릭하면, 설치형 프로그램인 IoT Dashboard를 다운 받을 수 있습니다. 이 프로그램은 윈도우 다운로드, 설치 및 관리에 사용되는 중요한 프로그램 입니다.

 

 

 

IoT Dashboard 실행 화면입니다. 처음에 Set up a new device 탭이 선택이 되어 있습니다.

 

제가 사용하는 디바이스가 라파이니 그대로 두고, OS Build는 최신 버전으로, SD 메모리가 들어있는 Drive는 자동선택됩니다.

 

관리자 비밀번호를 꼭! 입력하시고, IoT 디바이스와 네트워크 연결을 Wi-Fi로 하실려면 Wi-Fi를 꼭 체크해 주셔야 합니다.

 

Download and Install 버튼을 클릭하시면, 해당 버전을 다운로드하고 설치까지 진행 합니다. 이 과정은 금방 끝나니 잠시만 기다리시면 됩니다.

 

 

설치가 완료되면, SD 카드를 뽑아서 라파에 꼽아주고 전원을 연결하면 부팅이 됩니다.

 

 


2. 라파를 네트워크에 연결하기


SD 카드를 꼽고, 전원을 연결하면 네트워크에 자동으로 붙을 줄 알았습니다만.. 그런 일은 일어나지 않았습니다.

Windows 10 IoT도 나름 OS이기 때문에 부팅 과정도 들어가고, 윈도우 설정 같은 화면도 출력됩니다. 다만, 이런 작업을 위해서는 처음 한번은 HDMI를 이용해서 모니터에 연결하고, USB 키보드, 마우스를 이용해서, 네트워크를 설정해 주어야 하네요.


일반 랜선을 사용하는 경우에도 자동으로 언어 설정등을 하지 못하니(아마도..) 될 수 있으면 해당 주변 기기를 이용하는 것이 좋을 것 같습니다.

 



3. 라파와 PC 연결하기


My devices탭을 선택하고 잠시 기다리면 목록에 방금 연결한 녀석이 뜹니다. 이 때 동일한 네트워크(공유기에)에 연결이 않되어 있으면, 표시가 되지 않을 수 있으니 참고하시기 바랍니다.


라파를 선택하면 다음 화면으로 넘어 갑니다.

 


여기서는 간단한 정보만 확인이 가능합니다. User name이 입력이 되어 있는데, 수정은 불가능 하더군요.. Apply 버튼도 눌러지지 않고..


여기서 중요한 건 Open Windows Device Portal in browser 링크입니다. 링크를 클릭해서 브라우저를 열어서 장비 상태, 설정 등을 확인하고 변경할 수 있습니다.



Windows Device Portal 페이지 입니다. 


가장 처음에 보이는 것은 Device Settings이며, 장비 이름을 변경하고, 비밀 번호를 변경할 수 있습니다. 또한 오른쪽에 오디오 장치와 스피커에 대한 정보 등도 표시 됩니다.


아쉬운데로 이 화면을 보면서 이것 저것 만저 보기는 했는데.. 저는 디바이스 화면이 궁금하더군요. 아래 Capture Screenshot이라는 버튼을 누르면, 현재 라파의 화면을 캡처해서 보여주고, 저장을 할 수 있습니다만..아무래도 실시간으로 보는 것이 필요 했습니다.


 

그래서 찾아보니, Remote 탭을 이용하면 된다는 것을 알았습니다.


Enable Windows IoT Remote Server를 체크하신 후 그 아래 링크를 클릭합니다.

 


링크를 클릭하면 Windows IoT Remote Client라는 앱을 다운로드 받으라고 나옵니다. 이 앱의 용도는 제가 원하던 라파의 화면을 실시간으로 보면서, 직접 조작도 가능한 것입니다. 즉, HDMI, 마우스와 키보드가 더 이상 필요가 없어진 다는 것입니다.


단점은.. 좀 느립니다. 하하;; 빠른 화면, 빠른 조작을 원하시면 그냥 주변 장치를 사용하셔야 합니다.



앱을 성공적으로 설치하고, 실행하면 바로 Connect라는 화면이 나오는데, 콤보 박스를 클릭하니, 아까 연결했던 정보가 떡~하고 출력됩니다. 이녀석을 선택하고, Connect를 클릭합니다.



잠시 후에 짜잔~ 아래 화면과 같이 라파의 현재 화면 출력을 볼 수 있습니다. 하하;; 전 여기까지만 해도 신기하네요..

이런 재미를 이제야 느끼다니..



이번 포스트는 우선 여기까지만 진행 하겠습니다. OS깔고 화면 나오면 절반은 성공 아닐까요??


이제는 달리면 됩니다. 열심히 달려 보겠습니다. 아마 다음에는 헬로 월드를 IoT에서 실행하는 예제를 따라할 것 같네용




Posted by MVP kaki104

윈도우 10 앱 개발(Windows 10 UWP app) 12월 오프 모임 후기


Microsoft MVP 박문찬입니다. Xamarin 환경 설정을하기위한 오프라인 모임이 마이크로소프트 멜팅팟의 도움으로 강남 토즈1호점에서 무사히 진행되었습니다.


여러분들이 참석해 주셨고, 그 중 몇 분은 트러블 슈팅에 성공을해서 Hyper-V로 안드로이드 에물레이터를 실행할 수 있었습니다. 음 모두 성공했으면 좋았겠지만, 여러가지 환경적인 요소들로 인해 불가능했습니다.


Xamarin 트러블 슈팅 포스트는 업데이트를 했습니다. 물론 직접 여러분들과 진행을 하다보니, 처음 하시는 분들이 쉽게 하지는 못할 것 같은 내용들이 존재하네요. 하지만, 해결이 아에 불가능 한 것은 아니니 꼭 다시 시도해 보시기를 바랍니다.


*하지만...정 실행이 않된다면 그냥 안드로이드 에물레이터를 이용하셔도...


참석해주신 여러분들과 멜팅팟 관계자 분들께 감사드리며, 2017년 1월 모임때 뵙겠습니다.



Posted by MVP kaki104

Microsoft MVP, 윈도우 10 앱 개발(Windows 10 UWP app)  운영자 박문찬입니다. 11월 27일날 MSP가 추최하는 Xamarin 세션에 참여해서 좋은 시간을 보냈습니다. 차분히 내용을 보니 예전 Universal app 개발하는 방법과 거의 비슷하다는 생각을 했습니다. 에.. 그동안은 윈도우 앱 개발 공부하기 바뻐서 손을 못대고 있었는데.. 이제는 시작해야 준비해서 시작해야 할 것 같습니다. 앞으로 자주 Xamarin 관련 포스트도 등록하겠습니다.



0. 참고

Xamarin.Forms Quickstart


Xamarin.Forms Requirements


포스트 작성하는 환경 :

OS : Windows 10 version 1607

기타 : Windows SDK 14393 (이전 버전 SDK도 역시 설치되어있습니다. UWP 앱 개발을 주로 하고 있기 때문입니다)



1. 자마린 설치


https://www.xamarin.com/platform


Download now for free를 눌러서 설치파일을 다운로드 받습니다. 물론 설치전에 Requirements 문서를 읽어 보시면 많은 도움이 됩니다.




설치하는데 시간이 오래 걸리고, 기본 옵션으로 설치해야 성공하지 않는가 생각됩니다. 옵션 다른거 좀 건들이면 제대로 설치가 앙되더군요;;


총 다운로드받을 용량이 3GB가 넘으니..반드시 집에서 설치하시는 것을 권장합니다.



한참을 기다리고 인스톨 완료가 되면 비주얼 스튜디오를 실행합니다. 설치전 입력 사항에 비주얼 스튜디오가 이미 설치 되어 있다고 선택했습니다.



2. Visual Studio 2015 시작


File -> New Project -> Cross-Platform -> Blank XAML App(Xamarin.Forms Portable) 선택


Name은 HelloWorldXamarinForms로 하겠습니다.


* Blank App (Xamarin.Forms.Portable)은 UI를 코드로 작성합니다.

* Blank XAML App(Xamarin.Forms Portable)은 UI를 XAML로 작성합니다.


두가지 중에 편한 것으로 하시면 되는데..아무래도 저는 xaml이 편하네요..




만들기를 하면...머 Mac이 필요하다는 설명이 나옵니다. 물론 Mac이 없기 때문에 대충 읽고 패스, 다음에 나오는 메시지 박스도 Close시킵니다.



이 화면은 UWP 앱의 버전을 지정하는 화면입니다. 제 예상인데..자마린을 이용해서 크로스 플랫폼 앱을 개발하도록 하면서 자연 스럽게 UWP 앱 개발까지 할 수 있도록 하려는 것이 아닐까 생각됩니다.



그런데 UWP 앱 프로젝트를 생성하는 중에 오류가 발생하네요;; 다시 만들어 보겠습니다.



다른 버전을 선택해서 다시 만들어 보겠습니다.



11월 30일 기준으로 아직 Windows 10 Anniversary Edition이 적용된 것 같지 않습니다. 바로 이전 버전을 선택해서 만드시면 좋을 것 같습니다. 몇번 만들어 봤는데..오류가 발생하지 않는 경우도 있습니다;;


프로젝트 만들기가 완료되면, 6개의 프로젝트가 생성됩니다.



F6을 눌러서 전체 빌드를 한번 해주겠습니다.


빌드를 하면 Nuget package들을 다운 받기 때문에 역시나 빠른 네트워크가 연결되어있어야 합니다. 빨리 4G 무제한으로 변경해야 할듯합니다..쿨럭;;


Xamarin 환경 확인을 이용하면, 설치 상태에 대한 점검을 할 수 있습니다.

https://msdn.microsoft.com/en-us/library/mt488769.aspx



3. NuGet Packages 업데이트

반드시 모든 프로젝트에 포함되어있는 자마린 폼즈 패키지를 최신 버전으로 업그레이드를 해야합니다.


Solution 'HelloWorldXamarinForms' 선택 -> 마우스 오른쪽 클릭 -> 컨텍스트 메뉴에서 Manage NuGet Packages for Solution... 선택



Installed 탭 선택 -> Xamarin.Forms 선택 -> 전체 프로젝트 선택 -> 콤보박스에서 최신 버전(Latest stable...) 선택 -> Install 클릭



확인 창이 몇번 출력되고, OK를 눌러주면 인스톨 완료. 다시 시작해야 한다는 메시지가 출력되면 비주얼 스튜디오를 다시 시작해 주세요.


F6을 눌러서 빌드를 진행합니다.


Severity Code Description Project File Line Suppression State
Error  error: package android.support.v7.internal.widget.ActivityChooserModel does not exist
  android.support.v7.internal.widget.ActivityChooserModel.OnChooseActivityListener HelloWorldXamarinForms.Droid C:\Samples\HelloWorldXamarinForms\HelloWorldXamarinForms\HelloWorldXamarinForms.Droid\obj\Debug\android\src\mono\android\support\v7\internal\widget\ActivityChooserModel_OnChooseActivityListenerImplementor.java 8 


머 대략 이런 오류가 발생하는 경우에는 솔루션을 선택한 후 마우스 오른쪽 버튼을 눌러서 컨텍스 메뉴를 출력한 후 Rebuild Solution...을 선택해서 다시 빌드를 진행하고, 이상이 없으면 다음으로 넘어 갑니다.


* 프로젝트를 생성하는 중에 Xamarin for Visual Studio Update가 뜨는 경우가 있는데 역시 클릭해서 업데이트 해줍니다.



4. 시작 프로젝트 설정 및 필요없는 프로젝트 언로드..


실행할 프로젝트를 선택하고 F5를 눌러서 시작하기 위해서 솔루션에서 마우스 오른쪽 클릭 -> Properties 선택



Current selection 선택 -> OK 클릭



이렇게 하면 선택한 프로젝트 실행됩니다. 하지만, 난 F5를 눌렀을때 항상 드로이드 프로젝트가 실행되었으면 좋겠어!!라고 하시는 분들은 Single startup project에서 드로이드 프로젝트를 선택하시면 됩니다.


전 mac이 없기 때문에 ios용 앱을 실행해 볼 수 없기 때문에..ios용 프로젝트를 선택 후 Unload Project를 선택하면 다음부터는 맥을 연결하라는 메시지도 나오지 않고 편합니다.  동일한 방법으로 Windows 8.1, Windows Phone 8.1도 Unload 합니다.


결국 남는건 Portable, Droid, UWP 3개가 되겠습니다.




5. 실행


Droid 프로젝트를 선택하고, F5를 누르면 에뮬레이터가 실행되면서 앱이 실행됩니다. (PC에서는 기본 에물레이터 Android_Accelerated_x86(Android 6.0 - API 23이 실행이 앙돼서, 자마린 홈페이지에 있는 Xamarin Android Player를 이용해서 Nexus 4(KitKat)(Android 4.4 - API 19)를 다운로드 받아서 사용하고 있습니다.)




6. 설치해서 실행까지..


아마 이 과정이 PC마다 약간씩 다를 것 같습니다. 중간에 발생하는 오류도 많을 듯하고요..

Facebook 윈도우 10 앱 개발(Windows 10 UWP app)에 오류 사항에 올려주시면 같이 해결해서 해결하고, 해결한 내용은 이 포스트에 추가해 놓으면 새로 시작하는 분들에게 많은 도움이 될 것 같습니다.



12월 윈도우 10 앱 개발(Windows 10 UWP app) 그룹 오프라 모임 때 HelloXamarin을 함께 만들어 보는 시간을 마련 하려고 합니다. 설치는 시간이 너무 걸리기 때문에..될 수 있으면 설치는 하신 후에 오시면 좋을 것 같습니다.


진행할 내용은

* 프로젝트 구성에 대한 설명

* XAML을 이용해서 화면 구성하기

* 버튼 추가하고, 메시지 박스라도 하나 출력

이 정도를 진행하려고 합니다. 제가 Mac을 못해봤으니 Mac을 가지고 오시면 더 좋을 것 같습니다.


예상 일시

12월 14일 오후 7시부터 10시까지


예상 장소

강남역 토즈1호


참석을 원하시는 분들은 미리 페이스북에 리플로 남겨주시면 좋겠습니다.

12월 3일까지 신청 받은 인원을 기준으로 예약 진행 하겠습니다.




4.2.1.64
Xamarin.VS 4.2.1.64 is an updated Stable Release of Cycle 8 Service Release 1. Please see https://releases.xamarin.com/connect-keynote-releases for more information. You can also see the latest builds in all channels at https://developer.xamarin.com/releases/current/.

Notes from previous releases:

4.2.1.62
Xamarin.VS 4.2.1.62 is an updated Stable Release of Cycle 8 Service Release 1. Please see https://releases.xamarin.com/connect-keynote-releases for more information. You can also see the latest builds in all channels at https://developer.xamarin.com/releases/current/.


* Java update






Posted by MVP kaki104

Microsoft MVP 박문찬입니다. Windows 10 UWP app 개발 기초 과정 12회를 어제 마무리 했습니다. 3개월 동안 쉴 틈이 없이 교육을 받으러 와주신 여러분들과 물심 양면으로 도움을 주신 Microsoft 멜팅팟 프로그램과 담당자, 정자동 SK 사옥에 교육장소를 제공해주신 임직원분들, 마지막으로 저와 함께 강의를 해주신 권영철 MVP님께 감사를 드립니다.


9월 3일 처음 시작할 때의 모습입니다. 이때만 해도 무척 더워서 에어컨을 틀어야 할 정도 였습니다.



권영철 MVP님의 XAML UI 강의를 잘 들었습니다.



9월 24일 교육장 모습입니다. 뒷 모습만 보아도 알 수 있는 듯합니다. ㅎㅎ



10월 8일의 모습입니다. 교육장도 여러번 변경되어서 다양한 곳에서 진행했었습니다. 이때부터 주말 근무가 있는 분들이 생기신것 같내요..



10월 15일은 이렇게 둥글게 앉아서 진행했습니다.



11월 26일 마지막 교육은 서울대 입구역 근처에 있는 토즈에서 진행했습니다. 시설이 상당히 깨끗하고 마음에 드네요




교육을 마치고 입구에서 마지막 사진을 촬영했습니다. 반팔에서 시작해서 두꺼운 옷을 입는 계절까지 진행했던 교육이였습니다.


한곳에 안주하지 않고 변화하려는 개발자 분들이 계시기 때문에 앞으로 개발 환경이나 개발자의 처후가 더욱더 개선 될 것이라고 생각합니다.


저 또한 앞으로도 계속 변화하는 개발자가 되도록 노력하겠습니다. 감사합니다.



Posted by MVP kaki104

윈도우 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


Posted by MVP kaki104

Microsoft MVP 박문찬입니다. 날씨가 추워졌네요..오전에 보일러 고쳐서 참 다행인듯합니다. -0-;;

이전 포스트들을 진행 한 후에 이번 포스트를 보시면 좀 접근하는데 쉬울 것 같습니다.


Face API 사용해보기


Template10을 이용해서 UWP 앱 개발하기 Part1


MVVM Pattern에 대해서 자세히 알고 싶으시면 아래 내용을 참고하시면 됩니다.

Todo list Universal & UWP app


MVVM pattern 설명 동영상(오래전에 녹화한 내용입니다.)

http://youtu.be/f9aQkuoiPz4
http://youtu.be/uGxboAUwciI
http://youtu.be/2lQQiBEjbtU


MVVM Light Toolkit Fundamentals


삽잡이님 블로그

http://shovelman.tistory.com/695
http://shovelman.tistory.com/697
http://shovelman.tistory.com/705
http://shovelman.tistory.com/706



이전 포스트에서는 사진에서 얼굴 위치를 찾아오는 것 까지만 진행 했습니다. 이번에는 사진 속 인물이 누구인지, 학습을 시킨 후에 전혀 다른 사진을 이용해서 해당 사용자를 찾아내는 작업을 해 보도록 하겠습니다.



1. 참고

How to Identify Faces in Image



2. 인증 과정


1) groupId를 이용해서 그룹을 생성합니다. -> CreatePersonGroupAsync

2) 그룹에 사용자를 추가해서 personId를 반환 받습니다. -> CreatePersonAsync

3) groupId와 personId를 이용해서 이미지를 추가합니다.  -> AddPersonFaceAsync

4) 한 사람당 3장 이상의 이미지를 등록 후 머신 러닝을 실행합니다. -> GetPersonGroupTrainingStatusAsync

5) 인식을 원하는 이미지를 이용해서 DetectAsync를 실행합니다.

6) 찾아낸 FaceId를 모아서 인증을 수행합니다. -> IdentifyAsync

7) 인증된 사용자의 정보를 조회 합니다. -> GetPersonAsync


3. FaceIdentify 앱 만들기 시작


File -> New -> Project


Hambuger(Template 10)을 선택하고, FaceIdentify라고 입력하고 OK를 클릭합니다.



Build 후 비주얼 스튜디오를 종료 하고, 다시 시작합니다. (제사한 사항은 Template 10에 대한 포스트 참고)

F5를 눌러서 시작합니다.


햄버거 템플릿의 기본 화면입니다. 여기에 2개의 화면을 추가할 예정입니다. (아무래도 포스트가 2개 이상 필요할 듯하네요;;)





템플릿으로 프로젝트를 만들었는데 실행 했을 때 앱의 기본 골격이 보여지니 내가 필요한 것 몇개만 추가하면 바로 앱이 완성 됩니다. 물론, 솔루션 탐색기에 여러가지 파일들에 대해서 다 알면 좋겠지만, 몰라도 괜찮습니다. 딱 필요한 것만 알고 넘어 가도록 하겠습니다.



4. GroupPage 추가


그룹을 관리하고, 사람을 추가하고, 사진을 추가할 페이지를 추가하도록 하겠습니다.


솔루션 탐색기 Views 폴더에서 마우스 오른쪽 클릭 -> Add -> New Item...을 선택



GroupPage를 입력하고 Add를 클릭합니다.




GroupPage.xaml


<Page
    x:Class="FaceIdentify.Views.GroupPage"
    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:interactivity="using:Microsoft.Xaml.Interactivity"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
       
        <!--페이지 해더-->
        <controls:PageHeader x:Name="pageHeader" Text="Group Page">
            <controls:PageHeader.SecondaryCommands>
                <!--사람 삭제-->
                <AppBarButton Label="Remove Person" Command="{Binding RemovePersonCommand}"/>
            </controls:PageHeader.SecondaryCommands>
            <!--사람 추가-->
            <AppBarButton Icon="AddFriend" Label="Add Person" Command="{Binding AddPersonCommand}"/>
            <!--현재 선택된 사람에 사진 추가-->
            <AppBarButton Icon="Pictures" Label="Add Picture" Command="{Binding AddPictureCommand}"/>
            <!--트레이닝-->
            <AppBarButton Label="Train" Command="{Binding TrainCommand}">
                <AppBarButton.Icon>
                    <FontIcon Glyph="&#xE82E;"/>
                </AppBarButton.Icon>
            </AppBarButton>
        </controls:PageHeader>

        <Grid x:Name="InputName" Grid.Row="1" Margin="0 10 4 0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="Person" VerticalAlignment="Center"/>

            <!--기존 등록된 사람 목록 선택용 콤보 박스-->
            <ComboBox x:Name="comboBox" Grid.Column="1" HorizontalAlignment="Stretch" Margin="10,0,0,0"
                ItemsSource="{Binding Persons}" DisplayMemberPath="Name">
                <interactivity:Interaction.Behaviors>
                    <core:EventTriggerBehavior EventName="SelectionChanged">
                        <core:InvokeCommandAction Command="{Binding SelectionChangedPersonCommand}"
                                                  CommandParameter="{Binding SelectedItem, ElementName=comboBox}"/>
                    </core:EventTriggerBehavior>
                </interactivity:Interaction.Behaviors>
            </ComboBox>

            <!--추가할 사람 이름-->
            <TextBox Grid.Column="2" Margin="6 0 0 0"
                     Text="{Binding PersonName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        </Grid>

        <!--현재 선택된 사람-->
        <Grid x:Name="personName" Grid.Row="2" Margin="0 10 4 0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="Current Person"/>
            <TextBlock Grid.Column="1" Margin="6 0 0 0" Text="{Binding CurrentPerson.Name}"/>
            <TextBlock Grid.Column="2" Text="Reg Pics" Margin="6 0 0 0"/>
            <TextBlock Grid.Column="3" Margin="6 0 0 0"
                       Text="{Binding CurrentPerson.PersistedFaceIds.Length}"
                       HorizontalAlignment="Right"/>
        </Grid>

        <!--현재 선택된 사람에게 추가된 이미지(나중에 다시 보는 기능은 없습니다)-->
        <ListBox Grid.Row="3"
                 Margin="0 10 4 10"
                 ItemsSource="{Binding CurrentPerson.FaceImages}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding}" Stretch="UniformToFill"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>


XAML 코드 한줄 한줄이 어떻게 만들어 졌는지는..따로 설명을 드리기가 어렵겠네요..ㅜㅜ 지금 진행하고 있는 앱 개발 기초 과정에서 2주간에 걸쳐서 만들어진 코드랍니다.



5. Shell.xaml에 메뉴 추가하기


솔루션 탐색기에서 Shell.xaml 파일을 더블 클릭해서 디자인 화면을 열고, 30라인에 아래 내용을 붙여 넣기 하시면 됩니다. (라인 번호가 보이지 않으면, Tool -> Options -> Text Editor -> All Languages -> Line numbers에 체크를 해주시면 됩니다.)


            <Controls:HamburgerButtonInfo PageType="views:GroupPage">
                <StackPanel Orientation="Horizontal">
                    <SymbolIcon Width="48" Height="48"
                                Symbol="People" />
                    <TextBlock Margin="12,0,0,0" VerticalAlignment="Center"
                               Text="Group" />
                </StackPanel>
            </Controls:HamburgerButtonInfo>


실행해서 디자인 확인하기


다음과 같은 화면이 나오면 정상 입니다. 이제, 내용을 넣도록 하겠습니다.




6. FaceAPIHelper 추가하기

Nuget packages에서 Microsoft.ProjectOxford.Face를 선택해서 추가합니다. (이전 포스트에서 다루었던 내용입니다.)


Services 폴더를 선택하고 마우스 오른쪽, Add -> Class를 선택 -> FaceAPIHelper입력 -> OK


Input your service key에 여러분의 키를 입력하시면 됩니다. 역시 이전 포스트에서 다루는 내용입니다.


using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.ProjectOxford.Face;
using Microsoft.ProjectOxford.Face.Contract;

namespace FaceIdentify.Services
{
    public class FaceAPIHelper
    {
        private static FaceAPIHelper _instance;

        // Create an empty person group
        private readonly string _personGroupId = "myfriends";

        private readonly IFaceServiceClient faceServiceClient =
            new FaceServiceClient("Input your service key");

        private bool _init;

        /// <summary>
        ///     생성자
        /// </summary>
        public FaceAPIHelper()
        {
            Init();
        }

        /// <summary>
        ///     인스턴스
        /// </summary>
        public static FaceAPIHelper Instance
        {
            get { return _instance = _instance ?? new FaceAPIHelper(); }
        }

        /// <summary>
        ///     초기화
        /// </summary>
        public async void Init()
        {
            if (_init) return;

            //사람그룹 목록을 조회한 후에
            var list = await faceServiceClient.ListPersonGroupsAsync();
            //myFriends란 아이디를 가지고 있지 않다면, 사람그룹을 생성한다.
            if (list.All(p => p.PersonGroupId != _personGroupId))
            {
                await faceServiceClient.CreatePersonGroupAsync(_personGroupId, "My Friends");
            }

            _init = true;
        }

        /// <summary>
        ///     사진에서 얼굴 디텍트
        /// </summary>
        /// <param name="fileStream"></param>
        /// <returns></returns>
        public async Task<Face[]> GetDetectAsync(Stream fileStream)
        {
            return await faceServiceClient.DetectAsync(fileStream);
        }

        /// <summary>
        ///     사람 추가
        /// </summary>
        /// <param name="personName"></param>
        /// <returns></returns>
        public async Task<Guid> CreatePersonAsync(string personName)
        {
            var persons = await faceServiceClient.GetPersonsAsync(_personGroupId);
            if (persons.Any(p => p.Name == personName))
            {
                var per = persons.First(p => p.Name == personName);
                return per.PersonId;
            }
            var result = await faceServiceClient.CreatePersonAsync(_personGroupId, personName);
            return result.PersonId;
        }

        /// <summary>
        ///     사람 삭제
        /// </summary>
        /// <param name="personId"></param>
        /// <returns></returns>
        public async Task DeletePersonAsync(Guid personId)
        {
            await faceServiceClient.DeletePersonAsync(_personGroupId, personId);
        }

        /// <summary>
        ///     등록된 사람들 정보 조회
        /// </summary>
        /// <returns></returns>
        public async Task<Person[]> GetPersonsAsync()
        {
            var persons = await faceServiceClient.GetPersonsAsync(_personGroupId);
            return persons;
        }

        /// <summary>
        ///     사람에 얼굴 등록하기
        /// </summary>
        /// <param name="personId"></param>
        /// <param name="fileStream"></param>
        /// <returns></returns>
        public async Task<AddPersistedFaceResult> AddPersonFaceAsync(Guid personId, Stream fileStream)
        {
            try
            {
                // Detect faces in the image and add to Anna
                var result = await faceServiceClient.AddPersonFaceAsync(
                    _personGroupId, personId, fileStream);
                return result;
            }
            catch (FaceAPIException fae)
            {
                Debug.WriteLine(fae.ErrorMessage);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return null;
        }

        /// <summary>
        ///     트레이닝 사람그룹
        /// </summary>
        /// <returns></returns>
        public async Task TrainPersonGroupAsync()
        {
            await faceServiceClient.TrainPersonGroupAsync(_personGroupId);

            while (true)
            {
                var trainingStatus = await faceServiceClient.GetPersonGroupTrainingStatusAsync(_personGroupId);
                if (trainingStatus.Status != Status.Running)
                {
                    break;
                }
                await Task.Delay(1000);
            }
        }

        /// <summary>
        ///     인증하기
        /// </summary>
        /// <param name="faceIds"></param>
        /// <returns></returns>
        public async Task<IdentifyResult[]> IdentifyAsync(Guid[] faceIds)
        {
            try
            {
                var result = await faceServiceClient.IdentifyAsync(_personGroupId, faceIds);
                return result;
            }
            catch (FaceAPIException fae)
            {
                Debug.WriteLine(fae.ErrorMessage);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return null;
        }

        /// <summary>
        ///     사람 정보 조회
        /// </summary>
        /// <param name="personId"></param>
        /// <returns></returns>
        public async Task<Person> GetPersonAsync(Guid personId)
        {
            var result = await faceServiceClient.GetPersonAsync(_personGroupId, personId);
            return result;
        }
    }
}



7. PersonModel 추가


Models 폴더를 선택하고 마우스 오른쪽, Add -> Class를 선택 -> PersonModel 입력 후 OK


Face API에 있는 Person이라는 클래스를 상속 받아서 몇개의 프로퍼티를 추가하고, 기능을 보강한 모델을 만들어 사용합니다.


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>
        /// <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
    }
}



8. GroupPageViewModel.cs 추가하기


GroupPage.xaml을 추가했던 비슷한 방법으로, ViewModels 폴더를 선택하고 마우스 오른쪽, Add -> Class를 선택 -> GroupPageViewModel 입력 후 OK



using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Popups;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
using FaceIdentify.Models;
using FaceIdentify.Services;
using FaceIdentify.Views;
using Microsoft.ProjectOxford.Face.Contract;
using Template10.Mvvm;

namespace FaceIdentify.ViewModels
{
    public class GroupPageViewModel : ViewModelBase
    {
        private PersonModel _currentPerson;
        private string _personName;
        private IList<Person> _persons;

        public GroupPageViewModel()
        {
            if (DesignMode.DesignModeEnabled)
            {
                //디자인 타임 데이터
            }
            else
            {
                //런타임 데이터
                Init();
            }
        }

        /// <summary>
        ///     현재 작업 중인(추가한) 사용자
        /// </summary>
        public PersonModel CurrentPerson
        {
            get { return _currentPerson; }
            set { Set(ref _currentPerson, value); }
        }

        /// <summary>
        ///     등록된 사용자 목록
        /// </summary>
        public IList<Person> Persons
        {
            get { return _persons; }
            set { Set(ref _persons, value); }
        }

        /// <summary>
        ///     콤보박스 사용자가 선택되는 경우 실행되는 커맨드
        /// </summary>
        public ICommand SelectionChangedPersonCommand { get; set; }

        /// <summary>
        ///     트레이닝 커맨드
        /// </summary>
        public ICommand TrainCommand { get; set; }

        /// <summary>
        ///     사람 제거 커맨드
        /// </summary>
        public ICommand RemovePersonCommand { get; set; }

        /// <summary>
        ///     사람 추가
        /// </summary>
        public ICommand AddPersonCommand { get; set; }

        /// <summary>
        ///     사진 추가
        /// </summary>
        public ICommand AddPictureCommand { get; set; }

        /// <summary>
        ///     사람 이름
        /// </summary>
        public string PersonName
        {
            get { return _personName; }
            set { Set(ref _personName, value); }
        }

        /// <summary>
        ///     초기화
        /// </summary>
        private void Init()
        {
            //커맨드 생성
            AddPersonCommand = new DelegateCommand(AddPersonCommandHandler,
                () => !string.IsNullOrEmpty(PersonName));

            AddPictureCommand = new DelegateCommand(AddPictureCommandHandler);

            TrainCommand = new DelegateCommand(async () =>
            {
                //화면에 Busy 표시
                Busy.SetBusy(true, "Training....");
                await FaceAPIHelper.Instance.TrainPersonGroupAsync();
                Busy.SetBusy(false);
            });

            SelectionChangedPersonCommand = new DelegateCommand<object>(obj =>
            {
                //obj로 넘어온 데이터를 이용해서 현재 사람 표시
                var person = obj as Person;
                if (person == null) return;
                CurrentPerson = new PersonModel
                {
                    Name = person.Name,
                    PersonId = person.PersonId,
                    PersistedFaceIds = person.PersistedFaceIds
                };
            });

            RemovePersonCommand = new DelegateCommand(async () =>
            {
                if (CurrentPerson == null) return;

                Busy.SetBusy(true, "Removing...");
                await FaceAPIHelper.Instance.DeletePersonAsync(CurrentPerson.PersonId);

                Persons = await FaceAPIHelper.Instance.GetPersonsAsync();
                CurrentPerson = null;
                Busy.SetBusy(false);
            });

            PropertyChanged += (s, e) =>
            {
                switch (e.PropertyName)
                {
                    case "PersonName":
                        //커맨드 사용 가능 여부 확인
                        ((DelegateCommand) AddPersonCommand).RaiseCanExecuteChanged();
                        break;
                }
            };
        }

        private async void AddPictureCommandHandler()
        {
            //FileOpePicker 설정
            var openDlg = new FileOpenPicker();
            openDlg.FileTypeFilter.Add(".jpg");
            openDlg.FileTypeFilter.Add(".jpeg");
            openDlg.FileTypeFilter.Add(".png");
            //파일 선택 창 출력
            var file = await openDlg.PickSingleFileAsync();
            var basic = await file.GetBasicPropertiesAsync();
            //서버에 올릴 수 있는 이미지의 크기는 4MB까지입니다.
            if (basic.Size > 4*1024*1024)
            {
                var msg = new MessageDialog("It is too big image. It can only be up to 4MB.");
                await msg.ShowAsync();
                return;
            }
            //처음 화면에 출력용
            var bm = new BitmapImage();
            using (var stream = await file.OpenAsync(FileAccessMode.Read))
            {
                await bm.SetSourceAsync(stream);
            }
            //얼굴인식하고 테두리 표시하기
            using (var stream = await file.OpenStreamForReadAsync())
            {
                //얼굴 인식에 실패하는 경우 null을 반환합니다.
                var faceResult = await FaceAPIHelper.Instance.AddPersonFaceAsync(CurrentPerson.PersonId, stream);
                if (faceResult == null) return;

                CurrentPerson.AddFace(faceResult.PersistedFaceId, bm);
            }
        }


        /// <summary>
        ///     사람 추가하기
        /// </summary>
        private async void AddPersonCommandHandler()
        {
            if (string.IsNullOrEmpty(PersonName)) return;
            var personId = await FaceAPIHelper.Instance.CreatePersonAsync(PersonName);

            CurrentPerson = new PersonModel
            {
                Name = PersonName,
                PersonId = personId
            };
        }

        /// <summary>
        ///     Template 10의 기능으로 페이지 네비게이션을 통해서 페이지로 진입하면 실행되는 부분입니다.
        /// </summary>
        /// <param name="parameter"></param>
        /// <param name="mode"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode,
            IDictionary<string, object> state)
        {
            Persons = await FaceAPIHelper.Instance.GetPersonsAsync();
        }
    }
}



9. View와 ViewModel 연결


빌드를 한 후 GroupPage.xaml 디자인 화면으로 이동합니다.


네임스페이스를 추가하고 그 아래 Page.DataContrext에 뷰모델을 넣습니다.


참고로 이 방법은 디자인 타임과 런타임에 동일한 방식으로 뷰모델이 생성되는 방식입니다. 뷰모델을 따로 관리하지 않고, 페이지 네비게이션이 발생할 때마다 뷰모델도 생성되었다가 없어지는 구조입니다. 이런 경우 뷰모델 내부에서 저장해야할 데이터는 SessionState를 이용해서 관리합니다.


    xmlns:ViewModels="using:FaceIdentify.ViewModels"

    x:Class="FaceIdentify.Views.GroupPage"
    mc:Ignorable="d">


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



10. 실행


사람 이름을 입력하면 상단에 사람 추가 버튼이 활성화 됩니다.



정상적으로 추가가 되면 Current Person에 사람 이름이 표시 됩니다.



빨간 색의 사진 추가 버튼을 눌러서 사진을 추가합니다. 이때 사진은 단독 사진으로 하여야 합니다. (여러 사람 얼굴이 나오는 사진도 가능하기는 하지만, 현재 기능 구현이 되어있지 않습니다.)




그래서 저는 총4장을 추가했습니다. 아래는..추가한 사진들 입니다. 흐흐흐





마지막으로 트레이닝 버튼을 눌러서 학습을 시킵니다.



트레이닝은 금방 종료됩니다. ^^;;


그런데, 오늘은 트레이닝까지만... 진행 하겠습니다. 다음에 우주소녀 12명 중에 성소를 찾는 부분을 진행하도록 하겠습니다. -0-;;;; 시간이 늦어서말이죠..


후다닥 =3 =3



11. 소스 (파일이름 틀렸네요;; part2 소스 맞습니다.)


FaceIdentify_part1.zip


Posted by MVP kaki104

어느날 서피스 4 윈도우 업데이트를 봤는데.. 0%에서 더이상 진행이 되지 않더군요..

머..언젠간 되겠지..라고 넘어갔다가, 일주일 뒤에도 그 뒤에도 0%.... 디팬더 업데이트도 앙대고..;;쿨럭



그래서, 이렇게는 앙되겠다 싶어서 검색을 했습니다.


Windows Update stuck downloading updates in Windows 10


이런 내용이 있더군요 흐흐흐. 하지만, 초보는 역시 쉽지 않은 내용!! 그래서 포스트를 작성했습니다.



1. Command Prompt 창을 Admin으로 실행하기


왼쪽 하단, 윈도우 아이콘 오른쪽에 cmd라고 입력합니다.



Command Prompt라는 항목을 마우스 오른쪽 클릭을 한 후 Run as administrator를 선택해서 실행합니다.



그러면, 관리자 모드에서 실행하기 때문에 다시 한번 확인을 해주고 나면 아래와 같은 창이 나타납니다.


2. 서비스 중지하기


net stop wuauserv 라는 명령어를 입력 후 엔터


net stop bits 라는 명령어를 입력 후 엔터


저 같은 경우에는 처음에 입력한 명령어가 실패를 하고 다음줄에 명령어가 실행되면서 서비스를 중지 했었습니다.

아래 화면은 방금 찍은 것이라 예전과는 다른 화면이네요.. 머 일단 두가지 명령을 다 입력해서 서비스 중지를 하시면 됩니다.




3. C:\Windows\SoftwareDistribution 폴더의 내용 삭제하기


윈도우 탐색기를 이용해서 해당 폴더로 들어가신 후 모두 선택하고 삭제합니다.

사용중인 파일이 있는 경우에 삭제가 않될 수 있습니다. 그런 파일들은 skip으로 넘겨주세요.


사용중인 파일들도 삭제를 하기를 윈하시면, 재부팅하신 후에 다시 시도해 주시면 가능할 수 있습니다.

제 경우에는 재부팅해도 삭제 않되는 녀석들은 삭제가 않되더군요..




4. 서비스 다시 시작하기..


net start wuauserv


net start bits


명령을 입력해서 서비스를 다시 시작합니다.



5. 윈도우 업데이트 화면을 다시 확인 합니다.


아래와 같이 정상적으로 나온다면 Check for updates를 눌러서 업데이트를 진행하시면 됩니다.


제 경우에는 파일 삭제만 대충하고, 이 화면을 확인하니 업데이트가 가능한 상태라 바로 업데이트 진행했습니다. 서비스는 재부팅하면, 자동으로 실행이 되는 듯해서 따로 실행하지는 않았습니다.




좋아요 눌러 주실거죠? 흐흐;;;


우주소녀 성소 입니다. 캬캬



Posted by MVP kaki104

티스토리 툴바