Windows Machine Learning を WPF から使って手書き数字認識をしてみよう

This post has been republished via RSS; it originally appeared at: New blog articles in Microsoft Tech Community.

Windows 10 API に Windows ML というのがあります。

Windows Machine Learning

 

UWP アプリにはサンプルがあって mnist のモデルを使って InkCanvas に書いた数字を認識しています。

https://github.com/Microsoft/Windows-Machine-Learning/tree/master/Samples/MNIST/UWP/cs

 

これを XAML Islands を使って .NET Framework の WPF アプリから手書き数字の認識をしてみようと思います。

 

プロジェクトの作成と設定

Visual Studio 2019 で WPF (.NET Framework) を作成します。

.NET Framework は 4.8 をターゲットにしました。

 

そして Microsoft.Toolkit.Wpf.UI.Controls の v6.0.0-preview5 を追加します。

XAML Islands を使えるようにするための設定をしていきます。

 

まず、プロジェクトにマニフェストファイルを追加して以下のように変更します。重要なのは OS のターゲットと DPI Aware の設定をします。

<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- Windows 10 -->
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
      <maxversiontested Id="10.0.18362.0"/>
    </application>
  </compatibility>

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
    </windowsSettings>
  </application>
</assembly>

そして、Program.cs を追加して Main メソッドで XAML Islands の初期化コードを追加します。

using Microsoft.Toolkit.Win32.UI.XamlHost;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindowsMLWPFSample
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            using (new XamlApplication())
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();
            }
        }
    }
}

プロジェクトのプロパティでスタートアップ オブジェクトに Program クラスを指定します。

コメント 2019-05-16 144531.jpg

UI を作っていこう

UWP の Grid を置いて認識結果を表示するための TextBlock を置きます。

<Window
    x:Class="WindowsMLWPFSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WindowsMLWPFSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <StackPanel>
        <xamlhost:WindowsXamlHost ChildChanged="WindowsXamlHost_ChildChanged" InitialTypeName="Windows.UI.Xaml.Controls.Grid" />
        <TextBlock x:Name="textBlock" FontSize="64" />
    </StackPanel>
</Window>

そして、コードビハインドに UI をくみたてる処理を追加します。

 

using Microsoft.Toolkit.Wpf.UI.XamlHost;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using Windows.AI.MachineLearning;

namespace WindowsMLWPFSample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private Windows.UI.Xaml.Controls.Grid _grid;

        private void WindowsXamlHost_ChildChanged(object sender, EventArgs e)
        {
            var xamlHost = (WindowsXamlHost)sender;
            _grid = xamlHost.Child as Windows.UI.Xaml.Controls.Grid;
            if (_grid == null)
            {
                return;
            }

            _grid.Background = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.Black);
            _grid.Width = 300;
            _grid.Height = 300;
            var inkCanvas = new Windows.UI.Xaml.Controls.InkCanvas();
            inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(new Windows.UI.Input.Inking.InkDrawingAttributes
            {
                Color = Windows.UI.Colors.White,
                Size = new Windows.Foundation.Size(22, 22),
            });
            inkCanvas.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected;
            _grid.Children.Add(inkCanvas);
        }

        private void InkPresenter_StrokesCollected(Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args)
        {
            // ここに認識処理を追加
        }
    }
}

Windows Machine Learning を使う処理を追加

 

Windows Machine Learning Code Generator 拡張機能を Visual Studio 2019 に追加します。そして、MNIST の ONNX ファイルを下記リポジトリーから取得します。バージョンは 1.3 を使用します。

https://github.com/onnx/models/tree/master/mnist

 

解凍して中に入っている onnx ファイルを Mnist.onnx にリネームして WPF アプリのプロジェクトに追加します。そうすると Mnist.cs というファイルが生成されます。生成されない場合は UWP アプリケーションのプロジェクトを作成して、そちらに ONNX ファイルを追加してコードをコピーしてください。

 

ONNX ファイルはコンテンツとして出力フォルダーにコピーされるようにプロパティを設定します。

Mnist.cs ファイルの MnistInput クラスの Input3 フィールドの型を ImageFeatureValue に変更します。

public sealed class MnistInput
{
    public ImageFeatureValue Input3; // shape(1,1,28,28)
}

そして、Grid を画像に変換する処理が書かれている Helper.cs ファイルを Windows ML のサンプルのリポジトリーからコピーしてプロジェクトに追加します。

 

そしてコードビハインドの StrokesCollected イベントハンドラーに Windows ML を使った認識処理を追加します。ONNX ファイルを読み込み初期化をして、Grid を画像にしてモデルに食わせます。結果はスコアのリストになります。インデックス 0 番目に数字の 0 のスコアが入っていてインデックス 9 まであります。つまり、最高のスコアのインデックスを求めることで認識結果を取得できます。

private MnistModel _model;

private async void InkPresenter_StrokesCollected(Windows.UI.Input.Inking.InkPresenter sender, Windows.UI.Input.Inking.InkStrokesCollectedEventArgs args)
{
    if (_model == null)
    {
        var mnistModelFilePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Mnist.onnx");
        var modelFile = await Windows.Storage.StorageFile.GetFileFromPathAsync(mnistModelFilePath);
        _model = await MnistModel.CreateFromStreamAsync(modelFile);
    }

    var visual = await new Helper().GetHandWrittenImage(_grid);
    var result = await _model.EvaluateAsync(new MnistInput
    {
        Input3 = ImageFeatureValue.CreateFromVideoFrame(visual),
    });
    var scores = result.Plus214_Output_0.GetAsVectorView().ToArray();
    var answer = Array.IndexOf(scores, scores.Max());
    textBlock.Text = $"Result: {answer}";
}

実行して動作確認

実行して動作確認をします。

mnist2.gif

ちゃんと動いています。

 

まとめ

Windows 10 の API に対して .NET などの既存アプリケーションからのアクセスが容易になっています。さらに XAML Islands を使うことで UWP のコントロールを簡単に既存のデスクトップアプリに追加できるようになっています。

 

今回利用した Windows ML の API を使うことで簡単にディープラーニングの結果のモデルをデスクトップアプリで使用できることがわかりました。

是非、皆さんも何か面白いディープラーニングのモデルがあったらアプリに組み込んだりしてみて遊んでみてください。

 

今回のソースコードは以下のリポジトリーに置いています。

https://github.com/runceel/WindowsMLWPFSample

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.