次の方法で共有


ONNX Runtime を使用して WinUI アプリで ONNX モデルの使用を開始する

この記事では、ONNX モデルを使用して画像内のオブジェクトを分類し、各分類の信頼度を表示する WinUI 3 アプリを作成する手順について説明します。 Windows アプリで AI モデルと機械学習モデルを使用する方法の詳細については、「Windows アプリで AI モデルと Machine Learning モデルの使用を開始する」を参照してください。

AI 機能を利用する場合は、「責任を持って生成的 AI アプリケーションと機能を開発するためのガイドライン 」および「Windowsでの推奨事項」を確認することをお勧めします。

ONNX ランタイムとは

ONNX Runtime は、ハードウェア固有のライブラリを統合するための柔軟なインターフェイスを備えたクロスプラットフォーム機械学習モデル アクセラレータです。 ONNX Runtime は、PyTorch、Tensorflow/Keras、TFLite、scikit-learn、およびその他のフレームワークのモデルで使用できます。 詳細については、https://onnxruntime.ai/docs/の ONNX Runtime Web サイトを参照してください。

このサンプルでは、Windows デバイス上のさまざまなハードウェア オプションを抽象化して実行し、GPU や NPU などのローカル アクセラレータ間での実行をサポートする DirectML Execution Provider を使用します。

前提 条件

  • デバイスで開発者モードが有効になっている必要があります。 詳細については、「開発用にデバイスを有効化」を参照してください。
  • Visual Studio 2022 以降と .NET デスクトップ開発ワークロードが必要です。

新しい C# WinUI アプリを作成する

Visual Studio で、新しいプロジェクトを作成します。 [新しいプロジェクト の作成] ダイアログで、言語フィルターを "C#" に設定し、プロジェクトの種類フィルターを "winui" に設定し、空のアプリ 、パッケージ (デスクトップの WinUI3) テンプレートを選択します。 新しいプロジェクトに "ONNXWinUIExample" という名前を付けます。

Nuget パッケージへの参照を追加する

ソリューション エクスプローラーで、[依存関係] を右クリックし、[NuGet パッケージの管理] を選択します。NuGet パッケージ マネージャーで、[参照] タブを選択します。以下のパッケージを検索し、それぞれについて [バージョン] ドロップダウンで最新の安定バージョンを選択して [インストール] をクリックします。

Package 説明
Microsoft.ML.OnnxRuntime.DirectML GPU で ONNX モデルを実行するための API を提供します。
SixLabors.ImageSharp モデル入力のイメージを処理するためのイメージ ユーティリティを提供します。
SharpDX.DXGI C# から DirectX デバイスにアクセスするための API を提供します。

ディレクティブを使用する次の の先頭に追加して、これらのライブラリから API にアクセスします。

// MainWindow.xaml.cs
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using SharpDX.DXGI;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

プロジェクトにモデルを追加する

ソリューション エクスプローラーで、プロジェクトを右クリックし、[追加][新しいフォルダー]選択します。 新しいフォルダーに "model" という名前を付けます。 この例では、https://github.com/onnx/modelsから resnet50-v2-7.onnx モデルを使用します。 https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnxのモデルのリポジトリ ビューに移動します。 [*][未加工ファイル のダウンロード] ボタンをクリックします。 このファイルを、先ほど作成した "model" ディレクトリにコピーします。

ソリューション エクスプローラーでモデル ファイルをクリックし、[出力ディレクトリにコピー] を [新しい場合はコピー] に設定します。

シンプルな UI を作成する

この例では、ユーザーがモデルで評価するイメージを選択できるようにする Button、選択したイメージを表示する Image コントロール、および画像で検出されたオブジェクトと各オブジェクト分類の信頼度を一覧表示する TextBlock を含む単純な UI を作成します。

MainWindow.xaml ファイルで、既定の StackPanel 要素を次の XAML コードに置き換えます。

<!--MainWindow.xaml-->
<Grid Padding="25" >
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button x:Name="myButton" Click="myButton_Click" Grid.Column="0" VerticalAlignment="Top">Select photo</Button>
    <Image x:Name="myImage" MaxWidth="300" Grid.Column="1" VerticalAlignment="Top"/>
    <TextBlock x:Name="featuresTextBlock" Grid.Column="2" VerticalAlignment="Top"/>
</Grid>

モデルを初期化する

ファイルの MainWindow クラス内に、モデルを初期化する InitModel というヘルパー メソッドを作成します。 このメソッドは、SharpDX.DXGI ライブラリ の API を使用して、使用可能な最初のアダプターを選択します。 選択したアダプターは、このセッションの DirectML 実行プロバイダーの SessionOptions オブジェクトに設定されます。 最後に、新しい InferenceSession が初期化され、モデル ファイルへのパスとセッション オプションが渡されます。

// MainWindow.xaml.cs

private InferenceSession _inferenceSession;
private string modelDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "model");

private void InitModel()
{
    if (_inferenceSession != null)
    {
        return;
    }

    // Select a graphics device
    var factory1 = new Factory1();
    int deviceId = 0;

    Adapter1 selectedAdapter = factory1.GetAdapter1(0);

    // Create the inference session
    var sessionOptions = new SessionOptions
    {
        LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_INFO
    };
    sessionOptions.AppendExecutionProvider_DML(deviceId);
    _inferenceSession = new InferenceSession($@"{modelDir}\resnet50-v2-7.onnx", sessionOptions);

}

画像を読み込んで分析する

わかりやすくするために、この例では、イメージの読み込みと書式設定、モデルの呼び出し、結果の表示に関するすべての手順がボタン クリック ハンドラー内に配置されます。 既定のテンプレートに含まれるボタン クリック ハンドラーに async キーワードを追加して、ハンドラーで非同期操作を実行できることに注意してください。

// MainWindow.xaml.cs

private async void myButton_Click(object sender, RoutedEventArgs e)
{
    ...
}

FileOpenPicker を使用して、ユーザーがコンピューターから画像を選択して分析し、UI に表示できるようにします。

    FileOpenPicker fileOpenPicker = new()
    {
        ViewMode = PickerViewMode.Thumbnail,
        FileTypeFilter = { ".jpg", ".jpeg", ".png", ".gif" },
    };
    InitializeWithWindow.Initialize(fileOpenPicker, WinRT.Interop.WindowNative.GetWindowHandle(this));
    StorageFile file = await fileOpenPicker.PickSingleFileAsync();
    if (file == null)
    {
        return;
    }

    // Display the image in the UI
    var bitmap = new BitmapImage();
    bitmap.SetSource(await file.OpenAsync(Windows.Storage.FileAccessMode.Read));
    myImage.Source = bitmap;

次に、入力を処理して、モデルでサポートされている形式に取得する必要があります。 SixLabors.ImageSharp ライブラリは、24 ビット RGB 形式で画像を読み込み、画像のサイズを 224 x 224 ピクセルに変更するために使用されます。 次に、ピクセル値は平均 255*[0.485, 0.456, 0.406] と標準偏差 255*[0.229, 0.224, 0.225]で正規化されます。 モデルで想定される形式の詳細については、github ページの resnet モデルを参照してください。

    using var fileStream = await file.OpenStreamForReadAsync();

    IImageFormat format = SixLabors.ImageSharp.Image.DetectFormat(fileStream);
    using Image<Rgb24> image = SixLabors.ImageSharp.Image.Load<Rgb24>(fileStream);


    // Resize image
    using Stream imageStream = new MemoryStream();
    image.Mutate(x =>
    {
        x.Resize(new ResizeOptions
        {
            Size = new SixLabors.ImageSharp.Size(224, 224),
            Mode = ResizeMode.Crop
        });
    });

    image.Save(imageStream, format);

    // Preprocess image
    // We use DenseTensor for multi-dimensional access to populate the image data
    var mean = new[] { 0.485f, 0.456f, 0.406f };
    var stddev = new[] { 0.229f, 0.224f, 0.225f };
    DenseTensor<float> processedImage = new(new[] { 1, 3, 224, 224 });
    image.ProcessPixelRows(accessor =>
    {
        for (int y = 0; y < accessor.Height; y++)
        {
            Span<Rgb24> pixelSpan = accessor.GetRowSpan(y);
            for (int x = 0; x < accessor.Width; x++)
            {
                processedImage[0, 0, y, x] = ((pixelSpan[x].R / 255f) - mean[0]) / stddev[0];
                processedImage[0, 1, y, x] = ((pixelSpan[x].G / 255f) - mean[1]) / stddev[1];
                processedImage[0, 2, y, x] = ((pixelSpan[x].B / 255f) - mean[2]) / stddev[2];
            }
        }
    });

次に、マネージド イメージ データ配列の上に Tensor 型の OrtValue を作成して、入力を設定します。

    // Setup inputs
    // Pin tensor buffer and create a OrtValue with native tensor that makes use of
    // DenseTensor buffer directly. This avoids extra data copy within OnnxRuntime.
    // It will be unpinned on ortValue disposal
    using var inputOrtValue = OrtValue.CreateTensorValueFromMemory(OrtMemoryInfo.DefaultInstance,
        processedImage.Buffer, new long[] { 1, 3, 224, 224 });

    var inputs = new Dictionary<string, OrtValue>
    {
        { "data", inputOrtValue }
    };

次に、推論セッションがまだ初期化されていない場合は、InitModel ヘルパー メソッド 呼び出します。 次に、Run メソッドを呼び出してモデルを実行し、結果を取得します。

    // Run inference
    if (_inferenceSession == null)
    {
        InitModel();
    }
    using var runOptions = new RunOptions();
    using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);

モデルは、結果をネイティブ テンソル バッファーとして出力します。 次のコードは、出力を float の配列に変換します。 softmax 関数が適用され、値が [0,1] の範囲にあり、合計が 1 になるようにします。

    // Postprocess output
    // We copy results to array only to apply algorithms, otherwise data can be accessed directly
    // from the native buffer via ReadOnlySpan<T> or Span<T>
    var output = results[0].GetTensorDataAsSpan<float>().ToArray();
    float sum = output.Sum(x => (float)Math.Exp(x));
    IEnumerable<float> softmax = output.Select(x => (float)Math.Exp(x) / sum);

出力配列内の各値のインデックスは、モデルがトレーニングされたラベルにマップされ、そのインデックスの値は、入力画像で検出されたオブジェクトをラベルが表すモデルの信頼度です。 信頼度が最も高い 10 個の結果を選択します。 このコードでは、次の手順で定義するいくつかのヘルパー オブジェクトを使用します。

    // Extract top 10
    IEnumerable<Prediction> top10 = softmax.Select((x, i) => new Prediction { Label = LabelMap.Labels[i], Confidence = x })
        .OrderByDescending(x => x.Confidence)
        .Take(10);

    // Print results
    featuresTextBlock.Text = "Top 10 predictions for ResNet50 v2...\n";
    featuresTextBlock.Text += "-------------------------------------\n";
    foreach (var t in top10)
    {
        featuresTextBlock.Text += $"Label: {t.Label}, Confidence: {t.Confidence}\n";
    }
} // End of myButton_Click

ヘルパー オブジェクトを宣言する

Prediction クラスは、オブジェクト ラベルを信頼度値に関連付ける簡単な方法を提供するだけです。 MainPage.xaml.csで、このクラスを ONNXWinUIExample 名前空間ブロック内に追加しますが、MainWindow クラス定義の外部に追加します。

internal class Prediction
{
    public object Label { get; set; }
    public float Confidence { get; set; }
}

次に、LabelMap ヘルパー クラスを追加します。このヘルパー クラスには、モデルがトレーニングしたすべてのオブジェクト ラベルが特定の順序で一覧表示され、ラベルがモデルによって返された結果のインデックスにマップされます。 そのため、ラベルの一覧が長すぎてここに完全に表示することはできません。 ONNXRuntime github リポジトリのサンプル コード ファイルから完全な LabelMap クラスを コピーし、ONNXWinUIExample 名前空間ブロックに貼り付けることができます。

public class LabelMap
{
    public static readonly string[] Labels = new[] {
        "tench",
        "goldfish",
        "great white shark",
        ...
        "hen-of-the-woods",
        "bolete",
        "ear",
        "toilet paper"};

例を実行する

プロジェクトをビルドして実行します。 [写真 の選択] ボタンをクリックし、分析する画像ファイルを選択します。 LabelMap ヘルパー クラスの定義を確認して、モデルが認識できる内容を確認し、興味深い結果を得ることができる画像を選択できます。 モデルが初期化された後、初めて実行され、モデルの処理が完了すると、画像で検出されたオブジェクトの一覧と、各予測の信頼度値が表示されます。

Top 10 predictions for ResNet50 v2...
-------------------------------------
Label: lakeshore, Confidence: 0.91674984
Label: seashore, Confidence: 0.033412453
Label: promontory, Confidence: 0.008877817
Label: shoal, Confidence: 0.0046836217
Label: container ship, Confidence: 0.001940886
Label: Lakeland Terrier, Confidence: 0.0016400366
Label: maze, Confidence: 0.0012478716
Label: breakwater, Confidence: 0.0012336193
Label: ocean liner, Confidence: 0.0011933135
Label: pier, Confidence: 0.0011284945

関連項目