Rozpocznij pracę z modelami ONNX w aplikacji WinUI z ONNX Runtime
W tym artykule opisano proces tworzenia aplikacji WinUI 3, która używa modelu ONNX do klasyfikowania obiektów na obrazie i wyświetlania pewności każdej klasyfikacji. Aby uzyskać więcej informacji na temat używania modeli sztucznej inteligencji i uczenia maszynowego w aplikacji systemu Windows, zobacz Wprowadzenie do sztucznej inteligencji w systemie Windows.
Podczas korzystania z funkcji sztucznej inteligencji zalecamy przejrzenie: Opracowywanie odpowiedzialnych aplikacji i funkcji sztucznej inteligencji w systemie Windows.
Co to jest środowisko uruchomieniowe ONNX
ONNX Runtime to wieloplatformowy akcelerator modelu uczenia maszynowego z elastycznym interfejsem integrowania bibliotek specyficznych dla sprzętu. ONNX Runtime można używać z modelami z platform PyTorch, Tensorflow/Keras, TFLite, scikit-learni innych platform. Aby uzyskać więcej informacji, zobacz witrynę internetową ONNX Runtime pod adresem https://onnxruntime.ai/docs/.
W tym przykładzie użyto DirectML Execution Provider, który abstrahuje i działa w różnych opcjach sprzętowych na urządzeniach z systemem Windows, a także obsługuje wykonywanie w ramach lokalnych akceleratorów, takich jak GPU i NPU.
Warunki wstępne
- Urządzenie musi mieć włączony tryb dewelopera. Aby uzyskać więcej informacji, zobacz Włącz urządzenie do celów deweloperskich.
- Visual Studio 2022 lub nowszy z obciążeniem do tworzenia aplikacji desktopowych .NET.
Tworzenie nowej aplikacji WinUI w języku C#
W programie Visual Studio utwórz nowy projekt. W oknie dialogowym Tworzenie nowego projektu ustaw filtr języka na "C#" i filtr typu projektu na "winui", a następnie wybierz szablon Pusta aplikacja, Pakiet (WinUI3 w Desktop). Nadaj nazwę nowemu projektowi "ONNXWinUIExample".
Dodawanie odwołań do pakietów NuGet
W Eksploratorze rozwiązań kliknij prawym przyciskiem Zależności i wybierz pozycję Zarządzaj pakietami NuGet.... W menedżerze pakietów NuGet wybierz kartę Przeglądaj. Wyszukaj poniższe pakiety i dla każdego z nich wybierz najnowszą stabilną wersję z listy rozwijanej w sekcji Wersja, następnie kliknij Zainstaluj.
Pakiet | Opis |
---|---|
Microsoft.ML.OnnxRuntime.DirectML | Udostępnia interfejsy API do uruchamiania modeli ONNX na procesorze GPU. |
SixLabors.ImageSharp | Udostępnia narzędzia do przetwarzania obrazów na potrzeby danych wejściowych modelu. |
SharpDX.DXGI | Udostępnia interfejsy API umożliwiające uzyskiwanie dostępu do urządzenia DirectX z poziomu języka C#. |
Dodaj następujące za pomocą dyrektyw na początku MainWindows.xaml.cs
, aby uzyskać dostęp do interfejsów API tych bibliotek.
// 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;
Dodawanie modelu do projektu
W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy swój projekt i wybierz Dodaj ->Nowy folder. Nazwij nowy folder "model". W tym przykładzie użyjemy modelu resnet50-v2-7.onnx z https://github.com/onnx/models. Przejdź do widoku repozytorium dla modelu w https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Kliknij przycisk *Pobierz nieprzetworzone pliki. Skopiuj ten plik do właśnie utworzonego katalogu "model".
W Eksploratorze rozwiązań kliknij plik modelu i ustaw Kopiuj do katalogu wyjściowego na "Kopiuj, jeśli jest nowsze".
Tworzenie prostego interfejsu użytkownika
W tym przykładzie utworzymy prosty interfejs użytkownika zawierający Przycisk, aby umożliwić użytkownikowi wybranie obrazu do oceny za pomocą modelu, kontrolkę Image do pokazania wybranego obrazu, oraz kontrolkę TextBlock do wypisania obiektów, które model wykrył na obrazie, wraz z pewnością każdej klasyfikacji obiektu.
W pliku MainWindow.xaml
zastąp domyślny element StackPanel następującym kodem 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>
Inicjowanie modelu
W pliku MainWindow.xaml.cs
wewnątrz klasy MainWindow utwórz metodę pomocnika o nazwie InitModel, która zainicjuje model. Ta metoda używa interfejsów API z biblioteki SharpDX.DXGI, aby wybrać pierwszy dostępny adapter. Wybrany adapter jest ustawiony w obiekcie SessionOptions dla dostawcy wykonawczego DirectML w tej sesji. Na koniec zainicjowano nową InferenceSession, przekazując jako parametry ścieżkę do pliku modelu oraz opcje sesji.
// 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);
}
Ładowanie i analizowanie obrazu
Dla uproszczenia w tym przykładzie wszystkie kroki ładowania i formatowania obrazu, wywoływania modelu i wyświetlania wyników zostaną umieszczone w procedurze obsługi kliknięcia przycisku. Należy pamiętać, że dodajemy słowo kluczowe async do procedury obsługi kliknięcia przycisku zawartej w szablonie domyślnym, abyśmy mogli uruchamiać operacje asynchroniczne w programie obsługi.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
Użyj FileOpenPicker, aby umożliwić użytkownikowi wybranie obrazu z komputera w celu przeanalizowania i wyświetlenia go w interfejsie użytkownika.
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;
Następnie musimy przetworzyć dane wejściowe, aby uzyskać je w formacie obsługiwanym przez model. Biblioteka SixLabors.ImageSharp służy do ładowania obrazu w formacie 24-bitowego RGB i zmieniania rozmiaru obrazu na 224x224 pikseli. Następnie wartości pikseli są znormalizowane ze średnią 255*[0,485, 0,456, 0,406] i odchylenie standardowe 255*[0,229, 0,224, 0,225]. Szczegóły dotyczące formatu oczekiwanego przez model można znaleźć na stronie modelu ResNet na GitHubie .
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];
}
}
});
Następnie skonfigurujemy dane wejściowe, tworząc OrtValue typu Tensor na bazie tablicy danych zarządzanego obrazu.
// 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 }
};
Następnie, jeśli sesja wnioskowania nie została jeszcze zainicjowana, wywołaj metodę pomocnika InitModel. Następnie wywołaj metodę Run, aby uruchomić model i pobrać wyniki.
// Run inference
if (_inferenceSession == null)
{
InitModel();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
Model generuje wyniki jako natywny bufor tensorowy. Poniższy kod konwertuje dane wyjściowe na tablicę zmiennoprzecinkowych. Zastosowano funkcję softmax, tak aby wartości mieściły się w zakresie [0,1] i sumują się do 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 każdej wartości w tablicy danych wyjściowych jest mapowany na etykietę, na którą został wytrenowany model, a wartość w tym indeksie to pewność modelu, że etykieta reprezentuje obiekt wykryty na obrazie wejściowym. Wybieramy 10 wyników z najwyższą wartością ufności. Ten kod używa niektórych obiektów pomocnika, które zdefiniujemy w następnym kroku.
// 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
Deklarowanie obiektów pomocnika
Klasa przewidywania zapewnia prosty sposób skojarzenia etykiety obiektu z wartością pewności. W MainPage.xaml.cs
dodaj tę klasę wewnątrz ONNXWinUIExample bloku przestrzeni nazw, ale poza definicją klasy MainWindow.
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
Następnie dodaj klasę pomocnika LabelMap, w której wymieniono wszystkie etykiety obiektów, na których został wytrenowany model, w określonej kolejności, tak aby etykiety były mapowane na indeksy wyników zwracanych przez model. Lista etykiet jest za długa, aby przedstawić je w całości tutaj. Pełną klasę LabelMap można skopiować z przykładowego pliku kodu w repozytorium github ONNXRuntime i wkleić ją do bloku przestrzeni nazw ONNXWinUIExample.
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
Uruchamianie przykładu
Skompiluj i uruchom projekt. Kliknij przycisk Wybierz zdjęcie i wybierz plik obrazu do przeanalizowania. Możesz przyjrzeć się LabelMap definicji klasy pomocniczej, aby zobaczyć, co model może rozpoznać i wybrać obraz, który może mieć interesujące wyniki. Po zainicjowaniu modelu i jego pierwszym uruchomieniu, oraz po zakończeniu przetwarzania, powinna zostać wyświetlona lista obiektów wykrytych na obrazie oraz wartość pewności dla każdego przewidywania.
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