かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Windows Mixed Reality で普通の UWP を作成「何故か写真を撮らせない方法まで」

まぁ出来ますよね。

UWP アプリを新規作成して普通に作ってデプロイしちゃえばいいです。 めんどくさいのがデバッグです。そのままローカルコンピューターに対して実行するとデスクトップで立ち上がってくるんですよね…。なんかいい方法ないものだろうか。

デバッグ方法

Cliff House でスタートメニューからデバッグ対象のアプリケーションを立ち上げます。そしてデスクトップの世界に戻ってデバッガのアタッチをします。 これでデバッグが出来るようになります。

写真撮らせないようにしてみた

試しにこれをやってみました。写真ってスクリーンショットと同じ扱いなのかなぁと思って。

blog.okazuki.jp

ということで撮影してみたら真っ黒画面になりました!!スクリーンショットと同じなんですねぇ。

f:id:okazuki:20171020232129j:plain

DI コンテナの Unity の v5.0.0 がリリースされています!

ゲームエンジンじゃないほうの Unity がバージョンアップしてました!!

Added support for .NET 4.0, 4.5, 4.7, .NET Core 1.0+ and .NET Standard 1.0+ where available. ということなので .NET Standard 1.0+ というとても幅広い環境で使えるように仕上がっています。

github.com

www.nuget.org

あと、しれっと v4.x 系は End of life になってますね…。ふむ…。悩ましい。

New Surface Pro で Windows MR が動くまで(結構苦戦した)

ヘッドマウントディスプレイをさしても動かなかったんですよね…セットアップが終わってもこんな感じで。

チカチカチカチカしてます。しばらくするとこんなエラーになってます。

C0001160-101 が出ちゃうんですよねぇ。ぐぐっても情報なし。

Duet Diaplay との相性が悪かったです

iPad をサブディスプレイにするために Duet Diaplay をインストールしてたのですが、こいつがいるとこのエラーになるみたいです。停止だけではだめでアンインストールが必要でした。

アンインストールしたらOKでした。

2017/10/19 時点の情報なので Duet Diaplay のバージョンアップにより改善される可能性があります。

New Surface Pro で Windows 10 Fall Creators Update をあてて Mixed Reality ポータルを起動してみよう

まだヘッドマウントディスプレイ持ってません。はい。なので試せてませんが Windows 10 Fall Creators Update を当てた New Surface Pro で Mixed Reality ポータルをあてるとこうなりました。

f:id:okazuki:20171018113753p:plain

なんだと… WinMR が試せるから Surface Book から New Surface Pro にしたっていうのに!?

対処法

ということで、デバイスマネージャーからグラフィックドライバを更新しましょう。

f:id:okazuki:20171018113915p:plain

f:id:okazuki:20171018113931p:plain

そうすると無事に起動しました!やったね!!

f:id:okazuki:20171018113955p:plain

まとめ

ヘッドマウントディスプレイ買わなきゃ…。

一番簡単な DesktopBridge のやり方(Visual Studio 2017 15.4 の Windows アプリケーション パッケージ プロジェクト)

先日リリースされた Visual Studio 2017 15.4 で追加された Windows アプリケーション パッケージ プロジェクト を使うと手元に exe を出力する形式のプロジェクトがあって xcopy でインストールが終わるような類のアプリケーションを凄く簡単に appx 形式にパッケージングしてくれます!!

これはいい。これまでも Visual Studio を使って appx にパッケージングする方法とかはありましたが…

Visual Studio を使ったアプリのパッケージ化 (デスクトップ ブリッジ) - UWP app developer | Microsoft Docs

これは、楽っちゃぁ楽だけど、まぁ無理やり感というかなんというかという感じでした。

Windows アプリケーション パッケージ プロジェクト

そこで Windows アプリケーション パッケージ プロジェクトです。 こいつは、Windows ユニバーサルのプロジェクトテンプレートの中にひっそりと追加されています。

f:id:okazuki:20171013235142p:plain

プロジェクトを新規作成すると、こんな感じになります。

f:id:okazuki:20171014085412p:plain

あとは、同じソリューションに exe 形式を出力するプロジェクトを追加して、Windows アプリケーション パッケージ プロジェクトの中のアプリケーションに追加します。試しに Visual C++ の Win32 アプリケーションを追加してみました。

f:id:okazuki:20171014090337p:plain

あとは、ビルドして実行すると appx が作られてインストールされて起動してきます。(.NET のアプリだと大丈夫なのかもだけど、C++ なのでターゲットプラットフォームは x86 or x64 を選んでから実行)

ちゃんとデバッガがアタッチされてブレークポイントも止まります。素晴らしい。

f:id:okazuki:20171014090806p:plain

ストア関連の機能も…

ストアのアプリとの紐づけやパッケージの作成ができます。

f:id:okazuki:20171014091156p:plain

Windows アプリ 認定キット(WACK)ももちろんかけることができます。

f:id:okazuki:20171014091353p:plain

まとめ

インストーラーが不要なアプリケーションは大体いけそうな気がしますね。。

Microsoft Edge の iOS 版と Android 版が!?

なんかできてる。

blogs.windows.com

今朝、手元の Android の Arrows Launcher が Microsoft Lanucher に置き換わってたのですよ。まぁそれは置いといて、Microsoft Edge の iOS 版と Android 版が出るとな!?

これは、設定同期とかされると嬉しい。

個人的にはカスタマイズが嫌いなので、Android では Chrome, iOS では Safari, Windows では Edge を使ってたのですが、Edge に統一して便利なら乗り換えてみようかなぁ。

あ、ステータスはまだ Preview です。

Windows ストアへ自分の Win32 アプリを上げる Desktop Bridge

変更履歴

  • 2017/10/05 paint.net について追記
  • 2017/10/06 msi 形式のインストーラーという表記をインストーラーに変更(msi である必要はなかったので)

やってみよう!

WPF とかで作ったアプリは、そのままではストアから配布できません。 ストア対応したかったら UWP でアプリを作りましょう!!というのが一応基本スタンスです。 (UWP が現状流行ってるのかは置いておいて、Windows 10 ネイティブなアプリケーション開発は UWP でやるのが一応第一選択肢です)

まぁそうはいっても…という状況で活躍するのが Desktop Bridge ですね。ということで見てみましょう。

Desktop Bridge

ドキュメントがしっかりしてるので、そこを見るのが正です。

デスクトップ ブリッジ - UWP app developer | Microsoft Docs

まだ、にわかなので間違ってること書いてるかもしれませんが、登場当初は msi 形式のインストーラを appx に変換するぜ!みたいな雰囲気で登場したと思います。まぁでも、実態は AppxManifest.xml がちゃんとあって、exe を同梱して appx にパッケージングすればストアに上げることが出来るっぽいですね。

ちなみに、Desktop Bridge で変換したものをストアに上げたい!って思ってる人はここのフォームから申請するとプロセスがスタートします。

Desktop Bridge Sign Up Form - Windows app development

Desktop App Converter

Desktop App Converter を使うと、簡単にインストーラーや実行形式の exe を appx にパッケージングしてくれたり AppxManifest.xml を生成してくれます。便利なので、まずはこれを足掛かりにやってみるのがいいのかなと感じています。

セットアップは Desktop App Converter をストアから仕入れて自分の OS のバージョンとあった BaseImage をダウンロードしてきてコマンドを叩くという感じです。ここらへんに書いてあります。(BaseImage のダウンロードはインストーラーを Desktop App Converter で変換するときに使うのでインストーラーじゃない場合はいらないですね)

Desktop App Converter を使用してアプリをパッケージ化する (デスクトップ ブリッジ) - UWP app developer | Microsoft Docs

注意点としてアップデートをきちんとあてている Creators Update の Windows 10 を使ってると対応する BaseImage-15063 を入れるのではなく BaseImage-15063-UPDATE というのを入れないとダメってところですかね。

zip で配ってる感じのアプリを変換してみよう

つまり、よくあるフリーソフトみたいな感じのやつですね。 インストーラーがない形式です。zip をダウンロードして任意の場所に展開して、その中にある exe を起動する感じで使うアプリになります。

例えば DacApp フォルダ以下に DacApp.exe というファイルがあって、それを実行すれば動くアプリの場合は以下のようなコマンドで appx にできます。

DesktopAppConverter.exe -Installer .\DacApp -AppExecutable DacApp.exe -Destination c:\Work\dacsample-noinstaller\Output -PackageName "DacApp" -Publisher "CN=okazuki" -Version 0.0.0.1 -MakeAppx -Sign -Verify

パラメータの意味は以下を参照するといいです。

Desktop App Converter を使用してアプリをパッケージ化する (デスクトップ ブリッジ) - UWP app developer | Microsoft Docs

インストーラー形式のアプリを変換してみよう

とりあえず WPF のアプリを作って Microsoft Visual Stduio 2017 Instalelr Projects を入れてセットアッププロジェクトを作って msi 形式にパッケージングしてみました。別に msi 形式じゃなくてもサイレントインストール可能なら何でもいいです。

この Installer Projects で作った msi 形式のインストーラーは /qn 引数でサイレントインストールできるみたいなので、Desktop App Converter のマニュアルに従い以下のようなコマンドをぱちぱちと叩くことで appx にパッケージングして適当な証明書で署名してくれます。

DesktopAppConverter -Installer .\DacApp.Setup.msi -InstallerArguments "/qn" -Destination .\Output
-PackageName DacApp -Publisher "CN=okazuki" -Version 0.0.0.2 -MakeAppx -Sign -Verify

マニュアルはこちら。(再掲)

Desktop App Converter を使用してアプリをパッケージ化する (デスクトップ ブリッジ) - UWP app developer | Microsoft Docs

インストールして実行してみよう

こんな感じのものが Output フォルダの下に作られます。

f:id:okazuki:20171005224615p:plain

生成された appx はダブルクリックすることでさくっとインストールできそうな雰囲気を醸し出しつつ失敗します。これはローカル コンピューターの信頼されたルート証明機関に auto-generated.pfx をインストールしてやる必要があります。やですね。 インストールの過程でパスワードを求められるのですが、これは 123456 になります。

証明書をインストールしたら気を取り直して appx を起動するとこんな感じの画面が出ます。

f:id:okazuki:20171005224924p:plain

インストール後起動するとこんな感じ。

f:id:okazuki:20171005225043p:plain

いいね!!

再パッケージ

Desktop App Converter での変換は毎回しなくてもよくて、なんかインストール時に書き込むレジストリが変わっただとかいう影響のでかい変更しない限りは必要ないみたいです。

では、どうやって appx を作るのか?ですが makeappx.exe を使ってやります。

docs.microsoft.com

出力先として指定したフォルダに PackageFiles というフォルダが出来てるので、そこのファイルを更新したら makeappx.exe で appx に固めるといった感じです。makeappx.exe は以下のフォルダにあるのでパスを通しておくとスムーズかもしれません。

  • x86: C:\Program Files (x86)\Windows Kits\10\bin\x86\makeappx.exe
  • x64: C:\Program Files (x86)\Windows Kits\10\bin\x64\makeappx.exe

こんなコマンドで再パッケージングできます。

makeappx.exe pack /d .\PackageFiles /p DacApp.appx /l

appx が出来たらインストールできるように署名しましょう。署名はここにドキュメントがあります。

docs.microsoft.com

Desktop App Converter が生成してくれる auto-generated.pfx を使う場合は以下のようなコマンドで OK です。

signtool sign /fd SHA256 /a /f .\auto-generated.pfx /p 123456 .\DacApp.appx

Windows 10 S でのテストも忘れずに

これは Desktop Bridge の要件ではなく Windows ストアの要件なんですが Windows 10 S で動くことが必須みたいですね。

デスクトップ ブリッジ - UWP app developer | Microsoft Docs

Windows ストアにアプリを公開する場合は、Windows 10 S を実行するデバイスでアプリが正しく動作することを確認してください。これは、ストア要件です。 「Windows アプリの Windows 10 S 対応をテストする」をご覧ください。

Windows 10 S ですがサイドローディング禁止です。じゃぁどうやってテストするの?ということになりますが、Windows 10 Pro になんかポリシーみたいなのあてて疑似的にやる感じみたいですね。

ちなみに仮想マシンでやることを強くおすすめされてます。(まぁ普通のアプリインストールできなくなるしね…)

Test your Windows app for Windows 10 S - UWP app developer | Microsoft Docs

具多的にはダウンロードできる zip の SiPolicy_DevModeEx_Enforced.p7b を SiPolicy.p7b にリネームして C:\Windows\System32\CodeIntegrity において再起動するだけみたいですね。

そうすると、zip に同梱されてる AppxTestRootAgency.pfx で署名した appx がインストールできるようになるみたいです。署名するにあたり AppxManifest.xml の書き換えが必要です。

Identity の Publisher を CN=Appx Test Root Agency Ex にする必要があります。例えば私の場合 Desktop App Converter で okazuki としたので CN=okazuki と記載されています。

  <Identity Name="DacApp" ProcessorArchitecture="x64" Publisher="CN=okazuki" Version="0.0.0.1" />

こんな感じにします。

  <Identity Name="DacApp" ProcessorArchitecture="x64" Publisher="CN=Appx Test Root Agency Ex" Version="0.0.0.1" />

そしたら makeappx.exe で appx にパッケージングします。そして signtool で署名しておきます。

makeappx.exe pack /d .\PackageFiles /p DacApp.appx /l
signtool sign /fd SHA256 /a /f .\AppxTestRootAgency.pfx .\DacApp.appx

ポリシーあてた Windows 10 Pro でも無事インストールして起動できました。

f:id:okazuki:20171005233953p:plain

ちなみに、AppxTestRootAgency.pfx 以外の証明書で署名した appx を用意して pfx を信頼されたルート証明機関に追加するとインストールまでは成功しますが起動しようとすると以下のような感じになります。

f:id:okazuki:20171005234916p:plain

まとめ

とりあえず Desktop Bridge の入り口である変換の部分を紹介しました。Desktop Bridge 自体は秀丸エディタさんがストアで公開されたりとかされてて熱い感じですね。

秀まるおのホームページ(サイトー企画)−秀丸エディタ(ストアアプリ版)

長い目で見ると Pure な UWP なんでしょうが、とりあえず現実問題として exe 形式のアプリを抱えてる人は UWP 化へのとっかかりとして Desktop Bridge で appx にしてしまうのもありじゃないでしょうか。

あ、因みにもとが exe なので Windows.Desktop でしか動かないです。

追記

Paint.net もいつの間にかストアで配布されてた!!すごい!

blog.getpaint.net

Prism.Forms.Unity を .NET Standard プロジェクトにしてみよう(無理やり感)

Xamarin.Forms 2.4 が .NET Standard 2.0 化したこともあり .NET Standard の機運が高まってまいりました。 Prism のほうも 7.0.0 で .NET Standard 対応っぽいのでいい流れです。

最近のライブラリは .NET Standard 前提で作られてるものもチラホラ出てきてるので対応が進んでくれるのは嬉しいですね。

Prism.Forms のプロジェクトの .NET Standard への変換

ということで、現状の Prism Template Pack で新規作成されたプロジェクトとベースに .NET Standard にしてみようと思います。 まず、Prism Template Pack でプロジェクトを新規作成します(NETStdPrismApp という名前でここでは作りました)。現時点では Android, iOS をターゲットにします。何故かというと Xamarin.Forms が .NET Standard 2.0 を対象としているためです。UWP は Fall Creators Update 以降で .NET Standard 2.0 対応ということなので、現時点ではまだ使えないためです。しょんぼり。

新規作成したら、さっそく .NET Standard のクラスライブラリのプロジェクトを追加しましょう。NETStdPrismApp.NETStandard という感じの名前で作りました。Class1.cs をさくっと消して NETStdPrismApp.NETStandard のプロパティを開きます。ターゲットフレームワークを .NET Standard 2.0 にして規定の名前空間を NETStdPrismApp あたりにしておきます。

f:id:okazuki:20171003130613p:plain

移植可能のプロジェクトから packages.config 以外のファイルをまるっとコピーします。 そして、移植可能のプロジェクトを右クリックからアンロードしておきましょう(消すのはうまく動くようになってからのほうが精神衛生上いいので)Droid と iOS プロジェクトから .NET Standard のプロジェクトを参照に追加します。ついでに、移植可能のプロジェクトを Droid と iOS プロジェクトから削除しておきましょう。

ソリューションの NuGet パッケージの管理を開きます。開いたらプレリリースを含めるにチェックを入れて Prism.Unity.Forms v7.0.0-pre1 を選択して Droid, iOS, .NET Standard のプロジェクトに追加します。私はここで Visual Studio の再起動を求められたので再起動をしました。

NuGet パッケージマネージャーからプレリリースを含めるのチェックを外して Xamarin.Forms の最新版(現時点で 2.4.0.282 でした)を全プロジェクトに追加します。私はここでまた Visual Studio の再起動を求められました。

App.xaml, MainPage.xaml が EmbeddedResource として csproj ファイルに追加されてるのをさくっと消します。

  <ItemGroup>
    <EmbeddedResource Include="App.xaml">
      <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
    </EmbeddedResource>
    <EmbeddedResource Include="Views\MainPage.xaml">
      <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
    </EmbeddedResource>
  </ItemGroup>

あと、プロジェクトの下にあるファイルは一定のルールで勝手に追加してくれるみたいなので、MainPage.xaml.cs や App.xaml.cs に関する記述も消してOKっぽいですね。

ここで、やっとビルドが通るようになります。 実行すると…

f:id:okazuki:20171003143236p:plain

動いた!!やったね!

警告!!(未解決)

動いたけど警告が出てますね。 こういうのは .NET Portability Analyzer を使ってチェックすることが出来ます。

docs.microsoft.com

とりあえず exe をダウンロードして適当な場所に配置します。ソリューションエクスプローラを見る限りだと Unity と CommonServiceLocator が警告のおおもとなんですかね?C:\Users\[ユーザー名]\.nuget\packages\commonservicelocator\1.3.0\lib\portable-net4+sl5+netcore45+wpa81+wp8 あたりに dll があるので、そこで以下のコマンドを打ち込みました。

apiport.exe analyze -f Microsoft.Practices.ServiceLocation.dll
Microsoft (R) API Portability Analyzer v1.4.0.alpha.00199
Copyright (C) Microsoft Corporation. All rights reserved.

This tool analyzes .NET assemblies to determine possible problems moving between .NET platforms (such as
Windows Store, desktop, Mono, .NET Core, etc) as well as between .NET Framework versions (ie 4.x->4.y).

To learn more about how this tool works, including the data we are collecting,
go here - http://go.microsoft.com/fwlink/?LinkId=397652

Detecting assembly references                     [Done]
Analyzing compatibility                           [Done]
Writing Excel report                              [Done]

Wrote output to file:

Excel 形式のレポートが出てくるので見てみると大丈夫そうですね。

f:id:okazuki:20171003160612p:plain

Unity も同じ感じでレポートを出してみると、こっちはダメな感じっぽいですね。

f:id:okazuki:20171003161608p:plain

まぁ動いたし警告気にしないで無視るかと思って Prism.Unity.Forms の NoWarn の項目に NU1701 を入れたのに警告が消えない。ふむ。Prism.Unity.Forms じゃなくて、その依存先の警告は消してくれないのかな?消し方がわからん…。プロジェクトレベルで警告を無視することはできるみたいだけど、それはやりすぎ感ですしね。

python から Cognitive Services の API をたたいてみよう

Cognitive Services は、ある程度でしたら無料で試せます。

azure.microsoft.com

ここでは、Emotion API を試しにたたいてみようと思います。

API キーの取得

Emotion API の API キーの取得 ボタンを押します。 使用条件が表示されるので、よく読んで国を選択して、同意できる場合は同意して次へ進みます。

f:id:okazuki:20170920111740p:plain

次にサインインを求められます。これは Microsoft Account, Facebook, LinkedIn, Github が使用できます。

f:id:okazuki:20170920111832p:plain

私は Github を選択しました。 Github のサインイン画面が出てくるのでサインインしましょう。 サインインすると以下のように呼び出しに必要なエンドポイントとキーが表示されます。とりあえずキー1があれば問題ありません。

f:id:okazuki:20170920112143p:plain

呼んでみよう

基本こちらにやり方が書いてあります。

docs.microsoft.com

Python 2.7 で試してみてます。 とりあえず Emotion API のサンプルがインターネット上の画像の URL を送って結果を返すものだったので、ここではローカルの画像ファイルを読み込んで POST するものにしました。

import httplib
import urllib
import base64

headers = {
    'Content-Type': 'application/octet-stream',
    'Ocp-Apim-Subscription-Key': 'ポータルから取得したキー'
}

params = urllib.urlencode({

})

try:
    with open("emotionsample.jpeg", "rb") as f:
        conn = httplib.HTTPSConnection('westus.api.cognitive.microsoft.com')
        conn.request('POST', '/emotion/v1.0/recognize?%s' % params, f, headers)
        response = conn.getresponse()
        data = response.read()
        print(data)
        conn.close()
except Exception as e:
    print("[Errno {0}] {1}".format(e.errno, e.strerror))

実行すると以下のように JSON が返ってきます。

[
    {
        "faceRectangle": {
            "height": 162,
            "left": 130,
            "top": 141,
            "width": 162
        },
        "scores": {
            "anger": 9.29041E-06,
            "contempt": 0.000118981574,
            "disgust": 3.15619363E-05,
            "fear": 0.000589638,
            "happiness": 0.06630674,
            "neutral": 0.00555004273,
            "sadness": 7.44669524E-06,
            "surprise": 0.9273863
        }
    }
]

コードは以下の Github リポジトリに格納しています。

github.com

CoreML を Xamarin.Forms で使ってみよう

なんか iOS 11 から CoreML ってのが使えて簡単にいうと機械学習の学習結果を iOS ローカルで動かせるぜ!っていう感じのものらしいですね。強い。

ということで、Apple Developer と Xamarin のドキュメントを見ながら試してみたいと思います。

Core ML | Apple Developer Documentation

developer.xamarin.com

ちなみに、CoreML で使える学習結果のファイルは、Cognitive Services の Custo Vision API で作れるということなので、今回はこれも使ってみたいと思います。

azure.microsoft.com

Custom Vision API でいい感じの画像データを用意するのがめんどくさかったので、Drew さんの作ってくれた、このハンズオンにある画像をそのまま使いたいと思います。

github.com

customvision.ai

ではさくっと Custom Visoin を使えるようにしましょう。本題じゃないので注意点だけを。

Custom Vision のサイトでプロジェクトを作るときに General (compact) を使うことです。これをしないと CoreML で使えるファイルをエクスポートできません。

f:id:okazuki:20170919203915p:plain

あとは、Drew さんのリポジトリにある画像を適当に投げ込んで Fries / Not Fries のカテゴリを作って学習させます。

学習させたら下の青で囲ったエクスポートボタンを押します。

f:id:okazuki:20170919210929p:plain

こんな画面になるので、あとは支持に従うだけです。mlmodel という拡張子のファイルが取得できます。

f:id:okazuki:20170919211037p:plain

次の作業環境は Mac です。以下のコマンドをターミナルでうちます。

xcrun coremlcompiler compile さっきダウンロードしたファイル.mlmodel 出力先フォルダ

出来上がったファイルをどうにかして Windows にもっていきましょう。

Xamarin.iOS で頑張る

とりあえず、Xamarin.Forms でプロジェクトを作って iOS プロジェクトだけで動くように作りたいと思います。先ほどのコマンドでできた一連のファイルを iOS プロジェクトの Resources フォルダに移動させます。

f:id:okazuki:20170919223451p:plain

ClreML を使って認識をしてくれるであろう処理のためのインターフェースを PCL プロジェクトに作ります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CoreMLLabApp
{
    public interface IFriesOrNotFriesService
    {
        Task<string> DetectAsync(byte[] image);
    }
}

そして、MainPage.xaml を以下のような感じにします。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:CoreMLLabApp"
             x:Class="CoreMLLabApp.MainPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness">
            <On Platform="iOS">0,20,0,0</On>
        </OnPlatform>
    </ContentPage.Padding>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        
        <Button Text="Take picture"
            Clicked="Button_Clicked" />

        <Image x:Name="image"
               HorizontalOptions="Fill"
               VerticalOptions="Fill"
               Grid.Row="1" />
    </Grid>
</ContentPage>

そして、カメラから画像をとりたいので Xam.Plugin.Media を導入して表示される readme.txt の内容に従って info.plist に設定を追加したら MainPage.xaml.cs を以下のようにします。

using Plugin.Media;
using Plugin.Media.Abstractions;
using System;
using System.IO;
using Xamarin.Forms;

namespace CoreMLLabApp
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private async void Button_Clicked(object sender, EventArgs e)
        {
            await CrossMedia.Current.Initialize();

            var file = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions());
            if (file == null) { return; }

            this.image.Source = ImageSource.FromStream(() => file.GetStream());

            using (var fs = file.GetStream())
            using (var ms = new MemoryStream())
            {
                await fs.CopyToAsync(ms);
                var d = DependencyService.Get<IFriesOrNotFriesService>();
                var result = await d.DetectAsync(ms.ToArray());
                await this.DisplayAlert("Result", result, "OK");
            }
        }
    }
}

ここまでは、ただの Xamarin.Forms ですね。

CoreML を使ってみよう

では、iOS プロジェクトに FriesOrNotFriesService.cs を追加して処理を書いていきます。サンプルプロジェクトとかを参考に以下のように書いてみました。

using System;
using System.Linq;
using System.Threading.Tasks;
using Foundation;
using Vision;
using CoreML;
using CoreImage;
using CoreFoundation;

[assembly: Xamarin.Forms.Dependency(typeof(CoreMLLabApp.iOS.FriesOrNotFriesService))]
namespace CoreMLLabApp.iOS
{
    public class FriesOrNotFriesService : IFriesOrNotFriesService
    {
        private static VNCoreMLModel VModel { get; }

        static FriesOrNotFriesService()
        {
            // Load the ML model
            var assetPath = NSBundle.MainBundle.GetUrlForResource("e3e4e645c0944c6ca84f9a000e501b22", "mlmodelc");
            var friedOrNotFriedModel = MLModel.Create(assetPath, out _);
            VModel = VNCoreMLModel.FromMLModel(friedOrNotFriedModel, out _);
        }

        public Task<string> DetectAsync(byte[] image)
        {
            var taskSource = new TaskCompletionSource<string>();
            void handleClassification(VNRequest request, NSError error)
            {
                var observations = request.GetResults<VNClassificationObservation>();
                if (observations == null)
                {
                    taskSource.SetException(new Exception("Unexpected result type from VNCoreMLRequest"));
                    return;
                }

                if (observations.Length == 0)
                {
                    taskSource.SetResult(null);
                    return;
                }

                var best = observations.First();
                taskSource.SetResult(best.Identifier);
            }

            using (var data = NSData.FromArray(image))
            {
                var ciImage = new CIImage(data);
                var handler = new VNImageRequestHandler(ciImage, new VNImageOptions());
                DispatchQueue.DefaultGlobalQueue.DispatchAsync(() =>
                {
                    handler.Perform(new VNRequest[] { new VNCoreMLRequest(VModel, handleClassification) }, out _);
                });
            }

            return taskSource.Task;
        }

    }
}

byte[] から CoreML の入力として渡すための CIImage に変換して認識処理を呼び出しています。

動かして動作確認

ふむ。とりあえず動くっぽい。

f:id:okazuki:20170919224454p:plain

f:id:okazuki:20170919224507p:plain

あとは、DependencyService で Android や UWP の時には、CustomVision の API をたたくように仕込めば iOS だけ CoreML で他は REST API みたいなことが出来ますね。

ソースコードは以下に置いています。

github.com