Поделиться через


Начало работы с моделями ONNX в приложении WinUI с помощью ONNX Runtime

В этой статье описывается создание приложения WinUI 3, использующего модель ONNX для классификации объектов на изображении и отображения достоверности каждой классификации. Дополнительные сведения об использовании моделей искусственного интеллекта и машинного обучения в приложении Windows см. в статье "Начало работы с ИИ" и Машинное обучение моделей в приложении Windows.

Что такое среда выполнения ONNX

ONNX Runtime — это кроссплатформенный акселератор модели машинного обучения с гибким интерфейсом для интеграции библиотек, относящихся к оборудованию. ONNX Runtime можно использовать с моделями из PyTorch, Tensorflow/Keras, TFLite и scikit-learnдругих платформ. Дополнительные сведения см. на ONNX Runtime веб-сайте https://onnxruntime.ai/docs/.

В этом примере используется DirectML Execution Provider абстрагирование и запуск различных аппаратных параметров на устройствах Windows и поддержка выполнения в локальных акселераторах, таких как GPU и NPU.

Необходимые компоненты

  • Устройство должно быть включено в режиме разработчика. Дополнительные сведения см. в разделе "Включение устройства для разработки".
  • Visual Studio 2022 или более поздней версии с рабочей нагрузкой разработки классических приложений .NET.

Создание нового приложения WinUI C#

В Visual Studio создайте проект . В диалоговом окне "Создание проекта" задайте для фильтра языка значение "C#", а фильтр типа проекта — "winui", а затем выберите пустое приложение, упаковав (WinUI3 в desktop). Назовите новый проект ONNXWinUIExample.

Добавление ссылок на пакеты Nuget

В Обозреватель решений щелкните правой кнопкой мыши зависимости и выберите пункт "Управление пакетами NuGet...". В диспетчере пакетов NuGet выберите вкладку "Обзор". Найдите следующие пакеты и выберите последнюю стабильную версию в раскрывающемся списке "Версия", а затем нажмите кнопку "Установить".

Пакет Description
Microsoft.ML.OnnxRuntime.DirectML Предоставляет API для запуска моделей ONNX на GPU.
SixLabors.ImageSharp Предоставляет служебные программы изображений для обработки изображений для ввода модели.
SharpDX.DXGI Предоставляет API-интерфейсы для доступа к устройству DirectX из C#.

Добавьте следующие директивы using в верхнюю часть MainWindows.xaml.cs для доступа к API из этих библиотек.

// 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;

Добавление модели в проект

В Обозреватель решений щелкните проект правой кнопкой мыши и выберите "Добавить новую> папку". Назовите новую папку "model". В этом примере мы будем использовать модель resnet50-v2-7.onnx из https://github.com/onnx/models. Перейдите в представление репозитория для модели по адресу https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Нажмите кнопку *Скачать необработанный файл . Скопируйте этот файл в только что созданный каталог model.

В Обозреватель решений щелкните файл модели и установите для параметра Copy to Output Directory значение "Копировать, если создать".

Создание простого пользовательского интерфейса

В этом примере мы создадим простой пользовательский интерфейс, содержащий кнопку, чтобы пользователь могли выбрать изображение для оценки с помощью модели, элемент управления "Изображение" для отображения выбранного изображения и TextBlock для перечисления объектов, обнаруженных в изображении, и достоверности каждой классификации объектов.

В файле замените MainWindow.xaml элемент StackPanel по умолчанию следующим кодом 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>

Инициализация модели

MainWindow.xaml.cs В файле в классе MainWindow создайте вспомогательный метод с именем InitModel, который инициализирует модель. Этот метод использует API из библиотеки SharpDX.DXGI для выбора первого доступного адаптера. Выбранный адаптер задан в объекте SessionOptions для поставщика выполнения DirectML в этом сеансе. Наконец, новое определениеSession инициализируется, передав путь к файлу модели и параметрам сеанса.

// 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);

}

Загрузка и анализ образа

Для простоты в этом примере все шаги по загрузке и форматированию изображения, вызову модели и отображению результатов будут помещены в обработчик нажатия кнопки. Обратите внимание, что мы добавим асинхронную ключевое слово в обработчик нажатия кнопки, включенный в шаблон по умолчанию, чтобы можно было выполнять асинхронные операции в обработчике.

// MainWindow.xaml.cs

private async void myButton_Click(object sender, RoutedEventArgs e)
{
    ...
}

Используйте FileOpenPicker, чтобы разрешить пользователю выбрать изображение на компьютере для анализа и отображения его в пользовательском интерфейсе.

    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;

Затем необходимо обработать входные данные, чтобы получить его в формате, поддерживаемом моделью. Библиотека SixLabors.ImageSharp используется для загрузки изображения в 24-разрядном формате RGB и изменения размера изображения до 224x224 пикселей. Затем нормализованы значения пикселей нормализованы со средним значением 255*[0,485, 0,456, 0,406] и стандартным отклонением от 255*[0,229, 0,224, 0,225]. Подробные сведения о формате, который ожидает модель, можно найти на странице github модели 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];
            }
        }
    });

Затем мы настроим входные данные путем создания типа OrtValue типа Tensor на вершине массива данных управляемого образа.

    // 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 }
    };

Затем, если сеанс вывода еще не инициализирован, вызовите вспомогательный метод InitModel . Затем вызовите метод Run , чтобы запустить модель и получить результаты.

    // Run inference
    if (_inferenceSession == null)
    {
        InitModel();
    }
    using var runOptions = new RunOptions();
    using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);

Модель выводит результаты в виде собственного буфера тензора. Следующий код преобразует выходные данные в массив с плавающей запятой. Функция softmax применяется таким образом, чтобы значения лежали в диапазоне [0,1] и сумме до 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);

Индекс каждого значения в выходном массиве сопоставляется с меткой, на которую была обучена модель, и значение по указанному индексу является уверенностью модели в том, что метка представляет объект, обнаруженный на входном изображении. Мы выбрали 10 результатов с самым высоким значением достоверности. Этот код использует некоторые вспомогательные объекты, которые мы определим на следующем шаге.

    // 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

Объявление вспомогательных объектов

Класс Прогнозирования просто предоставляет простой способ связывания метки объекта со значением достоверности. Добавьте MainPage.xaml.csэтот класс в блок пространства имен ONNXWinUIExample , но за пределами определения класса MainWindow .

internal class Prediction
{
    public object Label { get; set; }
    public float Confidence { get; set; }
}

Затем добавьте вспомогательный класс LabelMap , который перечисляет все метки объектов, на которые была обучена модель, в определенном порядке, чтобы метки сопоставлялись с индексами результатов, возвращаемых моделью. Список меток слишком длинный, чтобы представить в полном объеме здесь. Полный класс LabelMap можно скопировать из примера файла кода в репозитории github ONNXRuntime и вставить его в блок пространства имен ONNXWinUIExample.

public class LabelMap
{
    public static readonly string[] Labels = new[] {
        "tench",
        "goldfish",
        "great white shark",
        ...
        "hen-of-the-woods",
        "bolete",
        "ear",
        "toilet paper"};

Выполнение примера

Постройте и запустите проект. Нажмите кнопку "Выбрать фотографию " и выберите файл изображения для анализа. Вы можете просмотреть определение вспомогательного класса LabelMap , чтобы увидеть, что модель может распознать и выбрать изображение, которое может иметь интересные результаты. После инициализации модели при первом запуске и после завершения обработки модели вы увидите список объектов, обнаруженных на изображении, и значение достоверности каждого прогноза.

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

См. также