Kom igång med ONNX-modeller i din WinUI-app med ONNX Runtime
Den här artikeln beskriver hur du skapar en WinUI 3-app som använder en ONNX-modell för att klassificera objekt i en bild och visa förtroende för varje klassificering. Mer information om hur du använder AI- och maskininlärningsmodeller i din Windows-app finns i Komma igång med AI i Windows.
När du använder AI-funktioner rekommenderar vi att du granskar: Utveckla ansvarsfulla generativa AI-program och funktioner i Windows.
Vad är ONNX-körmiljön?
ONNX Runtime är en plattformsoberoende maskininlärningsmodellaccelerator med ett flexibelt gränssnitt för att integrera maskinvaruspecifika bibliotek. ONNX Runtime kan användas med modeller från PyTorch, Tensorflow/Keras, TFLite, scikit-learnoch andra ramverk. Mer information finns på ONNX Runtime webbplats på https://onnxruntime.ai/docs/.
Det här exemplet använder DirectML Execution Provider som abstraherar och körs över olika maskinvarualternativ på Windows-enheter och stöder körning över lokala acceleratorer, till exempel GPU och NPU.
Förutsättningar
- Enheten måste ha aktiverat utvecklarläge. För mer information, se Aktivera enheten för utveckling.
- Visual Studio 2022 eller senare med .NET-arbetsbelastning för skrivbordsutveckling.
Skapa en ny C#WinUI-app
Skapa ett nytt projekt i Visual Studio. I dialogrutan Skapa ett nytt projekt anger du språkfiltret till "C#" och projekttypsfiltret till "winui" och väljer sedan Tom app, Paketerad (WinUI3 i Desktop) mall. Ge det nya projektet namnet "ONNXWinUIExample".
Lägga till referenser till Nuget-paket
I Solution Explorerhögerklickar du på beroenden och väljer Hantera NuGet-paket.... I NuGet-pakethanteraren väljer du fliken Bläddra. Sök efter följande paket och välj den senaste stabila versionen i listrutan Version och klicka sedan på Installera.
Paket | Beskrivning |
---|---|
Microsoft.ML.OnnxRuntime.DirectML | Tillhandahåller API:er för att köra ONNX-modeller på GPU:n. |
SixLabors.ImageSharp | Tillhandahåller bildverktyg för bearbetning av bilder för modellindata. |
SharpDX.DXGI | Tillhandahåller API:er för åtkomst till DirectX-enheten från C#. |
Lägg till följande med hjälp av-direktiv överst i MainWindows.xaml.cs
för att få åtkomst till API:erna från dessa 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;
Lägg till modellen i projektet
I Solution Explorerhögerklickar du på projektet och väljer Lägg till>ny mapp. Ge den nya mappen namnet "model". I det här exemplet använder vi resnet50-v2-7.onnx modell från https://github.com/onnx/models. Gå till lagringsplatsvy för modellen på https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Klicka på knappen *Ladda ned råfil. Kopiera den här filen till katalogen "modell" som du nyss skapade.
I lösningsutforskaren klickar du på modellfilen och ställer in Kopiera till utdatakatalog till "Kopiera om nyare".
Skapa ett enkelt användargränssnitt
I det här exemplet skapar vi ett enkelt användargränssnitt som innehåller en knapp så att användaren kan välja en bild som ska utvärderas med modellen, en bild kontroll för att visa den valda bilden och en TextBlock- för att visa de objekt som modellen identifierade i bilden och konfidensen för varje objektklassificering.
I filen MainWindow.xaml
ersätter du standardelementet StackPanel med följande XAML-kod.
<!--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>
Initiera modellen
I MainWindow.xaml.cs
-filen i klassen MainWindow skapar du en hjälpmetod med namnet InitModel som initierar modellen. Den här metoden använder API:er från biblioteket SharpDX.DXGI för att välja det första tillgängliga kortet. Den valda adaptern anges i objektet SessionOptions för DirectML-exekveringsleverantören i den här sessionen. Slutligen initieras en ny InferenceSession, där sökvägen till modellfilen och sessionsalternativen skickas in.
// 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);
}
Läsa in och analysera en bild
För enkelhetens skull placeras alla steg för att läsa in och formatera bilden, anropa modellen och visa resultatet i knappklickshanteraren. Observera att vi lägger till nyckelordet async i knappklickshanteraren som ingår i standardmallen så att vi kan köra asynkrona åtgärder i hanteraren.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
Använd en FileOpenPicker- så att användaren kan välja en bild från datorn för att analysera och visa den i användargränssnittet.
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;
Därefter måste vi bearbeta indata för att få in dem i ett format som stöds av modellen. Biblioteket SixLabors.ImageSharp används för att läsa in bilden i 24-bitars RGB-format och ändra storlek på bilden till 224 x 224 bildpunkter. Sedan normaliseras pixelvärdena med medelvärdet 255*[0,485, 0,456, 0,406] och standardavvikelsen 255*[0,229, 0,224, 0,225]. Information om det format som modellen förväntar sig finns på github-sidan resnet-modellen.
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];
}
}
});
Sedan konfigurerar vi indata genom att skapa en OrtValue av Tensor-typ i anslutning till datamatrisen för hanterade bilder.
// 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 }
};
Om slutsatsdragningssessionen inte har initierats ännu kan du anropa InitModel hjälpmetod. Anropa sedan metoden Kör för att köra modellen och hämta resultatet.
// Run inference
if (_inferenceSession == null)
{
InitModel();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
Modellen matar ut resultatet som en intern tensorbuffert. Följande kod konverterar utdata till en matris med flyttal. En softmax-funktion tillämpas så att värdena ligger i intervallet [0,1] och summan till 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);
Indexet för varje värde i utdatamatrisen mappar till en etikett som modellen tränats på, och värdet vid det indexet är modellens förtroende för att etiketten representerar ett objekt som identifierats i indatabilden. Vi väljer de 10 resultaten med det högsta konfidensvärdet. Den här koden använder några hjälpobjekt som vi kommer att definiera i nästa steg.
// 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
Deklarera hjälpobjekt
Klassen Prediction ger bara ett enkelt sätt att associera en objektetikett med ett konfidensvärde. I MainPage.xaml.cs
lägger du till den här klassen i ONNXWinUIExample namnområdesblock, men utanför MainWindow-klassdefinitionen.
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
Lägg sedan till hjälpklassen LabelMap som visar alla objektetiketter som modellen tränats på, i en specifik ordning så att etiketterna mappas till indexen för de resultat som returneras av modellen. Listan med etiketter är för lång för att kunna visas i sin helhet här. Du kan kopiera den fullständiga
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
Kör exemplet
Skapa och kör projektet. Klicka på knappen Välj foto och välj en bildfil att analysera. Du kan titta på LabelMap hjälpklassdefinition för att se vad modellen kan känna igen och välja en bild som kan ha intressanta resultat. När modellen har initierats första gången den körs och när modellens bearbetning är klar bör du se en lista över objekt som har identifierats i bilden och konfidensvärdet för varje förutsägelse.
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