Aan de slag met ONNX-modellen in uw WinUI-app met ONNX Runtime
In dit artikel wordt uitgelegd hoe u een WinUI 3-app maakt die gebruikmaakt van een ONNX-model om objecten in een afbeelding te classificeren en het vertrouwen van elke classificatie weer te geven. Zie Aan de slag met AI- en Machine Learning-modellen in uw Windows-appvoor meer informatie over het gebruik van AI- en machine learning-modellen in uw Windows-app.
Bij het gebruik van AI-functies raden we u aan het volgende te bekijken: Ontwikkelen van verantwoorde Generatieve AI-toepassingen en -functies in Windows.
Wat is de ONNX-runtime?
ONNX Runtime is een platformoverschrijdende machine learning-modelversneller met een flexibele interface voor het integreren van hardwarespecifieke bibliotheken. ONNX Runtime kan worden gebruikt met modellen van PyTorch, Tensorflow/Keras, TFLite, scikit-learnen andere frameworks. Voor meer informatie, zie de ONNX Runtime website bij https://onnxruntime.ai/docs/.
In dit voorbeeld wordt de DirectML Execution Provider gebruikt die de verschillende hardwareopties op Windows-apparaten abstraheert en uitvoert en ondersteuning biedt voor de uitvoering van lokale accelerators, zoals de GPU en NPU.
Voorwaarden
- Op uw apparaat moet de ontwikkelaarsmodus zijn ingeschakeld. Zie Uw apparaat inschakelen voor ontwikkelingvoor meer informatie.
- Visual Studio 2022 of hoger met de ontwikkelworkload voor .NET-desktops.
Een nieuwe C# WinUI-app maken
Maak in Visual Studio een nieuw project. Stel in het dialoogvenster Een nieuw project maken het taalfilter in op 'C#' en het projecttypefilter op 'WinUI'. Selecteer vervolgens de Lege app, verpakt ('WinUI3 in Desktop') sjabloon. Noem het nieuwe project "ONNXWinUIExample".
Verwijzingen toevoegen aan Nuget-pakketten
Klik in Solution Explorermet de rechtermuisknop op Afhankelijkheden en selecteer NuGet-pakketten beheren.... Selecteer in NuGet-pakketbeheer het tabblad Bladeren. Zoek de volgende pakketten en selecteer voor elk pakket de meest recente stabiele versie in de vervolgkeuzelijst versie en klik vervolgens op Installeren.
Pakket | Beschrijving |
---|---|
Microsoft.ML.OnnxRuntime.DirectML | Biedt API's voor het uitvoeren van ONNX-modellen op de GPU. |
SixLabors.ImageSharp | Biedt hulpprogramma's voor het verwerken van afbeeldingen voor modelinvoer. |
SharpDX.DXGI | Biedt API's voor toegang tot het DirectX-apparaat vanuit C#. |
Voeg de volgende toe met behulp van instructies boven aan MainWindows.xaml.cs
om toegang te krijgen tot de API's vanuit deze bibliotheken.
// 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;
Het model toevoegen aan uw project
Klik in Solution Explorer-met de rechtermuisknop op uw project en selecteer Toevoegen>Nieuwe map. Geef de nieuwe map de naam 'model'. In dit voorbeeld gebruiken we het resnet50-v2-7.onnx- model van https://github.com/onnx/models. Ga naar de repo voor model https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Klik op de knop *Onbewerkt bestand downloaden. Kopieer dit bestand naar de map "model" die u net hebt gemaakt.
Klik in Solution Explorer op het modelbestand en stel Kopiëren naar Doelmap in op 'Kopiëren als nieuwer'.
Een eenvoudige gebruikersinterface maken
In dit voorbeeld maken we een eenvoudige gebruikersinterface met een Button zodat de gebruiker een afbeelding kan selecteren die moet worden geëvalueerd met het model, een besturingselement Afbeelding om de geselecteerde afbeelding weer te geven en een TextBlock- om de objecten weer te geven die in de afbeelding zijn gedetecteerd en het vertrouwen van elke objectclassificatie.
Vervang in het bestand MainWindow.xaml
het standaardelement StackPanel door de volgende 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>
Het model initialiseren
Maak in het MainWindow.xaml.cs
-bestand in de klasse MainWindow een helpermethode met de naam InitModel waarmee het model wordt geïnitialiseerd. Deze methode maakt gebruik van API's uit de SharpDX.DXGI bibliotheek om de eerste beschikbare adapter te selecteren. De geselecteerde adapter wordt ingesteld in het SessionOptions--object voor de DirectML-uitvoeringsprovider in deze sessie. Ten slotte wordt er een nieuwe InferenceSession geïnitialiseerd en wordt daarbij het pad naar het modelbestand en de sessieopties doorgegeven.
// 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);
}
Een afbeelding laden en analyseren
Om het eenvoudig te maken, worden in dit voorbeeld alle stappen voor het laden en opmaken van de afbeelding, het aanroepen van het model en het weergeven van de resultaten in de knopklikhandler geplaatst. Houd er rekening mee dat we de asynchrone trefwoord toevoegen aan de knopklinkhandler die is opgenomen in de standaardsjabloon, zodat we asynchrone bewerkingen in de handler kunnen uitvoeren.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
Gebruik een FileOpenPicker- zodat de gebruiker een afbeelding van zijn computer kan selecteren om deze te analyseren en weer te geven in de gebruikersinterface.
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;
Vervolgens moeten we de invoer verwerken om deze op te halen in een indeling die wordt ondersteund door het model. De SixLabors.ImageSharp bibliotheek wordt gebruikt om de afbeelding in 24-bits RGB-indeling te laden en het formaat van de afbeelding te wijzigen in 224 x 224 pixels. Vervolgens worden de pixelwaarden genormaliseerd met een gemiddelde van 255*[0,485, 0,456, 0,406] en standaarddeviatie van 255*[0,229, 0,224, 0,225]. De details van de indeling die het model verwacht, vindt u op de github-pagina het resnet-model.
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];
}
}
});
Vervolgens stellen we de invoer in door een OrtValue- van het Tensor-type te maken bovenop een gegevensarray van beheerde afbeeldingsdata.
// 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 }
};
Vervolgens, als de deductiesessie nog niet is geïnitialiseerd, roept u de InitModel helpermethode aan. Roep vervolgens de methode Uitvoeren aan om het model uit te voeren en de resultaten op te halen.
// Run inference
if (_inferenceSession == null)
{
InitModel();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
Het model voert de resultaten uit als een systeemeigen tensorbuffer. Met de volgende code wordt de uitvoer geconverteerd naar een matrix met floats. Er wordt een softmax-functie toegepast, zodat de waarden in het bereik [0,1] en som tot 1 liggen.
// 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);
De index van elke waarde in de uitvoermatrix wordt toegewezen aan een label waarop het model is getraind en de waarde op die index is het vertrouwen van het model dat het label een object vertegenwoordigt dat in de invoerafbeelding is gedetecteerd. We kiezen de 10 resultaten met de hoogste betrouwbaarheidswaarde. In deze code worden enkele helperobjecten gebruikt die in de volgende stap worden gedefinieerd.
// 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
Helperobjecten declareren
De voorspellingsklasse biedt een eenvoudige manier om een objectlabel te koppelen aan een betrouwbaarheidswaarde. Voeg in MainPage.xaml.cs
deze klasse toe in de ONNXWinUIExample naamruimteblok, maar buiten de MainWindow klassedefinitie.
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
Voeg vervolgens de LabelMap helperklasse toe waarin alle objectlabels worden vermeld waarop het model is getraind, in een specifieke volgorde, zodat de labels worden toegewezen aan de indexen van de resultaten die door het model worden geretourneerd. De lijst met labels is te lang om hier volledig te presenteren. U kunt de volledige LabelMap klasse kopiëren uit een voorbeeldcodebestand in de GITHub-opslagplaats ONNXRuntime en in het ONNXWinUIExample naamruimteblok plakken.
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
Het voorbeeld uitvoeren
Bouw het project en voer het uit. Klik op de knop Foto selecteren en kies een afbeeldingsbestand dat u wilt analyseren. U kunt de definitie van de LabelMap helperklasse bekijken om te zien wat het model kan herkennen en een afbeelding kan kiezen die interessante resultaten kan hebben. Nadat het model is geïnitialiseerd, de eerste keer dat het wordt uitgevoerd, en nadat de verwerking van het model is voltooid, ziet u een lijst met objecten die zijn gedetecteerd in de afbeelding en de betrouwbaarheidswaarde van elke voorspelling.
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