Začínáme s modely ONNX v aplikaci WinUI pomocí ONNX Runtime
Tento článek vás provede vytvořením aplikace WinUI 3, která používá model ONNX ke klasifikaci objektů na obrázku a zobrazení spolehlivosti každé klasifikace. Další informace o používání modelů AI a strojového učení v aplikaci pro Windows najdete v tématu Začínáme s AI ve Windows.
Při používání funkcí umělé inteligence doporučujeme zkontrolovat: Vytváření odpovědných generativních aplikací a funkcí umělé inteligence ve Windows.
Co je modul runtime ONNX
ONNX Runtime je akcelerátor modelů strojového učení pro různé platformy s flexibilním rozhraním pro integraci knihoven specifických pro hardware. ONNX Runtime lze použít s modely z PyTorch, Tensorflow/Keras, TFLite, scikit-learna dalších architektur. Další informace najdete na webu ONNX Runtime na https://onnxruntime.ai/docs/.
Tato ukázka používá DirectML Execution Provider, která abstrahuje a běží napříč různými možnostmi hardwaru na zařízeních s Windows a podporuje spouštění v místních akcelerátorech, jako jsou GPU a NPU.
Požadavky
- Vaše zařízení musí mít povolený vývojářský režim. Podrobnosti naleznete v části Povolení zařízení pro vývoj.
- Visual Studio 2022 nebo novější s úlohou vývoje desktopových aplikací .NET
Vytvoření nové aplikace WinUI v C#
V sadě Visual Studio vytvořte nový projekt. V dialogovém okně Vytvořit nový projekt nastavte filtr podle jazyka na C# a filtr podle typu projektu na winui, a poté vyberte šablonu Prázdná aplikace, zabalenou (WinUI3 v desktopové aplikaci). Pojmenujte nový projekt ONNXWinUIExample.
Přidání odkazů na balíčky NuGet
V Průzkumníku řešeníklikněte pravým tlačítkem na Závislosti a vyberte Spravovat balíčky NuGet.... Ve správci balíčků NuGet vyberte kartu Procházet. Vyhledejte následující balíčky a pro každou z nich vyberte nejnovější stabilní verzi v rozevíracím seznamu Verze a potom klikněte na Nainstalovat.
Balíček | Popis |
---|---|
Microsoft.ML.OnnxRuntime.DirectML | Poskytuje rozhraní API pro spouštění modelů ONNX na GPU. |
SixLabors.ImageSharp | Poskytuje nástroje pro zpracování obrázků pro vstup modelu. |
SharpDX.DXGI | Poskytuje rozhraní API pro přístup k zařízení DirectX z jazyka C#. |
Přidejte následující pomocí direktiv na začátek MainWindows.xaml.cs
pro přístup k rozhraním API z těchto knihoven.
// 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;
Přidání modelu do projektu
V Průzkumníku řešeníklikněte pravým tlačítkem na váš projekt a vyberte Přidat ->Nová složka. Pojmenujte novou složku "model". V tomto příkladu budeme používat model resnet50-v2-7.onnx z https://github.com/onnx/models. Přejděte do zobrazení úložiště modelu v https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Klikněte na tlačítko *Stáhnout nezpracovaný soubor. Zkopírujte tento soubor do adresáře "model", který jste právě vytvořili.
V Průzkumníku řešení klikněte na soubor modelu a nastavte Kopírovat do výstupního adresáře na Kopírovat, pokud je novější.
Vytvoření jednoduchého uživatelského rozhraní
V tomto příkladu vytvoříme jednoduché uživatelské rozhraní, které obsahuje tlačítko , které uživateli umožní vybrat obrázek, který má model vyhodnotit, Obrázek ovládací prvek pro zobrazení vybraného obrázku a TextBlock k výpisu objektů, které model na obrázku rozpoznal, a jistotu každé klasifikace objektů.
V souboru MainWindow.xaml
nahraďte výchozí prvek StackPanel následujícím kódem 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>
Inicializujte model
V MainWindow.xaml.cs
souboru uvnitř třídy MainWindow vytvořte pomocnou metodu s názvem InitModel, která inicializuje model. Tato metoda používá rozhraní API z knihovny SharpDX.DXGI k výběru prvního dostupného adaptéru. Vybraný adaptér je nastaven v objektu SessionOptions pro poskytovatele provádění DirectML v rámci této relace. Nakonec se inicializuje nová instance InferenceSession s předáním cesty k souboru modelu a možností nastavení relace.
// 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);
}
Načtení a analýza obrázku
Pro zjednodušení se v tomto příkladu všechny kroky pro načtení a formátování obrázku, vyvolání modelu a zobrazení výsledků se umístí do obslužné rutiny kliknutí na tlačítko. Všimněte si, že přidáváme klíčové slovo async do handleru kliknutí na tlačítko, který je součástí výchozí šablony, abychom mohli spouštět asynchronní operace v handleru.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
Pomocí FileOpenPicker umožníte uživateli vybrat obrázek ze svého počítače k analýze a zobrazení v uživatelském rozhraní.
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ále musíme zpracovat vstup, abychom ho získali do formátu, který model podporuje. Knihovna SixLabors.ImageSharp slouží k načtení obrázku ve 24bitovém formátu RGB a změně velikosti obrázku na 224 × 224 pixelů. Pak se normalizují hodnoty pixelů s průměrem 255*[0,485, 0,456, 0,406] a směrodatnou odchylkou 255*[0,229, 0,224, 0,225]. Podrobnosti o formátu, který model očekává, najdete na stránce GitHubu 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];
}
}
});
Dále nastavíme vstupy vytvořením OrtValue typu Tensoru nad polem spravovaných obrazových dat.
// 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 }
};
Dále, pokud se relace odvození ještě neinicializovala, zavolejte InitModel pomocnou metodu. Potom zavolejte metodu Spustit, která model spustí a načte výsledky.
// Run inference
if (_inferenceSession == null)
{
InitModel();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
Model vypíše výsledky jako nativní tenzorovou vyrovnávací paměť. Následující kód konvertuje výstup na pole čísel typu float. Funkce softmax se použije tak, aby hodnoty spadají v rozsahu [0,1] a jejich součet byl 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);
Index každé hodnoty ve výstupním poli se mapuje na popisek, na který byl model natrénován, a hodnota v daném indexu je spolehlivost modelu, že popisek představuje objekt zjištěný ve vstupním obrázku. Vybereme 10 výsledků s nejvyšší hodnotou spolehlivosti. Tento kód používá některé pomocné objekty, které definujeme v dalším 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
Deklarace pomocných objektů
Třída Prediction poskytuje jednoduchý způsob, jak přidružit popisek objektu k hodnotě spolehlivosti. V MainPage.xaml.cs
přidejte tuto třídu do bloku oboru názvů ONNXWinUIExample, ale mimo definici třídy MainWindow.
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
Dále přidejte LabelMap pomocné třídě, která obsahuje seznam všech popisků objektů, na které byl model vytrénován, v určitém pořadí tak, aby popisky mapovaly na indexy výsledků vrácených modelem. Seznam popisků je příliš dlouhý na to, abychom jej zde mohli uvést celý. Kompletní třídu LabelMap můžete zkopírovat ze souboru ukázkového kódu v úložišti ONNXRuntime na githubu a vložit ji do bloku oboru názvů ONNXWinUIExample.
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
Spusťte příklad
Sestavte a spusťte projekt. Klikněte na tlačítko Vybrat fotku a vyberte soubor obrázku, který chcete analyzovat. Můžete se podívat na definici LabelMap pomocné třídy, abyste viděli, co model dokáže rozpoznat, a vybrat obrázek, který může mít zajímavé výsledky. Po inicializaci modelu byste při prvním spuštění a po dokončení zpracování modelu měli vidět seznam objektů, které byly zjištěny na obrázku, a hodnotu spolehlivosti jednotlivých předpovědí.
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