Partilhar via


Tutorial: Categorize uma imagem em ML.NET a partir do modelo Custom Vision ONNX

Aprenda a usar ML.NET para detetar objetos em imagens usando um modelo ONNX treinado no serviço Microsoft Custom Vision.

O serviço Microsoft Custom Vision é um serviço de IA que treina um modelo baseado em imagens que carrega. Depois podes exportar o modelo para formato ONNX e usá-lo em ML.NET para fazer previsões.

Neste tutorial, aprenderás como:

  • Use o serviço Custom Vision para criar um modelo ONNX
  • Incorpore o modelo ONNX no fluxo de trabalho do ML.NET
  • Treinar o modelo ML.NET
  • Detetar sinais de stop em imagens de teste

Pré-requisitos

Criar o modelo

Criar o projeto Visão Personalizada

Inicie sessão no serviço Microsoft Custom Vision e selecione Novo Projeto.

No diálogo Novo Projeto , preencha os seguintes itens obrigatórios:

  • Defina o nome do projeto Custom Vision como StopSignDetection.
  • Selecione o recurso que vai usar. Este é um recurso Azure que será criado para o projeto Custom Vision. Se nenhum estiver listado, pode ser criado um selecionando a opção Criar novo link.
  • Defina o tipo de Projeto como Deteção de Objetos.
  • Defina os Tipos de Classificação como Multiclasse , pois haverá uma classe por imagem.
  • Defina o domínio como geral (compacto) [S1]. O domínio compacto permite descarregar o modelo ONNX.
  • Para capacidades de exportação, selecione plataformas Básicas para permitir a exportação do modelo ONNX.

Depois de preencherem os campos acima, selecione Criar projeto.

Adicionar imagens

  1. Com o projeto criado, escolha Adicionar imagens para começar a adicionar imagens para o modelo treinar. Selecione as imagens dos sinais de stop que descarregou.
  2. Seleciona a primeira imagem que é mostrada. Podes selecionar objetos na imagem que queres que o modelo detete. Selecione o sinal de stop na imagem. Um popup exibe e define a etiqueta como sinal de stop.
  3. Repete para todas as imagens restantes. Algumas imagens têm mais do que um sinal de stop, por isso certifique-se de marcar todos os que estão nas imagens.

Treinar o modelo

Com as imagens carregadas e etiquetadas, o modelo pode agora ser treinado. Selecione comboio.

Aparece um pop-up a perguntar que tipo de treino usar. Escolha Treino Rápido e depois selecione Treinar.

Descarregue o modelo ONNX

Quando o treino estiver concluído, clique no botão Exportar . Quando o pop-up aparecer, selecione ONNX para descarregar o modelo ONNX.

Inspecionar o modelo ONNX

Descompacta o ficheiro ONNX descarregado. A pasta contém vários ficheiros, mas os dois que vais usar neste tutorial são:

  • labels.txt, que é um ficheiro de texto contendo os rótulos definidos no serviço Custom Vision.
  • model.onnx, que é o modelo ONNX que vais usar para fazer previsões em ML.NET.

Para construir o pipeline de ML.NET, vais precisar dos nomes das colunas de entrada e saída. Para obter esta informação, use o Netron, uma aplicação web e desktop que pode analisar modelos ONNX e mostrar a sua arquitetura.

  1. Ao usar a aplicação web ou desktop da Netron, abra o modelo ONNX na aplicação. Assim que abre, apresenta um gráfico. Este gráfico diz-lhe algumas coisas que vai precisar para construir o pipeline de ML.NET para previsões.

    • Nome da coluna de entrada - O nome da coluna de entrada necessário ao aplicar o modelo ONNX em ML.NET.

      Netron Input Column

    • Nome da coluna de saída - O nome da coluna de saída necessário ao aplicar o modelo ONNX em ML.NET.

      Coluna de Saída Netron

    • Tamanho da imagem - O tamanho necessário ao redimensionar imagens no pipeline de ML.NET.

      Tamanho da Imagem Netron

Criar um projeto de consola C#

  1. No Visual Studio, crie uma aplicação de consola C# chamada "StopSignDetection". Escolha o .NET 8 como framework de destino.

  2. Instale os seguintes pacotes NuGet para o projeto:

    • Microsoft.ML
    • Microsoft.ML.ImageAnalytics
    • Microsoft.Onnx.Transformer

    Observação

    Este exemplo utiliza a versão estável mais recente dos pacotes NuGet mencionados, salvo indicação em contrário.

Consulte o modelo ONNX

Encontre os dois ficheiros do modelo ONNX (labels.txt e model.onnx) no Visual Studio Solution Explorer. Clique com o botão direito neles e, na janela de Propriedades, defina Copiar para o diretório de saída para Copiar, se for mais recente.

Criar classes de entrada e previsão

  1. Adiciona uma nova turma ao teu projeto e nomea-a StopSignInput. Depois, adicione a seguinte estrutura à turma:

    public struct ImageSettings
    {
        public const int imageHeight = 320;
        public const int imageWidth = 320;
    }
    
  2. De seguida, adicione a seguinte propriedade à classe.

    public class StopSignInput
    {
        [ImageType(ImageSettings.imageHeight, ImageSettings.imageWidth)]
        public Bitmap Image { get; set; }
    }
    

    A Image propriedade contém o mapa de bits da imagem usada para a previsão. O atributo ImageType indica ao ML.NET que a propriedade é uma imagem com dimensões de 320 por 320, determinadas usando Netron.

  3. Adiciona outra aula ao teu projeto e nomeia-a StopSignPrediction. Depois, adicione as seguintes propriedades à classe.

    public class StopSignPrediction
    {
        [ColumnName("detected_classes")]
        public long[] PredictedLabels { get; set; }
    
        [ColumnName("detected_boxes")]
        public float[] BoundingBoxes { get; set; }
    
        [ColumnName("detected_scores")]
        public float[] Scores { get; set; }
    }
    

    A PredictedLabels propriedade contém as previsões das etiquetas para cada objeto detetado. O tipo é um array flutuante, pelo que cada item no array é a previsão de cada rótulo. O atributo ColumnName indica ao ML.NET que esta coluna no modelo representa o nome fornecido, que é detected_classes.

    A BoundingBoxes propriedade contém as caixas delimitadoras para cada objeto detetado. O tipo é um array flutuante e cada objeto detetado vem com quatro itens no array da caixa delimitadora. O atributo ColumnName indica à ML.NET que esta coluna no modelo corresponde ao nome fornecido, que é detected_boxes.

    A Scores propriedade contém as pontuações de confiança de cada objeto previsto e o seu rótulo. O tipo é um array flutuante, pelo que cada item no array é a pontuação de confiança de cada rótulo. O atributo ColumnName indica ao ML.NET que esta coluna no modelo é o nome dado, que é detected_scores.

Use o modelo para fazer previsões

Adicionar diretivas de utilização

No ficheiro Program.cs , adicione as seguintes using diretivas no topo do ficheiro.

using Microsoft.ML;
using Microsoft.ML.Transforms.Image;
using System.Drawing;
using WeatherRecognition;

Criar objetos

  1. Cria o MLContext objeto.

    var context = new MLContext();
    
  2. Cria uma IDataView com uma nova lista vazia StopSignInput .

    var data = context.Data.LoadFromEnumerable(new List<StopSignInput>());
    
  3. Para consistência, guarde as imagens previstas no caminho de montagem.

    var root = new FileInfo(typeof(Program).Assembly.Location);
    var assemblyFolderPath = root.Directory.FullName;
    

Construa o pipeline

Com o elemento vazio IDataView criado, o pipeline pode ser configurado para fazer as previsões de quaisquer novas imagens. O oleoduto consiste em vários passos:

  1. Redimensione as imagens que chegam.

    A imagem enviada ao modelo para previsão estará frequentemente numa proporção de aspeto diferente das imagens treinadas no modelo. Para manter a imagem consistente para previsões precisas, redimensione-a para 320x320. Para isso, use o ResizeImages método e defina o imageColumnName como nome da StopSignInput.Image propriedade.

    var pipeline = context.Transforms.ResizeImages(resizing: ImageResizingEstimator.ResizingKind.Fill, outputColumnName: "image_tensor", imageWidth: ImageSettings.imageWidth, imageHeight: ImageSettings.imageHeight, inputColumnName: nameof(StopSignInput.Image))
    
  2. Extrai os píxeis da imagem.

    Depois de a imagem ter sido redimensionada, é necessário extrair os píxeis da imagem. Anexe o ExtractPixels método ao seu pipeline e especifique o nome da coluna para onde os píxeis serão produzidos usando o outputColumnName parâmetro.

    .Append(context.Transforms.ExtractPixels(outputColumnName: "image_tensor"))
    
  3. Aplique o modelo ONNX à imagem para fazer uma previsão. Isto requer alguns parâmetros:

    • modelFile - O caminho para o ficheiro do modelo ONNX
    • outputColumnNames - Um array de strings que contém os nomes de todos os nomes das colunas de saída, que pode ser encontrado ao analisar o modelo ONNX no Netron.
    • inputColumnNames - Um array de strings que contém os nomes de todos os nomes das colunas de entrada, que também pode ser encontrado ao analisar o modelo ONNX no Netron.
    .Append(context.Transforms.ApplyOnnxModel(outputColumnNames: new string[] { "detected_boxes", "detected_scores", "detected_classes" }, inputColumnNames: new string[] { "image_tensor" }, modelFile: "./Model/model.onnx"));
    

Ajustar o modelo

Agora que definiu um pipeline, pode usá-lo para construir o modelo ML.NET. Use o Fit método no pipeline e passe o vazio IDataView.

var model = pipeline.Fit(data);

De seguida, para fazer previsões, use o modelo para criar um motor de previsão. Este é um método genérico, por isso inclui as StopSignInput classes e StopSignPrediction que foram criadas anteriormente.

var predictionEngine = context.Model.CreatePredictionEngine<StopSignInput, StopSignPrediction>(model);

Extrair as etiquetas

Para mapear as saídas do modelo para as suas etiquetas, precisa de extrair as etiquetas fornecidas pelo Custom Vision. Estas etiquetas encontram-se no ficheirolabels.txt que estava incluído no ficheiro zip com o modelo ONNX.

Chame o ReadAllLines método para ler todas as etiquetas do ficheiro.

var labels = File.ReadAllLines("./model/labels.txt");

Prever numa imagem de teste

Agora pode usar o modelo para prever novas imagens. No projeto, existe uma pasta de teste que pode usar para fazer previsões. Esta pasta contém duas imagens aleatórias, cada uma com um sinal de paragem, de Unsplash. Uma imagem tem um sinal de stop enquanto a outra tem dois sinais de stop. Use o GetFiles método para ler os caminhos dos ficheiros das imagens no diretório.

var testFiles = Directory.GetFiles("./test");

Percorre os caminhos dos ficheiros para fazer uma previsão com o modelo e obter o resultado.

  1. Crie um foreach ciclo para percorrer as imagens de teste.

    Bitmap testImage;
    
    foreach (var image in testFiles)
    {
    
    }
    
  2. No foreach ciclo, gera o nome da imagem prevista com base no nome da imagem de teste original.

    var predictedImage = $"{Path.GetFileName(image)}-predicted.jpg";
    
  3. Também no ciclo foreach, cria um FileStream da imagem e converte-o para um Bitmap.

    using (var stream = new FileStream(image, FileMode.Open))
    {
        testImage = (Bitmap)Image.FromStream(stream);
    }
    
  4. Também no foreach ciclo, chama o Predict método no motor de previsão.

    var prediction = predictionEngine.Predict(new StopSignInput { Image = testImage });
    
  5. Com a previsão, podes obter as caixas delimitadoras. Use o Chunk método para determinar quantos objetos o modelo detetou. Faça isto ao tomar a contagem das caixas delimitadoras previstas e dividi-la pelo número de rótulos previstos. Por exemplo, se tivesse três objetos detetados numa imagem, haveria 12 itens no BoundingBoxes array e três etiquetas previstas. O Chunk método dar-lhe-á então três matrizes de quatro para representar as caixas delimitadoras de cada objeto.

    var boundingBoxes = prediction.BoundingBoxes.Chunk(prediction.BoundingBoxes.Count() / prediction.PredictedLabels.Count());
    
  6. De seguida, capture a largura e altura originais das imagens usadas para previsão.

    var originalWidth = testImage.Width;
    var originalHeight = testImage.Height;
    
  7. Calcula onde na imagem deves desenhar as caixas. Para isso, cria um for ciclo baseado na contagem dos blocos de caixas delimitadoras.

    for (int i = 0; i < boundingBoxes.Count(); i++)
    {
    }
    
  8. Dentro do for loop, calcula a posição das coordenadas x e y, bem como a largura e altura da caixa a desenhar na imagem. A primeira coisa que precisas de fazer é obter o conjunto de caixas delimitadoras usando o ElementAt método.

    var boundingBox = boundingBoxes.ElementAt(i);
    
  9. Com a caixa delimitadora atual, você pode agora calcular onde desenhar a caixa. Use a largura original da imagem para o primeiro e terceiro elementos da caixa delimitadora, e a altura original da imagem para o segundo e quarto elementos.

    var left = boundingBox[0] * originalWidth;
    var top = boundingBox[1] * originalHeight;
    var right = boundingBox[2] * originalWidth;
    var bottom = boundingBox[3] * originalHeight;
    
  10. Calcule a largura e a altura da caixa para desenhar em torno do objeto detetado dentro da imagem. Os itens x e y são as left variáveis e top do cálculo anterior. Use o Math.Abs método para obter o valor absoluto dos cálculos de largura e altura caso seja negativo.

    var x = left;
    var y = top;
    var width = Math.Abs(right - left);
    var height = Math.Abs(top - bottom);
    
  11. De seguida, obtenha a etiqueta prevista a partir do conjunto de etiquetas.

    var label = labels[prediction.PredictedLabels[i]];
    
  12. Crie um gráfico baseado na imagem de teste usando o Graphics.FromImage método.

    using var graphics = Graphics.FromImage(testImage);
    
  13. Desenhe a imagem usando a informação da caixa delimitadora. Primeiro, desenhe o retângulo em torno dos objetos detetados usando o DrawRectangle método que absorve um Pen objeto para determinar a cor e largura do retângulo, e passe as xvariáveis , y, width, e height .

    graphics.DrawRectangle(new Pen(Color.NavajoWhite, 8), x, y, width, height);
    
  14. Depois, mostre a etiqueta prevista dentro da caixa com o DrawString método que requer a cadeia para imprimir e um Font objeto para determinar como desenhar a cadeia e onde colocá-la.

    graphics.DrawString(label, new Font(FontFamily.Families[0], 18f), Brushes.NavajoWhite, x + 5, y + 5);
    
  15. Após o for ciclo, verifica se o ficheiro previsto já existe. Se acontecer, elimina-o. Depois, guarde-o no caminho de saída definido.

    if (File.Exists(predictedImage))
    {
        File.Delete(predictedImage);
    }
    
    testImage.Save(Path.Combine(assemblyFolderPath, predictedImage));
    

Próximos passos

Experimenta um dos outros tutoriais de classificação de imagens: