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
- Perangkat Anda harus mengaktifkan mode pengembang. Untuk informasi selengkapnya, lihat Mengaktifkan perangkat Anda untuk pengembangan.
- Visual Studio 2022 atau yang lebih baru dengan beban kerja pengembangan desktop .NET.
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