Mulai menggunakan model ONNX di aplikasi WinUI Anda dengan ONNX Runtime
Artikel ini membimbing Anda membuat aplikasi WinUI 3 yang menggunakan model ONNX untuk mengklasifikasikan objek dalam gambar dan menampilkan tingkat kepercayaan dari setiap klasifikasi. Untuk informasi selengkapnya tentang menggunakan model AI dan pembelajaran mesin di aplikasi windows Anda, lihat Mulai menggunakan AI di Windows.
Saat menggunakan fitur AI, kami sarankan Anda meninjau: Mengembangkan Aplikasi dan Fitur AI Generatif yang Bertanggung Jawab di Windows.
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 situs web ONNX Runtime di https://onnxruntime.ai/docs/.
Sampel ini menggunakan DirectML Execution Provider yang mengabstraksi 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 lebih lanjut, lihat Aktifkan 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 Versi menu drop-down 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 berikut menggunakan perintah ke bagian atas MainWindows.xaml.cs
untuk mengakses API dari pustaka-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 memakai 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, kami 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 serta tingkat kepercayaan dari setiap klasifikasi objek.
Dalam file
<!--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
Dalam file MainWindow.xaml.cs
, di dalam kelas MainWindow, buat metode pembantu yang disebut InitModel yang akan menginisialisasi model. Metode ini menggunakan API dari pustaka
// 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 kami menambahkan kata kunci async ke penangan klik pada tombol yang disertakan dalam templat default, agar kita dapat menjalankan operasi asinkron di dalam 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 ortValue jenis 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 InitModel helper. 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 mengeluarkan hasil sebagai buffer tensor bawaan. Kode berikut mengonversi output menjadi array float. Fungsi softmax diterapkan sehingga nilai terletak dalam rentang [0, 1] dan berjumlah menjadi 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
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
Selanjutnya, tambahkan kelas LabelMap helper yang mencantumkan semua label objek yang digunakan dalam pelatihan model, dengan urutan tertentu agar label-label tersebut sesuai dengan indeks hasil yang dikembalikan oleh model. Daftar label terlalu panjang untuk disajikan secara lengkap di sini. Anda dapat menyalin kelas LabelMap
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
Jalankan contoh
Bangun dan jalankan proyek. Klik tombol Pilih foto dan pilih file gambar untuk dianalisis. Anda dapat melihat definisi kelas LabelMap helper untuk melihat hal-hal yang dapat dikenali model dan memilih gambar yang mungkin memiliki hasil 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