Introducción a los modelos ONNX en la aplicación WinUI con ONNX Runtime
Este artículo le guía a través de la creación de una aplicación WinUI 3 que usa un modelo ONNX para clasificar objetos en una imagen y mostrar la confianza de cada clasificación. Para obtener más información sobre el uso de modelos de inteligencia artificial y aprendizaje automático en la aplicación de Windows, consulte Introducción al uso de modelos de IA y Machine Learning en la aplicación de Windows.
Al utilizar las funciones de IA, le recomendamos revisar: Desarrollando aplicaciones y características responsables de IA generativa en Windows.
¿Qué es el entorno de ejecución de ONNX?
ONNX Runtime es un acelerador de modelos de aprendizaje automático multiplataforma, con una interfaz flexible para integrar bibliotecas específicas de hardware. ONNX Runtime se pueden usar con modelos de PyTorch, Tensorflow/Keras, TFLite, scikit-learny otros marcos. Para obtener más información, consulta el sitio web de ONNX Runtime en https://onnxruntime.ai/docs/.
En este ejemplo se usa el DirectML Execution Provider que abstrae y ejecuta en las distintas opciones de hardware en dispositivos Windows y admite la ejecución en aceleradores locales, como la GPU y la NPU.
Prerrequisitos
- El dispositivo debe tener habilitado el modo de desarrollador. Para obtener más información, vea Habilitar el dispositivo para el desarrollo.
- Visual Studio 2022 o posterior con la carga de trabajo de desarrollo de aplicaciones de escritorio .NET.
Creación de una nueva aplicación WinUI de C#
En Visual Studio, cree un nuevo proyecto. En el cuadro de diálogo Crear un nuevo proyecto, establezca el filtro de idioma en "C#" y el filtro de tipo de proyecto en "winui" y, a continuación, seleccione la plantilla Aplicación vacía, empaquetada (WinUI3 en escritorio). Asigne al nuevo proyecto el nombre "ONNXWinUIExample".
Adición de referencias a paquetes Nuget
En Explorador de Soluciones, haga clic derecho en Dependencias y seleccione Administrar paquetes NuGet.... En el administrador de paquetes NuGet, seleccione la pestaña Examinar. Busque los siguientes paquetes y, para cada uno, seleccione la versión estable más reciente en la lista desplegable de Versión y, a continuación, haga clic en Instalar.
Paquete | Descripción |
---|---|
Microsoft.ML.OnnxRuntime.DirectML | Proporciona API para ejecutar modelos ONNX en la GPU. |
SixLabors.ImageSharp | Proporciona utilidades de imagen para procesar imágenes para la entrada del modelo. |
SharpDX.DXGI | Proporciona API para acceder al dispositivo DirectX desde C#. |
Agregue las siguientes directivas de uso a la parte superior de MainWindows.xaml.cs
para acceder a las API de estas bibliotecas.
// 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;
Adición del modelo al proyecto
En Explorador de soluciones, haga clic con el botón derecho en su proyecto y seleccione Agregar->Nueva carpeta. Asigne el nombre "model" a la nueva carpeta. En este ejemplo, usaremos el modelo de resnet50-v2-7.onnx de https://github.com/onnx/models. Vaya a la vista del repositorio del modelo en https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Haga clic en el botón *Descargar archivo sin procesar. Copie este archivo en el directorio "modelo" que acaba de crear.
En el Explorador de Soluciones, haga clic en el archivo de modelo y configure Copiar al directorio de salida en "Copiar si es más reciente".
Creación de una interfaz de usuario sencilla
En este ejemplo, crearemos una interfaz de usuario sencilla que incluya un Button para permitir al usuario seleccionar una imagen para evaluar con el modelo, un control image image para mostrar la imagen seleccionada y un TextBlock para enumerar los objetos detectados en la imagen y la confianza de cada clasificación de objetos.
En el archivo MainWindow.xaml
, reemplace el elemento StackPanel predeterminado por el siguiente código 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>
Inicialización del modelo
En el archivo MainWindow.xaml.cs
, dentro de la clase MainWindow, cree un método auxiliar denominado InitModel que inicializará el modelo. Este método usa la API de la biblioteca de SharpDX.DXGI para seleccionar el primer adaptador disponible. El adaptador seleccionado se establece en el objeto SessionOptions para el proveedor de ejecución de DirectML en esta sesión. Por último, se inicializa una nueva InferenceSession, pasando la ruta al archivo del modelo y las opciones de sesión.
// 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);
}
Carga y análisis de una imagen
Por motivos de simplicidad, en este ejemplo, todos los pasos para cargar y dar formato a la imagen, invocar el modelo y mostrar los resultados se colocarán dentro del controlador de clic del botón. Tenga en cuenta que agregamos la palabra clave async al controlador del clic del botón de la plantilla predeterminada para poder ejecutar operaciones asincrónicas en el controlador.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
Utilice un FileOpenPicker para permitir al usuario seleccionar una imagen desde su ordenador para analizarla y mostrarla en la UI.
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;
A continuación, es necesario procesar la entrada para obtenerla en un formato compatible con el modelo. La biblioteca
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];
}
}
});
A continuación, configuramos las entradas mediante la creación de una OrtValue de tipo Tensor sobre la matriz de datos de imagen administrada.
// 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 }
};
A continuación, si aún no se ha inicializado la sesión de inferencia, llame al método auxiliar InitModel. A continuación, llame al método run
// Run inference
if (_inferenceSession == null)
{
InitModel();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
El modelo muestra los resultados como un búfer tensor nativo. El código siguiente convierte la salida en una matriz de floats. Se aplica una función softmax para que los valores se encuentran en el intervalo [0,1] y suman a 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);
El índice de cada valor de la matriz de salida se asigna a una etiqueta en la que se entrenó el modelo y el valor de ese índice es la confianza del modelo de que la etiqueta representa un objeto detectado en la imagen de entrada. Seleccionamos los 10 resultados con el valor de confianza más alto. Este código usa algunos objetos auxiliares que definiremos en el paso siguiente.
// 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
Declarar objetos auxiliares
La clase Prediction solo proporciona una manera sencilla de asociar una etiqueta de objeto con un valor de confianza. En MainPage.xaml.cs
, agregue esta clase dentro del bloque de espacio de nombres ONNXWinUIExample, pero fuera de la definición de la clase MainWindow.
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
A continuación, agregue la clase de ayuda LabelMap que enumera todas las etiquetas de objetos con las que se entrenó el modelo, en un orden específico para que las etiquetas se correspondan con los índices de los resultados devueltos por el modelo. La lista de etiquetas es demasiado larga para presentarse en su totalidad aquí. Puede copiar la clase LabelMap completa de un archivo de código de ejemplo en el repositorio de github ONNXRuntime y pegarla en el bloque de espacio de nombres ONNXWinUIExample.
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
Ejecución del ejemplo
Compile y ejecute el proyecto. Haga clic en el botón Seleccionar foto y elija un archivo de imagen para analizar. Puede consultar la definición de la clase auxiliar LabelMap para ver los elementos que el modelo puede reconocer y elegir una imagen que podría ofrecer resultados interesantes. Después de inicializar el modelo, la primera vez que se ejecuta y una vez completado el procesamiento del modelo, debería ver una lista de objetos detectados en la imagen y el valor de confianza de cada predicción.
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