Bagikan melalui


Mulai menggunakan model ONNX di aplikasi WinUI Anda dengan ONNX Runtime

Artikel ini memandikan Anda membuat aplikasi WinUI 3 yang menggunakan model ONNX untuk mengklasifikasikan objek dalam gambar dan menampilkan keyakinan setiap klasifikasi. Untuk informasi selengkapnya tentang menggunakan model AI dan pembelajaran mesin di aplikasi windows Anda, lihat Mulai menggunakan model AI dan Pembelajaran Mesin di aplikasi Windows Anda.

Apa itu runtime ONNX

ONNX Runtime adalah akselerator model pembelajaran mesin lintas platform, dengan antarmuka fleksibel untuk mengintegrasikan pustaka khusus perangkat keras. ONNX Runtime dapat digunakan dengan model dari PyTorch, Tensorflow/Keras, TFLite, scikit-learn, dan kerangka kerja lainnya. Untuk informasi selengkapnya, lihat ONNX Runtime situs web di https://onnxruntime.ai/docs/.

Sampel ini menggunakan DirectML Execution Provider abstrak mana dan berjalan di berbagai opsi perangkat keras pada perangkat Windows dan mendukung eksekusi di seluruh akselerator lokal, seperti GPU dan NPU.

Prasyarat

Membuat aplikasi C# WinUI baru

Di Visual Studio, buat proyek baru. Dalam dialog Buat proyek baru, atur filter bahasa ke "C#" dan filter jenis proyek ke "winui", lalu pilih templat Aplikasi kosong, Dipaketkan (WinUI3 di Desktop). Beri nama proyek baru "ONNXWinUIExample".

Menambahkan referensi ke paket Nuget

Di Penjelajah Solusi, klik kanan Dependensi dan pilih Kelola paket NuGet.... Di manajer paket NuGet, pilih tab Telusuri. Cari paket berikut dan untuk masing-masing paket, pilih versi stabil terbaru di menu drop-down Versi lalu klik Instal.

Paket Deskripsi
Microsoft.ML.OnnxRuntime.DirectML Menyediakan API untuk menjalankan model ONNX pada GPU.
SixLabors.ImageSharp Menyediakan utilitas gambar untuk memproses gambar untuk input model.
SharpDX.DXGI Menyediakan API untuk mengakses perangkat DirectX dari C#.

Tambahkan yang berikut menggunakan direktif ke bagian MainWindows.xaml.cs atas untuk mengakses API dari pustaka ini.

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

Menambahkan model ke proyek Anda

Di Penjelajah Solusi, klik kanan proyek Anda dan pilih Tambahkan> Folder Baru. Beri nama folder baru "model". Untuk contoh ini, kita akan menggunakan model resnet50-v2-7.onnx dari https://github.com/onnx/models. Buka tampilan repositori untuk model di https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Klik tombol *Unduh file mentah. Salin file ini ke direktori "model" yang baru saja Anda buat.

Di Penjelajah Solusi, klik file model dan atur Salin ke Direktori Output ke "Salin jika Lebih Baru".

Membuat UI sederhana

Untuk contoh ini, kita akan membuat UI sederhana yang menyertakan Tombol untuk memungkinkan pengguna memilih gambar untuk dievaluasi dengan model, kontrol Gambar untuk menampilkan gambar yang dipilih, dan TextBlock untuk mencantumkan objek yang dideteksi model dalam gambar dan keyakinan setiap klasifikasi objek.

MainWindow.xaml Dalam file, ganti elemen StackPanel default dengan kode XAML berikut.

<!--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>

Menginisialisasi model

MainWindow.xaml.cs Dalam file, di dalam kelas MainWindow, buat metode pembantu yang disebut InitModel yang akan menginisialisasi model. Metode ini menggunakan API dari pustaka SharpDX.DXGI untuk memilih adaptor pertama yang tersedia. Adaptor yang dipilih diatur dalam objek SessionOptions untuk penyedia eksekusi DirectML dalam sesi ini. Akhirnya, InferenceSession baru diinisialisasi, melewati jalur ke file model dan opsi sesi.

// 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);

}

Memuat dan menganalisis gambar

Untuk kesederhanaan, untuk contoh ini semua langkah untuk memuat dan memformat gambar, memanggil model, dan menampilkan hasilnya akan ditempatkan dalam handler klik tombol. Perhatikan bahwa kita menambahkan kata kunci asinkron ke handler klik tombol yang disertakan dalam templat default sehingga kita dapat menjalankan operasi asinkron di handler.

// MainWindow.xaml.cs

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

Gunakan FileOpenPicker untuk memungkinkan pengguna memilih gambar dari komputer mereka untuk menganalisis dan menampilkannya di 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;

Selanjutnya kita perlu memproses input untuk memasukkannya ke dalam format yang didukung oleh model. Pustaka SixLabors.ImageSharp digunakan untuk memuat gambar dalam format RGB 24-bit dan mengubah ukuran gambar menjadi 224x224 piksel. Kemudian nilai piksel dinormalisasi dinormalisasi dengan rata-rata 255*[0,485, 0,456, 0,406] dan simpang siur standar 255*[0,229, 0,224, 0,225]. Detail format yang diharapkan model dapat ditemukan di halaman github model 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];
            }
        }
    });

Selanjutnya, kami menyiapkan input dengan membuat jenis OrtValue of Tensor di atas array data gambar terkelola.

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

Selanjutnya, jika sesi inferensi belum diinisialisasi, panggil metode pembantu InitModel . Kemudian panggil metode Jalankan untuk menjalankan model dan mengambil hasilnya.

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

Model menghasilkan hasil sebagai buffer tensor asli. Kode berikut mengonversi output menjadi array float. Fungsi softmax diterapkan sehingga nilai terletak pada rentang [0,1] dan jumlah ke 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);

Indeks setiap nilai dalam array output dipetakan ke label tempat model dilatih, dan nilai pada indeks tersebut adalah keyakinan model bahwa label mewakili objek yang terdeteksi dalam gambar input. Kami memilih 10 hasil dengan nilai keyakinan tertinggi. Kode ini menggunakan beberapa objek pembantu yang akan kita tentukan di langkah berikutnya.

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

Mendeklarasikan objek pembantu

Kelas Prediksi hanya menyediakan cara sederhana untuk mengaitkan label objek dengan nilai keyakinan. Di MainPage.xaml.cs, tambahkan kelas ini di dalam blok namespace ONNXWinUIExample , tetapi di luar definisi kelas MainWindow .

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

Selanjutnya tambahkan kelas pembantu LabelMap yang mencantumkan semua label objek yang dilatih model, dalam urutan tertentu sehingga label memetakan ke indeks hasil yang dikembalikan oleh model. Daftar label terlalu panjang untuk disajikan secara lengkap di sini. Anda dapat menyalin kelas LabelMap lengkap dari file kode sampel di repositori github ONNXRuntime dan menempelkannya ke blok namespace ONNXWinUIExample.

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

Jalankan contoh

Buat dan jalankan proyek. Klik tombol Pilih foto dan pilih file gambar untuk dianalisis. Anda dapat melihat definisi kelas pembantu LabelMap untuk melihat hal-hal yang dapat dikenali model dan memilih gambar yang mungkin memiliki hasil yang menarik. Setelah model diinisialisasi, pertama kali dijalankan, dan setelah pemrosesan model selesai, Anda akan melihat daftar objek yang terdeteksi dalam gambar, dan nilai keyakinan setiap prediksi.

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

Lihat juga