Erste Schritte mit ONNX-Modellen in Ihrer WinUI-App mit ONNX Runtime
Dieser Artikel führt Sie durch das Erstellen einer WinUI 3-App, die ein ONNX-Modell verwendet, um Objekte in einem Bild zu klassifizieren und die Zuverlässigkeit der einzelnen Klassifizierungen anzuzeigen. Weitere Informationen zur Verwendung von KI- und Machine Learning-Modellen in Ihrer Windows-App finden Sie unter Erste Schritte mit KI- und Machine Learning-Modellen in Ihrer Windows-App.
Was ist die ONNX-Laufzeit?
ONNX Runtime ist ein plattformübergreifender Machine Learning-Modellbeschleuniger mit einer flexiblen Schnittstelle zur Integration hardwarespezifischer Bibliotheken. ONNX Runtime kann mit Modellen von PyTorch, Tensorflow/Keras, TFLite, scikit-learn und anderen Frameworks verwendet werden. Weitere Informationen findest du auf der ONNX Runtime Website unter https://onnxruntime.ai/docs/.
In diesem Beispiel wird der DirectML Execution Provider verwendet, der die verschiedenen Hardwareoptionen auf Windows-Geräten abstrahiert und darüber ausgeführt wird und die Ausführung über lokale Beschleuniger wie GPU und NPU unterstützt.
Voraussetzungen
- Auf Ihrem Gerät muss der Entwicklermodus aktiviert sein. Weitere Informationen finden Sie unter Aktivieren von Geräten für die Entwicklung.
- Visual Studio 2022 oder höher mit der Workload .NET Desktop Development.
Erstellen einer neuen C#-WinUI-App
Erstellen Sie in Visual Studio ein neues Projekt. Legen Sie im Dialogfeld Neues Projekt erstellen den Sprachfilter auf „C++“ und den Projekttypfilter auf „winui“ fest, und wählen Sie dann die Vorlage Leere App, Verpackt (WinUI3 in Desktop) aus. Nennen Sie das neue Projekt „ONNXWinUIExample“.
Hinzufügen von Verweisen auf NuGet-Pakete
Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf Abhängigkeiten, und wählen Sie NuGet-Pakete verwalten aus. Wählen Sie im NuGet-Paket-Manager die Registerkarte Durchsuchen aus. Suchen Sie nach den folgenden Paketen, und wählen Sie für jedes die neueste stabile Version in der Dropdownliste Version aus, und klicken Sie dann auf Installieren.
Paket | Beschreibung |
---|---|
Microsoft.ML.OnnxRuntime.DirectML | Stellt APIs zum Ausführen von ONNX-Modellen auf der GPU bereit. |
SixLabors.ImageSharp | Stellt Bildhilfsprogramme zum Verarbeiten von Bildern für die Modelleingabe bereit. |
SharpDX.DXGI | Stellt APIs für den Zugriff auf das DirectX-Gerät über C# bereit. |
Fügen Sie über MainWindows.xaml.cs
die folgenden using-Direktiven hinzu, um auf die APIs in diesen Bibliotheken zuzugreifen.
// 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;
Hinzufügen des Modells zu Ihrem Projekt
Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf Ihr Projekt, und wählen Sie Hinzufügen>Neuer Ordner aus. Nennen Sie den neuen Ordner „Modell“. In diesem Beispiel verwenden wir das Modell resnet50-v2-7.onnx von https://github.com/onnx/models. Wechseln Sie zur Repositoryansicht für das Modell unter https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Klicken Sie auf die Schaltfläche Rohdatendatei herunterladen. Kopieren Sie diese Datei in das soeben erstellte Verzeichnis mit dem Namen „Modell“.
Klicken Sie in Projektmappen-Explorer auf die Modelldatei, und legen Sie In Ausgabeverzeichnis kopieren auf „Kopieren, wenn neuer“ fest.
Erstellen einer einfachen UI
In diesem Beispiel erstellen wir eine einfache Benutzeroberfläche, die eine Schaltfläche enthält, damit der Benutzer ein Bild auswählen kann, das mit dem Modell ausgewertet werden soll, ein Bildsteuerelement zum Anzeigen des ausgewählten Bilds und einen TextBlock zum Auflisten der Objekte, die das Modell im Bild erkannt hat, und die Konfidenz der einzelnen Objektklassifizierungen.
Ersetzen Sie in der Datei MainWindow.xaml
das standardmäßige StackPanel-Element durch den folgenden XAML-Code.
<!--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>
Initialisieren des Modells
Erstellen Sie in der Datei MainWindow.xaml.cs
innerhalb der MainWindow-Klasse eine Hilfsmethode namens InitModel, die das Modell initialisiert. Diese Methode verwendet APIs aus der SharpDX.DXGI-Bibliothek, um den ersten verfügbaren Adapter auszuwählen. Der ausgewählte Adapter wird im SessionOptions-Objekt für den DirectML-Ausführungsanbieter in dieser Sitzung festgelegt. Schließlich wird eine neue InferenceSession initialisiert, wobei der Pfad zur Modelldatei und die Sitzungsoptionen übergeben werden.
// 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);
}
Laden und Analysieren eines Bilds
Der Einfachheit halber werden für dieses Beispiel alle Schritte zum Laden und Formatieren des Bilds, zum Aufrufen des Modells und zum Anzeigen der Ergebnisse im Schaltflächenklickhandler platziert. Beachten Sie, dass das asynchrone Schlüsselwort dem Schaltflächenklickhandler hinzugefügt wird, der in der Standardvorlage enthalten ist, damit asynchrone Vorgänge im Handler ausgeführt werden können.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
Verwenden Sie ein FileOpenPicker-Element, damit der Benutzer ein Bild von ihrem Computer auswählen kann, um es zu analysieren und in der Benutzeroberfläche anzuzeigen.
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;
Als Nächstes müssen wir die Eingabe verarbeiten, um sie in ein Format zu bringen, das vom Modell unterstützt wird. Die SixLabors.ImageSharp-Bibliothek wird verwendet, um das Bild im 24-Bit-RGB-Format zu laden und die Größe des Bilds auf 224 x 224 Pixel zu ändern. Dann werden die Pixelwerte normalisiert und mit einem Mittelwert von 255*[0,485, 0,456, 0,406] und der Standardabweichung von 255*[0,229, 0,224, 0,225] normalisiert. Die Details des Formats, das das Modell erwartet, finden Sie auf der GitHub-Seite des Resnet-Modells.
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];
}
}
});
Als Nächstes legen wir die Eingaben fest, indem wir einen OrtValue des Tensor-Typs über dem verwalteten Bilddatenarray erstellen.
// 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 }
};
Wenn die Rückschlusssitzung noch nicht initialisiert wurde, rufen Sie die InitModel-Hilfsmethode auf. Rufen Sie dann die Run-Methode auf, um das Modell auszuführen und die Ergebnisse abzurufen.
// Run inference
if (_inferenceSession == null)
{
InitModel();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
Das Modell gibt die Ergebnisse als systemeigenen Tensorpuffer aus. Der folgende Code konvertiert die Ausgabe in ein Array von Floats. Eine Softmax-Funktion wird angewendet, sodass die Werte im Bereich [0,1] liegen und 1 ergeben.
// 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);
Der Index jedes Werts im Ausgabearray entspricht einer Bezeichnung, auf die das Modell trainiert wurde, und der Wert bei diesem Index ist die Konfidenz des Modells, das die Beschriftung für ein Objekt darstellt, das im Eingabebild erkannt wurde. Wir wählen die 10 Ergebnisse mit dem höchsten Konfidenzwert aus. Dieser Code verwendet einige Hilfsobjekte, die wir im nächsten Schritt definieren werden.
// 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
Deklarieren von Hilfsobjekten
Die Prediction-Klasse bietet nur eine einfache Möglichkeit, eine Objektbezeichnung einem Konfidenzwert zuzuordnen. Fügen Sie in MainPage.xaml.cs
diese Klasse im ONNXWinUIExample-Namespaceblock hinzu, aber außerhalb der MainWindow-Klassendefinition.
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
Fügen Sie als Nächstes die LabelMap-Hilfsklasse hinzu, in der alle Objektbezeichnungen aufgelistet sind, auf denen das Modell trainiert wurde, sodass die Bezeichnungen den Indizes der vom Modell zurückgegebenen Ergebnisse zugeordnet sind. Die Liste der Bezeichnungen kann hier aufgrund ihrer Länge nicht vollständig aufgeführt werden. Sie können die vollständige LabelMap-Klasse aus einer Beispielcodedatei im ONNXRuntime github-Repository kopieren und in den ONNXWinUIExample-Namespaceblock einfügen.
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
Ausführen des Beispiels
Erstellen Sie das Projekt, und führen Sie es aus. Klicken Sie auf die Schaltfläche Foto auswählen, und wählen Sie eine zu analysierende Bilddatei aus. Sie können sich die LabelMap-Hilfsklassendefinition ansehen, um zu sehen, was das Modell erkennen kann, und ein Bild auswählen, das interessante Ergebnisse haben könnte. Nachdem das Modell initialisiert wurde, sollte bei der ersten Ausführung und nach Abschluss der Modellverarbeitung eine Liste der Objekte angezeigt werden, die im Bild erkannt wurden, sowie der Konfidenzwert jeder Vorhersage.
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