Ismerkedés az ONNX-modellekkel a WinUI-alkalmazásban a ONNX Runtime
Ez a cikk bemutatja, hogyan hozhat létre egy WinUI 3-alkalmazást, amely ONNX-modellel osztályozza az objektumokat egy képen, és megjeleníti az egyes besorolások megbízhatóságát. Az AI- és gépi tanulási modellek Windows-alkalmazásban való használatáról további információt a AI használatának első lépései a Windowscímű témakörben talál.
Az AI-funkciók használatakor javasoljuk, hogy tekintse át a következőket: Felelős Generatív AI-alkalmazások és -szolgáltatások fejlesztése a Windows rendszeren.
Mi az ONNX-futtatókörnyezet?
ONNX Runtime egy platformfüggetlen gépi tanulási modellgyorsító, amely rugalmas felülettel rendelkezik a hardverspecifikus kódtárak integrálásához. ONNX Runtime a PyTorch, a Tensorflow/Keras, a TFLite, a scikit-learnés más keretrendszerek modelljeihez használható. További információért lásd a ONNX Runtime weboldalt a https://onnxruntime.ai/docs/címen.
Ez a minta az DirectML Execution Provider használja, amely absztrakciókat hajt végre és futtat a Windows-eszközök különböző hardverbeállításain, és támogatja a végrehajtást a helyi gyorsítókon, például a GPU-n és az NPU-n.
Előfeltételek
- Az eszköznek engedélyeznie kell a fejlesztői módot. További információkért lásd: Az eszköz engedélyezése fejlesztéshez.
- Visual Studio 2022 vagy újabb verzió a .NET asztali fejlesztési számítási feladattal.
Új C# WinUI-alkalmazás létrehozása
Hozzon létre egy új projektet a Visual Studióban. Az Új projekt létrehozása párbeszédpanelen állítsa a nyelvi szűrőt "C#" értékre, a projekttípus-szűrőt pedig "winui" értékre, majd válassza a Üres alkalmazás, Csomagolva (WinUI3 asztali verzió) sablont. Adja az új projektnek az "ONNXWinUIExample" nevet.
Hivatkozások hozzáadása Nuget-csomagokhoz
A Megoldáskezelőbenkattintson a jobb gombbal Függőségek elemre, és válassza NuGet-csomagok kezelése...lehetőséget. A NuGet-csomagkezelőben válassza a Tallózás lapot. Keresse meg a következő csomagokat, és mindegyiknél válassza ki a legújabb stabil verziót a Verzió legördülő listában, majd kattintson a Telepítéselemre.
Csomag | Leírás |
---|---|
Microsoft.ML.OnnxRuntime.DirectML | API-kat biztosít AZ ONNX-modellek GPU-n való futtatásához. |
SixLabors.ImageSharp | Kép-segédprogramokat biztosít a képek modellbemenethez való feldolgozásához. |
SharpDX.DXGI | API-kat biztosít a DirectX-eszköz C#-ból való eléréséhez. |
Adja hozzá a következő irányelvek használatával, hogy hozzáférjen az API-khoz ezeknek a könyvtáraknak.
// 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;
Modell hozzáadása a projekthez
A Megoldáskezelőbenkattintson a jobb gombbal a projektre, és válassza a Hozzáadás>Új mappalehetőséget. Nevezze el az új mappát "modellnek". Ebben a példában a resnet50-v2-7.onnx modellt fogjuk használni https://github.com/onnx/models. Nyissa meg a modell adattárnézetét a https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Kattintson a *Nyers fájl letöltése gombra. Másolja ezt a fájlt az imént létrehozott "modell" könyvtárba.
A Megoldáskezelőben kattintson a modellfájlra, és állítsa Másolás kimeneti könyvtárba "Másolás, ha újabb" értékre.
Egyszerű felhasználói felület létrehozása
Ebben a példában létrehozunk egy egyszerű felhasználói felületet, amely egy gombot tartalmaz, amellyel a felhasználó kiválaszthat egy képet, amelyet kiértékelhet a modellel, egy Kép vezérlőt a kijelölt kép megjelenítéséhez, valamint egy TextBlock a modell által észlelt objektumok és az egyes objektumbesorolások megbízhatóságának listázásához.
A MainWindow.xaml
fájlban cserélje le az alapértelmezett StackPanel elemet a következő XAML-kódra.
<!--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>
A modell inicializálása
A MainWindow.xaml.cs
fájlban, a MainWindow osztályban hozzon létre egy InitModel nevű segédmetódust, amely inicializálja a modellt. Ez a módszer a SharpDX.DXGI kódtár API-jait használja az első elérhető adapter kiválasztásához. A kijelölt adapter a Munkamenetoptions objektumban van beállítva ebben a munkamenetben a DirectML végrehajtási szolgáltató számára. Végül inicializál egy új InferenceSession, amely a modellfájl és a munkamenet beállításainak elérési útját adja át.
// 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);
}
Kép betöltése és elemzése
Az egyszerűség kedvéért ebben a példában a kép betöltésének és formázásának, a modell meghívásának és az eredmények megjelenítésének összes lépése a gomb kattintáskezelőjében lesz elhelyezve. Vegye figyelembe, hogy hozzáadjuk a aszinkron kulcsszót az alapértelmezett sablonban található gombkattintási kezelőhöz, hogy aszinkron műveleteket futtathassunk a kezelőben.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
Egy FileOpenPicker használatával lehetővé teszi a felhasználó számára, hogy válasszon ki egy képet a számítógépéről, hogy elemezze és megjelenítse azt a felhasználói felületen.
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;
Ezután fel kell dolgoznunk a bemenetet, hogy a modell által támogatott formátumba juttassuk. A SixLabors.ImageSharp könyvtár a kép 24 bites RGB formátumban való betöltésére és a kép 224x224 képpontra való átméretezésére szolgál. Ezután a képpontértékek normalizálása 255*[0,485, 0,456, 0,406] középértékkel és 255*[0,229, 0,224, 0,225] szórással normalizálódik. A modell által várt formátum részletei az újrahálózati modellgithub-oldalon találhatók.
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];
}
}
});
Ezután a bemeneteket úgy állítjuk be, hogy létrehozunk egy Tensor típusú OrtValue-t a kezelt képadatok tömbjén.
// 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 }
};
Ezután, ha a következtetési munkamenet még nem inicializálódott, hívja fel InitModel segédmetódust. Ezután hívja meg a Run metódust a modell futtatásához és az eredmények lekéréséhez.
// Run inference
if (_inferenceSession == null)
{
InitModel();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
A modell natív tenzorpufferként adja ki az eredményeket. Az alábbi kód lebegőpontos tömbökké alakítja a kimenetet. A softmax függvényt úgy alkalmazzák, hogy az értékek a [0,1] tartományba essenek és az összegük 1 legyen.
// 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);
A kimeneti tömb minden értékének indexe egy olyan címkére van leképezve, amelyre a modellt betanították, és az index értéke a modell megbízhatósága, hogy a címke egy, a bemeneti képen észlelt objektumot jelöl. A legmagasabb megbízhatósági értékkel rendelkező 10 találatot választjuk ki. Ez a kód néhány segédobjektumot használ, amelyeket a következő lépésben fogunk meghatározni.
// 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
Segédobjektumok deklarálása
Az Prediction osztály egyszerűen társíthat egy objektumcímkét egy megbízhatósági értékkel. A MainPage.xaml.cs
adja hozzá ezt az osztályt az ONNXWinUIExample névtérblokkhoz, de a MainWindow osztálydefiníción kívül.
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
Ezután adja hozzá a LabelMap segédosztályt, amely felsorolja a modell által betanított összes objektumcímkét egy adott sorrendben, hogy a címkék a modell által visszaadott eredmények indexeihez igazodjanak. A címkék listája túl hosszú ahhoz, hogy itt teljes egészében szerepeljen. A teljes LabelMap osztályt az ONNXRuntime github-adattár mintakódfájljából másolhatja, és beillesztheti az ONNXWinUIExample névtérblokkba.
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
A példa futtatása
Hozza létre és futtassa a projektet. Kattintson a Fénykép kiválasztása gombra, és válasszon egy elemezni kívánt képfájlt. A LabelMap segédosztálydefiníciójában megtekintheti a modell által felismerhető elemeket, és kiválaszthat egy érdekes eredményeket tartalmazó képet. A modell inicializálása után az első futtatáskor és a modell feldolgozása után látnia kell a képen észlelt objektumok listáját, valamint az egyes előrejelzések megbízhatósági értékét.
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