Megosztás a következőn keresztül:


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.csadja 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

Lásd még: