共用方式為


開始使用 WinUI 應用程式中的 ONNX 模型 ONNX Runtime

本文將逐步引導您建立 WinUI 3 應用程式,該應用程式會使用 ONNX 模型來分類影像中的物件,並顯示每個分類的信心。 如需在 Windows 應用程式中使用 AI 和機器學習模型的詳細資訊,請參閱在 Windows 應用程式中開始使用 AI 和 機器學習 模型。

什麼是 ONNX 運行時間

ONNX Runtime 是跨平台機器學習模型加速器,具有彈性介面來整合硬體特定連結庫。 ONNX Runtime 可以搭配來自 PyTorch、Tensorflow/Keras、TFLite、 scikit-learn和其他架構的模型使用。 如需詳細資訊,請參閱 ONNX Runtime 位於 https://onnxruntime.ai/docs/的網站。

此範例會使用 DirectML Execution Provider 在 Windows 裝置上跨不同硬體選項進行抽象化及執行,並支援跨本機加速器執行,例如 GPU 和 NPU。

必要條件

  • 您的裝置必須啟用開發人員模式。 如需詳細資訊,請參閱啟用您的裝置以用於開發
  • Visual Studio 2022 或更新版本搭配 .NET 桌面開發工作負載。

建立新的 C# WinUI 應用程式

在 Visual Studio 中,建立新專案。 在 [ 建立新專案 ] 對話框中,將語言篩選器設定為 “C#”,並將專案類型篩選設定為 “winui”,然後選取 [空白應用程式]、[封裝] (Desktop 中的 WinUI3) 範本。 將新專案命名為 「ONNXWinUIExample」。

新增 Nuget 套件的參考

在 [方案總管] 中,以滑鼠右鍵按兩下 [相依性],然後選取 [管理 NuGet 套件...]。在 NuGet 套件管理員中,選取 [流覽] 索引標籤。搜尋下列套件,並針對每個套件,選取 [版本] 下拉式清單中的最新穩定版本,然後按兩下 [安裝]。

Package 描述
Microsoft.ML.OnnxRuntime.DirectML 提供在 GPU 上執行 ONNX 模型的 API。
SixLabors.ImageSharp 提供用於處理模型輸入影像的影像公用程式。
SharpDX.DXGI 提供從 C# 存取 DirectX 裝置的 API。

將下列 using 指示詞新增至 頂端 MainWindows.xaml.cs ,以從這些鏈接庫存取 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/modelsresnet50-v2-7.onnx 模型。 移至位於 的模型 https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx存放庫檢視。 按兩下 [下載源檔] 按鈕。 將此檔案複製到您剛才建立的 「model」 目錄。

在 方案總管 中,按兩下模型檔案,並將 [複製到輸出目錄] 設定為 [如果更新時複製]。

建立簡單的 UI

在此範例中,我們將建立包含 Button 的簡單 UI,讓使用者能夠選取影像來評估模型、顯示所選影像的 Image 控件,以及一個 TextBlock,以列出影像中偵測到的物件,以及每個對象分類的信心。

在檔案中 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.xaml.cs,在MainWindow類別內,建立名為InitModel的協助程式方法,以初始化模型。 此方法會使用來自 SharpDX.DXGI 連結庫的 API 來選取第一個可用的配接器。 選取的配接器會設定在此 會話中 DirectML 執行提供者的 SessionOptions 物件中。 最後,會初始化新的 推斷Session ,並傳入模型檔案的路徑和會話選項。

// 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 格式載入影像,並將影像大小調整為 224x224 像素。 然後,將圖元值正規化,平均值為 255*[0.485、0.456、0.406] 和標準偏差 255*[0.229、0.224、0.225]。 模型預期格式的詳細數據可以在 resnet 模型的 github 頁面上找到。

    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];
            }
        }
    });

接下來,我們會在Managed影像數據數位頂端建立 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);

模型會將結果輸出為原生張量緩衝區。 下列程式代碼會將輸出轉換成 floats 陣列。 會套用 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 類別只是提供簡單的方法,讓物件標籤與信賴值產生關聯。 在 中,在 ONNXWinUIExample 命名空間區塊內新增此類別,但在 MainWindow 類別定義之外MainPage.xaml.cs

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

另請參閱