Начало работы с моделями 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