Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo guia-o na criação de uma aplicação WinUI que utiliza um modelo ONNX para classificar objetos numa imagem e mostrar a confiança de cada classificação. Para mais informações sobre o uso de modelos de IA e aprendizagem automática na sua aplicação Windows, consulte Comece com IA no Windows. Este guia utiliza Windows ML para gerir automaticamente os fornecedores de execução para inferência acelerada por hardware.
Ao utilizar recursos de IA, recomendamos que você analise: Desenvolvendo aplicativos e recursos de IA generativa responsável no Windows.
O que é o tempo de execução do ONNX
ONNX Runtime é um acelerador de modelo de aprendizado de máquina multiplataforma, com uma interface flexível para integrar bibliotecas específicas de hardware. ONNX Runtime pode ser usado com modelos de PyTorch, Tensorflow/Keras, TFLite, scikit-learne outros frameworks. Para obter mais informações, consulte o site da ONNX Runtime em https://onnxruntime.ai/docs/.
Este exemplo utiliza Windows ML, que partilha ONNX Runtime e descarrega dinamicamente os melhores fornecedores de execução para o hardware do utilizador. O Windows ML abstrai a seleção de hardware — a sua aplicação beneficia automaticamente da aceleração da GPU, NPU ou CPU sem incluir fornecedores de execução específicos.
Pré-requisitos
- Seu dispositivo deve ter o modo de desenvolvedor ativado. Para obter mais informações, consulte Habilitar seu dispositivo para desenvolvimento.
- Visual Studio 2022 ou posterior com a carga de trabalho de desenvolvimento de desktop .NET.
Criar um novo aplicativo WinUI em C#
No Visual Studio, crie um novo projeto. Na caixa de diálogo Criar um novo projeto , defina o filtro de idioma como "C#" e o filtro de tipo de projeto como "winui" e, em seguida, selecione o modelo Aplicativo em branco, Empacotado (WinUI3 na área de trabalho ). Nomeie o novo projeto como "ONNXWinUIExample".
Adicionar referências a pacotes Nuget
No Gerenciador de Soluções, clique com o botão direito do mouse em Dependências e selecione Gerenciar pacotes NuGet.... No gerenciador de pacotes NuGet, selecione a guia Procurar . Procure os seguintes pacotes e, para cada um, selecione a versão estável mais recente na lista suspensa Versão e clique em Instalar.
| Embalagem | Descrição |
|---|---|
| Microsoft.WindowsAppSDK.ML | Fornece ao runtime do Windows ML o ONNX Runtime e a gestão automática do provedor de execução. |
| SixLabors.ImageSharp | Fornece utilitários de imagem para o processamento de imagens como entrada para modelos. |
Adicione as seguintes diretivas de uso à parte superior do MainWindows.xaml.cs para acessar as APIs dessas bibliotecas.
// MainWindow.xaml.cs
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using Microsoft.Windows.AI.MachineLearning;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
Adicionar o modelo ao seu projeto
No Gerenciador de Soluções, clique com o botão direito do mouse em seu projeto e selecione Adicionar> Nova Pasta. Nomeie a nova pasta como "modelo". Para este exemplo, usaremos o modelo resnet50-v2-7.onnx da https://github.com/onnx/models. Vá para o repositório do modelo em https://github.com/onnx/models/blob/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx. Clique no botão *Download de arquivo bruto . Copie este arquivo para o diretório "modelo" que você acabou de criar.
No Gerenciador de Soluções, clique no arquivo de modelo e defina Copiar para o diretório de saída como "Copiar se for mais recente".
Criar uma interface do usuário simples
Para este exemplo, criaremos uma interface do usuário simples que inclui um Button para permitir que o usuário selecione uma imagem para avaliar com o modelo, um controle Image para exibir a imagem selecionada e um TextBlock para listar os objetos que o modelo detetou na imagem e a confiança de cada classificação de objeto.
No arquivo
<!--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>
Inicializar o modelo
No arquivo
// MainWindow.xaml.cs
private InferenceSession _inferenceSession;
private string modelDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "model");
private async Task InitModelAsync()
{
if (_inferenceSession != null)
{
return;
}
// Use Windows ML to download and register the best execution providers
var catalog = ExecutionProviderCatalog.GetDefault();
if (catalog is not null)
{
await catalog.EnsureAndRegisterCertifiedAsync();
}
// Create the inference session — uses registered EPs automatically
_inferenceSession = new InferenceSession($@"{modelDir}\resnet50-v2-7.onnx");
}
Carregar e analisar uma imagem
Para simplificar, neste exemplo, todas as etapas para carregar e formatar a imagem, invocar o modelo e exibir os resultados serão colocadas dentro do manipulador de clique de botão. Observe que adicionamos a palavra-chave async ao manipulador de clique de botão incluído no modelo padrão para que possamos executar operações assíncronas no manipulador.
// MainWindow.xaml.cs
private async void myButton_Click(object sender, RoutedEventArgs e)
{
...
}
Use um FileOpenPicker para permitir que o usuário selecione uma imagem de seu computador para analisá-la e exibi-la na interface do usuário.
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;
Em seguida, precisamos processar a entrada para colocá-la em um formato que seja suportado pelo modelo. A 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];
}
}
});
Em seguida, configuramos as entradas criando um OrtValue do tipo Tensor sobre a matriz de dados de imagem gerenciada.
// 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 }
};
De seguida, se a sessão de inferência ainda não tiver sido inicializada, chame o método auxiliar InitModelAsync . Em seguida, chame o método Run para executar o modelo e recuperar os resultados.
// Run inference
if (_inferenceSession == null)
{
await InitModelAsync();
}
using var runOptions = new RunOptions();
using IDisposableReadOnlyCollection<OrtValue> results = _inferenceSession.Run(runOptions, inputs, _inferenceSession.OutputNames);
O modelo produz os resultados como um buffer tensor nativo. O código a seguir converte a saída em uma matriz de floats. É aplicada uma função softmax de modo a que os valores se situem no intervalo [0,1] e somem 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);
O índice de cada valor na matriz de saída mapeia para um rótulo no qual o modelo foi treinado, e o valor nesse índice é a confiança do modelo de que o rótulo representa um objeto detetado na imagem de entrada. Escolhemos os 10 resultados com o maior valor de confiança. Este código usa alguns objetos auxiliares que definiremos na próxima etapa.
// 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
A classe Prediction fornece apenas uma maneira simples de associar um rótulo de objeto a um valor de confiança. No
internal class Prediction
{
public object Label { get; set; }
public float Confidence { get; set; }
}
Em seguida, adicione a classe auxiliar LabelMap que lista todos os rótulos de objeto nos quais o modelo foi treinado, em uma ordem específica, para que os rótulos sejam mapeados para os índices dos resultados retornados pelo modelo. A lista de rótulos é demasiado longa para ser apresentada na íntegra aqui. Você pode copiar a classe LabelMap completa de um arquivo de código de exemplo no repositório github ONNXRuntime e colá-la no bloco de namespace ONNXWinUIExample .
public class LabelMap
{
public static readonly string[] Labels = new[] {
"tench",
"goldfish",
"great white shark",
...
"hen-of-the-woods",
"bolete",
"ear",
"toilet paper"};
Execute o exemplo
Crie e execute o projeto. Clique no botão Selecionar foto e escolha um arquivo de imagem para analisar. Você pode olhar para a definição de classe auxiliar do LabelMap para ver as coisas que o modelo pode reconhecer e escolher uma imagem que pode ter resultados interessantes. Depois que o modelo for inicializado, na primeira vez que ele for executado, e depois que o processamento do modelo for concluído, você verá uma lista de objetos que foram detetados na imagem e o valor de confiança de cada previsão.
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