Alguns comandos da CLI do Azure neste artigo usam a extensão azure-cli-ml ou v1 do Azure Machine Learning. O suporte à extensão v1 terminará em 30 de setembro de 2025. Você poderá instalar e usar a extensão v1 até essa data.
Recomendamos que você faça a transição para a extensão ml ou v2, antes de 30 de setembro de 2025. Para obter mais informações sobre a extensão v2, confira Extensão da CLI do Azure ML e SDK do Python v2.
Neste artigo, você aprenderá a usar o ONNX (Open Neural Network Exchange) para fazer previsões sobre modelos de pesquisa visual computacional gerados por meio do AutoML (machine learning automatizado) no Azure Machine Learning.
Para usar o ONNX para previsões, você precisa:
Baixar os arquivos de modelo do ONNX de uma execução de treinamento do AutoML.
Entender as entradas e saídas de um modelo do ONNX.
Pré-processar os seus dados para que eles estejam no formato necessário para as imagens de entrada.
Executar a inferência com o ONNX Runtime para Python.
Visualizar previsões para tarefas de segmentação de instâncias e detecção de objetos.
O ONNX é um padrão aberto para machine learning e modelos de aprendizado profundo. Ele permite a importação e exportação de modelos (interoperabilidade) entre estruturas de IA populares. Para obter mais detalhes, explore o Projeto ONNX do GitHub.
O ONNX Runtime é um projeto de código aberto que dá suporte à inferência de multiplataforma. O ONNX Runtime fornece APIs em linguagens de programação (incluindo Python, C++, C#, C, Java e JavaScript). Você pode usar essas APIs para executar a inferência em imagens de entrada. Depois de exportar o modelo para o formato ONNX, você poderá usar essas APIs em qualquer linguagem de programação que seu projeto precisar.
Neste guia, você aprenderá a usar as APIs do Python para o ONNX Runtime para fazer previsões sobre imagens em tarefas populares da pesquisa visual. Você pode usar esses modelos exportados do ONNX nas linguagens.
Instale o pacote onnxruntime. Os métodos neste artigo foram testados com as versões 1.3.0 a 1.8.0.
Baixar arquivos de modelo do ONNX
Você pode baixar arquivos de modelo do ONNX em execuções do AutoML usando a interface do usuário do Estúdio do Azure Machine Learning ou o SDK do Azure Machine Learning para Python. É recomendável baixar por meio do SDK com o nome do experimento e a ID de execução pai.
Azure Machine Learning Studio
No Estúdio do Azure Machine Learning, acesse seu experimento usando o hiperlink para o experimento gerado no notebook de treinamento ou selecionando o nome do experimento na guia Experimentos em Ativos. Em seguida, selecione a melhor execução filha.
Na melhor execução filha, acesse Saídas + logs>train_artifacts. Use o botão Baixar para baixar manualmente os seguintes arquivos:
labels.json: arquivo que contém todas as classes ou rótulos no conjunto de dados de treinamento.
model.onnx: modelo no formato ONNX.
Salve os arquivos de modelo baixados em um diretório. O exemplo neste artigo usa o diretório ./automl_models.
SDK do Python do Azure Machine Learning
Com o SDK, você pode selecionar a melhor execução filha (por métrica primária) com o nome do experimento e a ID de execução pai. Em seguida, você pode baixar os arquivos labels.json e model.onnx.
O código a seguir retorna a melhor execução filha com base na métrica primária relevante.
from azureml.train.automl.run import AutoMLRun
# Select the best child run
run_id = '' # Specify the run ID
automl_image_run = AutoMLRun(experiment=experiment, run_id=run_id)
best_child_run = automl_image_run.get_best_child()
Baixe o arquivo labels.json, que contém todas as classes e rótulos do conjunto de dados de treinamento.
Por padrão, o AutoML para Imagens oferece suporte à pontuação do lote para classificação. No entanto, os modelos de detecção de objetos e segmentação de instâncias não dão suporte à inferência em lote. No caso de inferência do lote para detecção de objetos e segmentação de instâncias, use o procedimento a seguir para gerar um modelo do ONNX para o tamanho de lote necessário. Os modelos gerados para um tamanho de lote específico não funcionam para outros tamanhos de lote.
from azureml.core.script_run_config import ScriptRunConfig
from azureml.train.automl.run import AutoMLRun
from azureml.core.workspace import Workspace
from azureml.core import Experiment
# specify experiment name
experiment_name = ''
# specify workspace parameters
subscription_id = ''
resource_group = ''
workspace_name = ''
# load the workspace and compute target
ws = ''
compute_target = ''
experiment = Experiment(ws, name=experiment_name)
# specify the run id of the automl run
run_id = ''
automl_image_run = AutoMLRun(experiment=experiment, run_id=run_id)
best_child_run = automl_image_run.get_best_child()
Para obter os valores de argumento necessários para criar o modelo de pontuação do lote, veja os scripts de pontuação gerados na pasta de saídas das execuções de treinamento do AutoML. Use os valores de hiperparâmetro disponíveis na variável de configurações do modelo dentro do arquivo de pontuação para a melhor execução filha.
Para classificação de imagem de várias classes, o modelo do ONNX gerado para a melhor execução filha dá suporte à pontuação do lote por padrão. Portanto, argumentos específicos do modelo não são necessários para esse tipo de tarefa e você pode pular para a seção Carregar os rótulos e arquivos de modelo do ONNX.
Para classificação de imagem de vários rótulos, o modelo do ONNX gerado para a melhor execução filha dá suporte à pontuação do lote por padrão. Portanto, argumentos específicos do modelo não são necessários para esse tipo de tarefa e você pode pular para a seção Carregar os rótulos e arquivos de modelo do ONNX.
arguments = ['--model_name', 'fasterrcnn_resnet34_fpn', # enter the faster rcnn or retinanet model name
'--batch_size', 8, # enter the batch size of your choice
'--height_onnx', 600, # enter the height of input to ONNX model
'--width_onnx', 800, # enter the width of input to ONNX model
'--experiment_name', experiment_name,
'--subscription_id', subscription_id,
'--resource_group', resource_group,
'--workspace_name', workspace_name,
'--run_id', run_id,
'--task_type', 'image-object-detection',
'--min_size', 600, # minimum size of the image to be rescaled before feeding it to the backbone
'--max_size', 1333, # maximum size of the image to be rescaled before feeding it to the backbone
'--box_score_thresh', 0.3, # threshold to return proposals with a classification score > box_score_thresh
'--box_nms_thresh', 0.5, # NMS threshold for the prediction head
'--box_detections_per_img', 100 # maximum number of detections per image, for all classes
]
arguments = ['--model_name', 'yolov5', # enter the yolo model name
'--batch_size', 8, # enter the batch size of your choice
'--height_onnx', 640, # enter the height of input to ONNX model
'--width_onnx', 640, # enter the width of input to ONNX model
'--experiment_name', experiment_name,
'--subscription_id', subscription_id,
'--resource_group', resource_group,
'--workspace_name', workspace_name,
'--run_id', run_id,
'--task_type', 'image-object-detection',
'--img_size', 640, # image size for inference
'--model_size', 'medium', # size of the yolo model
'--box_score_thresh', 0.1, # threshold to return proposals with a classification score > box_score_thresh
'--box_iou_thresh', 0.5 # IOU threshold used during inference in nms post processing
]
arguments = ['--model_name', 'maskrcnn_resnet50_fpn', # enter the maskrcnn model name
'--batch_size', 8, # enter the batch size of your choice
'--height_onnx', 600, # enter the height of input to ONNX model
'--width_onnx', 800, # enter the width of input to ONNX model
'--experiment_name', experiment_name,
'--subscription_id', subscription_id,
'--resource_group', resource_group,
'--workspace_name', workspace_name,
'--run_id', run_id,
'--task_type', 'image-instance-segmentation',
'--min_size', 600, # minimum size of the image to be rescaled before feeding it to the backbone
'--max_size', 1333, # maximum size of the image to be rescaled before feeding it to the backbone
'--box_score_thresh', 0.3, # threshold to return proposals with a classification score > box_score_thresh
'--box_nms_thresh', 0.5, # NMS threshold for the prediction head
'--box_detections_per_img', 100 # maximum number of detections per image, for all classes
]
Baixe e mantenha o arquivo ONNX_batch_model_generator_automl_for_images.py no diretório atual e envie o script. Use ScriptRunConfig para enviar o script ONNX_batch_model_generator_automl_for_images.py disponível no repositório GitHub do azureml-examples, para gerar um modelo do ONNX de um tamanho de lote específico. No código a seguir, o ambiente de modelo treinado é usado para enviar esse script para gerar e salvar o modelo do ONNX no diretório de saídas.
Depois que o modelo do lote for gerado, baixe-o em Saídas+logs>saídas manualmente ou use o seguinte método:
batch_size= 8 # use the batch size used to generate the model
onnx_model_path = 'automl_models/model.onnx' # local path to save the model
remote_run.download_file(name='outputs/model_'+str(batch_size)+'.onnx', output_file_path=onnx_model_path)
Após a etapa de download do modelo, use o pacote Python do ONNX Runtime para executar a inferência usando o arquivo model.onnx. Para fins de demonstração, este artigo usa os conjuntos de dados em Como preparar conjuntos de dados de imagens para cada tarefa de pesquisa visual.
Treinamos os modelos para todas as tarefas de pesquisa visual com os respectivos conjuntos de dados para demonstrar a inferência de modelo do ONNX.
Carregar os rótulos e arquivos de modelo do ONNX
O snippet de código a seguir carrega labels.json, no qual os nomes de classe são ordenados. Ou seja, se o modelo do ONNX prever uma ID de rótulo como 2, ela corresponderá ao nome do rótulo fornecido no terceiro índice no arquivo labels.json.
import json
import onnxruntime
labels_file = "automl_models/labels.json"
with open(labels_file) as f:
classes = json.load(f)
print(classes)
try:
session = onnxruntime.InferenceSession(onnx_model_path)
print("ONNX model loaded...")
except Exception as e:
print("Error loading ONNX file: ",str(e))
Obter detalhes de entrada e saída esperados para um modelo do ONNX
Quando você tem o modelo, é importante conhecer alguns detalhes específicos do modelo e da tarefa. Esses detalhes incluem o número de entradas e de saídas, a forma ou formato de entrada esperado para pré-processar a imagem e a forma de saída para que você saiba as saídas específicas do modelo ou da tarefa.
sess_input = session.get_inputs()
sess_output = session.get_outputs()
print(f"No. of inputs : {len(sess_input)}, No. of outputs : {len(sess_output)}")
for idx, input_ in enumerate(range(len(sess_input))):
input_name = sess_input[input_].name
input_shape = sess_input[input_].shape
input_type = sess_input[input_].type
print(f"{idx} Input name : { input_name }, Input shape : {input_shape}, \
Input type : {input_type}")
for idx, output in enumerate(range(len(sess_output))):
output_name = sess_output[output].name
output_shape = sess_output[output].shape
output_type = sess_output[output].type
print(f" {idx} Output name : {output_name}, Output shape : {output_shape}, \
Output type : {output_type}")
Formatos de entrada e de saída esperados para o modelo do ONNX
Todo modelo do ONNX tem um conjunto predefinido de formatos de entrada e de saída.
Este exemplo aplica o modelo treinado no conjunto de dados fridgeObjects com 134 imagens e 4 classes/rótulos para explicar a inferência de modelo do ONNX. Para obter mais informações sobre como treinar uma tarefa de classificação de imagem, confira o notebook de classificação de imagem de várias classes.
Formato de entrada
A entrada é uma imagem pré processada.
Nome de entrada
Forma de entrada
Tipo de entrada
Descrição
input1
(batch_size, num_channels, height, width)
ndarray(float)
A entrada é uma imagem pré-processada, com a forma (1, 3, 224, 224) para um tamanho de lote de 1, e uma altura e largura de 224. Esses números correspondem aos valores usados para crop_size no exemplo de treinamento.
Formato da saída
A saída é uma matriz de logits de todas as classes/rótulos.
Nome de saída
Forma de saída
Tipo de saída
Descrição
output1
(batch_size, num_classes)
ndarray(float)
O modelo retorna logits (sem softmax). Por exemplo, para o tamanho de lote de 1 e 4 classes, ele retorna (1, 4).
A entrada é uma imagem pré-processada, com a forma (1, 3, 224, 224) para um tamanho de lote de 1, e uma altura e largura de 224. Esses números correspondem aos valores usados para crop_size no exemplo de treinamento.
Formato da saída
A saída é uma matriz de logits de todas as classes/rótulos.
Nome de saída
Forma de saída
Tipo de saída
Descrição
output1
(batch_size, num_classes)
ndarray(float)
O modelo retorna logits (sem sigmoid). Por exemplo, para o tamanho de lote de 1 e 4 classes, ele retorna (1, 4).
Este exemplo de detecção de objetos usa o modelo treinado no conjunto de dados de detecção fridgeObjects com 128 imagens e 4 classes/rótulos para explicar a inferência de modelo do ONNX. Este exemplo treina modelos de Faster R-CNN para demonstrar as etapas de inferência. Para obter mais informações sobre como treinar os modelos de detecção de objetos, confira o notebook de detecção de objetos.
Formato de entrada
A entrada é uma imagem pré processada.
Nome de entrada
Forma de entrada
Tipo de entrada
Descrição
Entrada
(batch_size, num_channels, height, width)
ndarray(float)
A entrada é uma imagem pré-processada, com a forma (1, 3, 600, 800) para um tamanho de lote de 1, altura de 600 e largura de 800.
Formato da saída
A saída é uma tupla de output_names e previsões. Aqui, output_names e predictions são listas com 3*batch_size de comprimento cada. Para o Faster R-CNN, as saídas são caixas, rótulos e pontuações, enquanto que as saídas do RetinaNet são caixas, pontuações e rótulos.
Nome de saída
Forma de saída
Tipo de saída
Descrição
output_names
(3*batch_size)
Lista de chaves
Para um tamanho de lote de 2, output_names será ['boxes_0', 'labels_0', 'scores_0', 'boxes_1', 'labels_1', 'scores_1']
predictions
(3*batch_size)
Lista de ndarray(float)
Para um tamanho de lote de 2, predictions assumirá a forma de [(n1_boxes, 4), (n1_boxes), (n1_boxes), (n2_boxes, 4), (n2_boxes), (n2_boxes)]. Aqui, os valores em cada índice correspondem ao mesmo índice output_names.
A tabela a seguir descreve caixas, rótulos e pontuações retornados para cada exemplo no lote de imagens.
Nome
Forma
Tipo
Descrição
Caixas
(n_boxes, 4), em que cada caixa tem x_min, y_min, x_max, y_max
ndarray(float)
O modelo retorna n caixas com as coordenadas superior esquerda e inferior direita.
Rótulos
(n_boxes)
ndarray(float)
Rótulo ou ID de classe de um objeto em cada caixa.
Pontuações
(n_boxes)
ndarray(float)
Pontuação de confiança de um objeto em cada caixa.
Este exemplo de detecção de objetos usa o modelo treinado no conjunto de dados de detecção fridgeObjects com 128 imagens e 4 classes/rótulos para explicar a inferência de modelo do ONNX. Este exemplo treina modelos YOLO para demonstrar as etapas de inferência. Para obter mais informações sobre como treinar os modelos de detecção de objetos, confira o notebook de detecção de objetos.
Formato de entrada
A entrada é uma imagem pré-processada, com a forma (1, 3, 640, 640) para um tamanho de lote de 1, e uma altura e largura de 640. Esses números correspondem aos valores usados no exemplo de treinamento.
Nome de entrada
Forma de entrada
Tipo de entrada
Descrição
Entrada
(batch_size, num_channels, height, width)
ndarray(float)
A entrada é uma imagem pré-processada, com a forma (1, 3, 640, 640) para um tamanho de lote de 1, altura de 640 e largura de 640.
Formato da saída
As previsões de modelo do ONNX contêm várias saídas. A primeira saída é necessária para executar a supressão não máxima para detecções. Para facilitar o uso, o ML automatizado exibe o formato de saída após a etapa de pré-processamento do NMS. A saída após NMS é uma lista de caixas, rótulos e pontuações para cada amostra no lote.
Nome de saída
Forma de saída
Tipo de saída
Descrição
Saída
(batch_size)
Lista de ndarray(float)
O modelo retorna detecções de caixas para cada amostra no lote
Cada célula na lista indica detecções de caixas de uma amostra com a forma (n_boxes, 6), onde cada caixa tem x_min, y_min, x_max, y_max, confidence_score, class_id.
Neste exemplo de segmentação de instância, você usa o modelo Mask R-CNN que foi treinado no conjunto de dados fridgeObjects com 128 imagens e 4 classes/rótulos para explicar a inferência do modelo do ONNX. Para obter mais informações sobre o treinamento do modelo de segmentação de instância, confira o notebook de segmentação de instância.
Importante
Somente o Mask R-CNN tem suporte para tarefas de segmentação de instância. Os formatos de entrada e saída baseiam-se apenas no Mask R-CNN.
Formato de entrada
A entrada é uma imagem pré processada. O modelo do ONNX para Mask R-CNN foi exportado para trabalhar com imagens de formas diferentes. É recomendável redimensioná-los para um tamanho fixo consistente com os tamanhos de imagem de treinamento a fim de aprimorar o desempenho.
Nome de entrada
Forma de entrada
Tipo de entrada
Descrição
Entrada
(batch_size, num_channels, height, width)
ndarray(float)
A entrada é uma imagem pré-processada, com a forma (1, 3, input_image_height, input_image_width) para um tamanho de lote de 1 e uma altura e largura semelhante à imagem de entrada.
Formato da saída
A saída é uma tupla de output_names e previsões. Aqui, output_names e predictions são listas com 4*batch_size de comprimento cada.
Nome de saída
Forma de saída
Tipo de saída
Descrição
output_names
(4*batch_size)
Lista de chaves
Para um tamanho de lote de 2, output_names será ['boxes_0', 'labels_0', 'scores_0', 'masks_0', 'boxes_1', 'labels_1', 'scores_1', 'masks_1']
predictions
(4*batch_size)
Lista de ndarray(float)
Para um tamanho de lote de 2, predictions assumirá a forma de [(n1_boxes, 4), (n1_boxes), (n1_boxes), (n1_boxes, 1, height_onnx, width_onnx), (n2_boxes, 4), (n2_boxes), (n2_boxes), (n2_boxes, 1, height_onnx, width_onnx)]. Aqui, os valores em cada índice correspondem ao mesmo índice output_names.
Nome
Forma
Tipo
Descrição
Caixas
(n_boxes, 4), em que cada caixa tem x_min, y_min, x_max, y_max
ndarray(float)
O modelo retorna n caixas com as coordenadas superior esquerda e inferior direita.
Rótulos
(n_boxes)
ndarray(float)
Rótulo ou ID de classe de um objeto em cada caixa.
Pontuações
(n_boxes)
ndarray(float)
Pontuação de confiança de um objeto em cada caixa.
Máscaras
(n_boxes, 1, height_onnx, width_onnx)
ndarray(float)
Máscaras (polígonos) de objetos detectados com a altura e largura da forma de uma imagem de entrada.
Execute as seguintes etapas de pré-processamento para a inferência de modelo do ONNX:
Converta a imagem para RGB.
Redimensione a imagem para os valores valid_resize_size e valid_resize_size que correspondem aos valores usados na transformação do conjunto de dados de validação durante o treinamento. O valor padrão de valid_resize_size é 256.
Centralize e corte a imagem como height_onnx_crop_size e width_onnx_crop_size. Isso corresponde a valid_crop_size com o valor padrão de 224.
Alterar HxWxC para CxHxW.
Converta para o tipo float.
Normalize com mean = [0.485, 0.456, 0.406] e std = [0.229, 0.224, 0.225] do ImageNet.
Se você escolher valores diferentes para os hiperparâmetrosvalid_resize_size e valid_crop_size durante o treinamento, esses valores deverão ser usados.
Obtenha a forma de entrada necessária para o modelo do ONNX.
import glob
import numpy as np
from PIL import Image
def preprocess(image, resize_size, crop_size_onnx):
"""Perform pre-processing on raw input image
:param image: raw input image
:type image: PIL image
:param resize_size: value to resize the image
:type image: Int
:param crop_size_onnx: expected height of an input image in onnx model
:type crop_size_onnx: Int
:return: pre-processed image in numpy format
:rtype: ndarray 1xCxHxW
"""
image = image.convert('RGB')
# resize
image = image.resize((resize_size, resize_size))
# center crop
left = (resize_size - crop_size_onnx)/2
top = (resize_size - crop_size_onnx)/2
right = (resize_size + crop_size_onnx)/2
bottom = (resize_size + crop_size_onnx)/2
image = image.crop((left, top, right, bottom))
np_image = np.array(image)
# HWC -> CHW
np_image = np_image.transpose(2, 0, 1) # CxHxW
# normalize the image
mean_vec = np.array([0.485, 0.456, 0.406])
std_vec = np.array([0.229, 0.224, 0.225])
norm_img_data = np.zeros(np_image.shape).astype('float32')
for i in range(np_image.shape[0]):
norm_img_data[i,:,:] = (np_image[i,:,:]/255 - mean_vec[i])/std_vec[i]
np_image = np.expand_dims(norm_img_data, axis=0) # 1xCxHxW
return np_image
# following code loads only batch_size number of images for demonstrating ONNX inference
# make sure that the data directory has at least batch_size number of images
test_images_path = "automl_models_multi_cls/test_images_dir/*" # replace with path to images
# Select batch size needed
batch_size = 8
# you can modify resize_size based on your trained model
resize_size = 256
# height and width will be the same for classification
crop_size_onnx = height_onnx_crop_size
image_files = glob.glob(test_images_path)
img_processed_list = []
for i in range(batch_size):
img = Image.open(image_files[i])
img_processed_list.append(preprocess(img, resize_size, crop_size_onnx))
if len(img_processed_list) > 1:
img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
img_data = img_processed_list[0]
else:
img_data = None
assert batch_size == img_data.shape[0]
Com PyTorch
import glob
import torch
import numpy as np
from PIL import Image
from torchvision import transforms
def _make_3d_tensor(x) -> torch.Tensor:
"""This function is for images that have less channels.
:param x: input tensor
:type x: torch.Tensor
:return: return a tensor with the correct number of channels
:rtype: torch.Tensor
"""
return x if x.shape[0] == 3 else x.expand((3, x.shape[1], x.shape[2]))
def preprocess(image, resize_size, crop_size_onnx):
transform = transforms.Compose([
transforms.Resize(resize_size),
transforms.CenterCrop(crop_size_onnx),
transforms.ToTensor(),
transforms.Lambda(_make_3d_tensor),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
img_data = transform(image)
img_data = img_data.numpy()
img_data = np.expand_dims(img_data, axis=0)
return img_data
# following code loads only batch_size number of images for demonstrating ONNX inference
# make sure that the data directory has at least batch_size number of images
test_images_path = "automl_models_multi_cls/test_images_dir/*" # replace with path to images
# Select batch size needed
batch_size = 8
# you can modify resize_size based on your trained model
resize_size = 256
# height and width will be the same for classification
crop_size_onnx = height_onnx_crop_size
image_files = glob.glob(test_images_path)
img_processed_list = []
for i in range(batch_size):
img = Image.open(image_files[i])
img_processed_list.append(preprocess(img, resize_size, crop_size_onnx))
if len(img_processed_list) > 1:
img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
img_data = img_processed_list[0]
else:
img_data = None
assert batch_size == img_data.shape[0]
Execute as etapas de pré-processamento a seguir para a inferência de modelo do ONNX. Essas etapas são as mesmas para classificação de imagem de várias classes.
Converta a imagem para RGB.
Redimensione a imagem para os valores valid_resize_size e valid_resize_size que correspondem aos valores usados na transformação do conjunto de dados de validação durante o treinamento. O valor padrão de valid_resize_size é 256.
Centralize e corte a imagem como height_onnx_crop_size e width_onnx_crop_size. Isso corresponde a valid_crop_size com o valor padrão de 224.
Alterar HxWxC para CxHxW.
Converta para o tipo float.
Normalize com mean = [0.485, 0.456, 0.406] e std = [0.229, 0.224, 0.225] do ImageNet.
Se você escolher valores diferentes para os hiperparâmetrosvalid_resize_size e valid_crop_size durante o treinamento, esses valores deverão ser usados.
Obtenha a forma de entrada necessária para o modelo do ONNX.
import glob
import numpy as np
from PIL import Image
def preprocess(image, resize_size, crop_size_onnx):
"""Perform pre-processing on raw input image
:param image: raw input image
:type image: PIL image
:param resize_size: value to resize the image
:type image: Int
:param crop_size_onnx: expected height of an input image in onnx model
:type crop_size_onnx: Int
:return: pre-processed image in numpy format
:rtype: ndarray 1xCxHxW
"""
image = image.convert('RGB')
# resize
image = image.resize((resize_size, resize_size))
# center crop
left = (resize_size - crop_size_onnx)/2
top = (resize_size - crop_size_onnx)/2
right = (resize_size + crop_size_onnx)/2
bottom = (resize_size + crop_size_onnx)/2
image = image.crop((left, top, right, bottom))
np_image = np.array(image)
# HWC -> CHW
np_image = np_image.transpose(2, 0, 1) # CxHxW
# normalize the image
mean_vec = np.array([0.485, 0.456, 0.406])
std_vec = np.array([0.229, 0.224, 0.225])
norm_img_data = np.zeros(np_image.shape).astype('float32')
for i in range(np_image.shape[0]):
norm_img_data[i,:,:] = (np_image[i,:,:] / 255 - mean_vec[i]) / std_vec[i]
np_image = np.expand_dims(norm_img_data, axis=0) # 1xCxHxW
return np_image
# following code loads only batch_size number of images for demonstrating ONNX inference
# make sure that the data directory has at least batch_size number of images
test_images_path = "automl_models_multi_label/test_images_dir/*" # replace with path to images
# Select batch size needed
batch_size = 8
# you can modify resize_size based on your trained model
resize_size = 256
# height and width will be the same for classification
crop_size_onnx = height_onnx_crop_size
image_files = glob.glob(test_images_path)
img_processed_list = []
for i in range(batch_size):
img = Image.open(image_files[i])
img_processed_list.append(preprocess(img, resize_size, crop_size_onnx))
if len(img_processed_list) > 1:
img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
img_data = img_processed_list[0]
else:
img_data = None
assert batch_size == img_data.shape[0]
Com PyTorch
import glob
import torch
import numpy as np
from PIL import Image
from torchvision import transforms
def _make_3d_tensor(x) -> torch.Tensor:
"""This function is for images that have less channels.
:param x: input tensor
:type x: torch.Tensor
:return: return a tensor with the correct number of channels
:rtype: torch.Tensor
"""
return x if x.shape[0] == 3 else x.expand((3, x.shape[1], x.shape[2]))
def preprocess(image, resize_size, crop_size_onnx):
"""Perform pre-processing on raw input image
:param image: raw input image
:type image: PIL image
:param resize_size: value to resize the image
:type image: Int
:param crop_size_onnx: expected height of an input image in onnx model
:type crop_size_onnx: Int
:return: pre-processed image in numpy format
:rtype: ndarray 1xCxHxW
"""
transform = transforms.Compose([
transforms.Resize(resize_size),
transforms.CenterCrop(crop_size_onnx),
transforms.ToTensor(),
transforms.Lambda(_make_3d_tensor),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
img_data = transform(image)
img_data = img_data.numpy()
img_data = np.expand_dims(img_data, axis=0)
return img_data
# following code loads only batch_size number of images for demonstrating ONNX inference
# make sure that the data directory has at least batch_size number of images
test_images_path = "automl_models_multi_label/test_images_dir/*" # replace with path to images
# Select batch size needed
batch_size = 8
# you can modify resize_size based on your trained model
resize_size = 256
# height and width will be the same for classification
crop_size_onnx = height_onnx_crop_size
image_files = glob.glob(test_images_path)
img_processed_list = []
for i in range(batch_size):
img = Image.open(image_files[i])
img_processed_list.append(preprocess(img, resize_size, crop_size_onnx))
if len(img_processed_list) > 1:
img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
img_data = img_processed_list[0]
else:
img_data = None
assert batch_size == img_data.shape[0]
Para detecção de objetos com o algoritmo Faster R-CNN, siga as mesmas etapas de pré-processamento que a classificação de imagem, com exceção do corte da imagem. Você pode redimensionar a imagem com altura 600 e largura 800. Você pode obter a altura e a largura de entrada esperadas com o código a seguir.
Em seguida, execute as etapas de pré-processamento.
import glob
import numpy as np
from PIL import Image
def preprocess(image, height_onnx, width_onnx):
"""Perform pre-processing on raw input image
:param image: raw input image
:type image: PIL image
:param height_onnx: expected height of an input image in onnx model
:type height_onnx: Int
:param width_onnx: expected width of an input image in onnx model
:type width_onnx: Int
:return: pre-processed image in numpy format
:rtype: ndarray 1xCxHxW
"""
image = image.convert('RGB')
image = image.resize((width_onnx, height_onnx))
np_image = np.array(image)
# HWC -> CHW
np_image = np_image.transpose(2, 0, 1) # CxHxW
# normalize the image
mean_vec = np.array([0.485, 0.456, 0.406])
std_vec = np.array([0.229, 0.224, 0.225])
norm_img_data = np.zeros(np_image.shape).astype('float32')
for i in range(np_image.shape[0]):
norm_img_data[i,:,:] = (np_image[i,:,:] / 255 - mean_vec[i]) / std_vec[i]
np_image = np.expand_dims(norm_img_data, axis=0) # 1xCxHxW
return np_image
# following code loads only batch_size number of images for demonstrating ONNX inference
# make sure that the data directory has at least batch_size number of images
test_images_path = "automl_models_od/test_images_dir/*" # replace with path to images
image_files = glob.glob(test_images_path)
img_processed_list = []
for i in range(batch_size):
img = Image.open(image_files[i])
img_processed_list.append(preprocess(img, height_onnx, width_onnx))
if len(img_processed_list) > 1:
img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
img_data = img_processed_list[0]
else:
img_data = None
assert batch_size == img_data.shape[0]
Para detecção de objetos com o algoritmo YOLO, siga as mesmas etapas de pré-processamento que a classificação de imagem, com exceção do corte da imagem. Você pode redimensionar a imagem com altura de 600 e a largura de 800 e obter a altura e a largura de entrada esperadas com o código a seguir.
import glob
import numpy as np
from yolo_onnx_preprocessing_utils import preprocess
# use height and width based on the generated model
test_images_path = "automl_models_od_yolo/test_images_dir/*" # replace with path to images
image_files = glob.glob(test_images_path)
img_processed_list = []
pad_list = []
for i in range(batch_size):
img_processed, pad = preprocess(image_files[i])
img_processed_list.append(img_processed)
pad_list.append(pad)
if len(img_processed_list) > 1:
img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
img_data = img_processed_list[0]
else:
img_data = None
assert batch_size == img_data.shape[0]
Importante
Somente o Mask R-CNN tem suporte para tarefas de segmentação de instância. As etapas de pré-processamento são baseadas apenas no Mask R-CNN.
Execute as seguintes etapas de pré-processamento para a inferência de modelo do ONNX:
Converta a imagem para RGB.
Redimensione a imagem.
Alterar HxWxC para CxHxW.
Converta para o tipo float.
Normalize com mean = [0.485, 0.456, 0.406] e std = [0.229, 0.224, 0.225] do ImageNet.
Para resize_height e resize_width, você também pode usar os valores utilizados durante o treinamento, limitados pelos hiperparâmetrosmin_size e max_size para Mask R-CNN.
import glob
import numpy as np
from PIL import Image
def preprocess(image, resize_height, resize_width):
"""Perform pre-processing on raw input image
:param image: raw input image
:type image: PIL image
:param resize_height: resize height of an input image
:type resize_height: Int
:param resize_width: resize width of an input image
:type resize_width: Int
:return: pre-processed image in numpy format
:rtype: ndarray of shape 1xCxHxW
"""
image = image.convert('RGB')
image = image.resize((resize_width, resize_height))
np_image = np.array(image)
# HWC -> CHW
np_image = np_image.transpose(2, 0, 1) # CxHxW
# normalize the image
mean_vec = np.array([0.485, 0.456, 0.406])
std_vec = np.array([0.229, 0.224, 0.225])
norm_img_data = np.zeros(np_image.shape).astype('float32')
for i in range(np_image.shape[0]):
norm_img_data[i,:,:] = (np_image[i,:,:]/255 - mean_vec[i])/std_vec[i]
np_image = np.expand_dims(norm_img_data, axis=0) # 1xCxHxW
return np_image
# following code loads only batch_size number of images for demonstrating ONNX inference
# make sure that the data directory has at least batch_size number of images
# use height and width based on the trained model
# use height and width based on the generated model
test_images_path = "automl_models_is/test_images_dir/*" # replace with path to images
image_files = glob.glob(test_images_path)
img_processed_list = []
for i in range(batch_size):
img = Image.open(image_files[i])
img_processed_list.append(preprocess(img, height_onnx, width_onnx))
if len(img_processed_list) > 1:
img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
img_data = img_processed_list[0]
else:
img_data = None
assert batch_size == img_data.shape[0]
Inferência com o ONNX Runtime
Realizar inferência com o ONNX Runtime é diferente em cada tarefa de pesquisa visual computacional.
def get_predictions_from_ONNX(onnx_session, img_data):
"""Perform predictions with ONNX runtime
:param onnx_session: onnx model session
:type onnx_session: class InferenceSession
:param img_data: pre-processed numpy image
:type img_data: ndarray with shape 1xCxHxW
:return: scores with shapes
(1, No. of classes in training dataset)
:rtype: numpy array
"""
sess_input = onnx_session.get_inputs()
sess_output = onnx_session.get_outputs()
print(f"No. of inputs : {len(sess_input)}, No. of outputs : {len(sess_output)}")
# predict with ONNX Runtime
output_names = [ output.name for output in sess_output]
scores = onnx_session.run(output_names=output_names,\
input_feed={sess_input[0].name: img_data})
return scores[0]
scores = get_predictions_from_ONNX(session, img_data)
def get_predictions_from_ONNX(onnx_session,img_data):
"""Perform predictions with ONNX runtime
:param onnx_session: onnx model session
:type onnx_session: class InferenceSession
:param img_data: pre-processed numpy image
:type img_data: ndarray with shape 1xCxHxW
:return: scores with shapes
(1, No. of classes in training dataset)
:rtype: numpy array
"""
sess_input = onnx_session.get_inputs()
sess_output = onnx_session.get_outputs()
print(f"No. of inputs : {len(sess_input)}, No. of outputs : {len(sess_output)}")
# predict with ONNX Runtime
output_names = [ output.name for output in sess_output]
scores = onnx_session.run(output_names=output_names,\
input_feed={sess_input[0].name: img_data})
return scores[0]
scores = get_predictions_from_ONNX(session, img_data)
def get_predictions_from_ONNX(onnx_session, img_data):
"""perform predictions with ONNX runtime
:param onnx_session: onnx model session
:type onnx_session: class InferenceSession
:param img_data: pre-processed numpy image
:type img_data: ndarray with shape 1xCxHxW
:return: boxes, labels , scores
(No. of boxes, 4) (No. of boxes,) (No. of boxes,)
:rtype: tuple
"""
sess_input = onnx_session.get_inputs()
sess_output = onnx_session.get_outputs()
# predict with ONNX Runtime
output_names = [output.name for output in sess_output]
predictions = onnx_session.run(output_names=output_names,\
input_feed={sess_input[0].name: img_data})
return output_names, predictions
output_names, predictions = get_predictions_from_ONNX(session, img_data)
def get_predictions_from_ONNX(onnx_session,img_data):
"""perform predictions with ONNX Runtime
:param onnx_session: onnx model session
:type onnx_session: class InferenceSession
:param img_data: pre-processed numpy image
:type img_data: ndarray with shape 1xCxHxW
:return: boxes, labels , scores
:rtype: list
"""
sess_input = onnx_session.get_inputs()
sess_output = onnx_session.get_outputs()
# predict with ONNX Runtime
output_names = [ output.name for output in sess_output]
pred = onnx_session.run(output_names=output_names,\
input_feed={sess_input[0].name: img_data})
return pred[0]
result = get_predictions_from_ONNX(session, img_data)
O modelo de segmentação de instância prevê caixas, rótulos, pontuações e máscaras. O ONNX gera uma máscara prevista por instância, juntamente com as caixas delimitadoras correspondentes e a pontuação de confiança de classe. Talvez seja necessário converter a máscara binária em um polígono.
def get_predictions_from_ONNX(onnx_session, img_data):
"""Perform predictions with ONNX runtime
:param onnx_session: onnx model session
:type onnx_session: class InferenceSession
:param img_data: pre-processed numpy image
:type img_data: ndarray with shape 1xCxHxW
:return: boxes, labels , scores , masks with shapes
(No. of instances, 4) (No. of instances,) (No. of instances,)
(No. of instances, 1, HEIGHT, WIDTH))
:rtype: tuple
"""
sess_input = onnx_session.get_inputs()
sess_output = onnx_session.get_outputs()
# predict with ONNX Runtime
output_names = [ output.name for output in sess_output]
predictions = onnx_session.run(output_names=output_names,\
input_feed={sess_input[0].name: img_data})
return output_names, predictions
output_names, predictions = get_predictions_from_ONNX(session, img_data)
Aplique softmax() sobre os valores previstos para obter as pontuações de confiança de classificação (probabilidades) de cada classe. Em seguida, a previsão será a classe com a maior probabilidade.
conf_scores = torch.nn.functional.softmax(torch.from_numpy(scores), dim=1)
class_preds = torch.argmax(conf_scores, dim=1)
print("predicted classes:", ([(class_idx.item(), classes[class_idx]) for class_idx in class_preds]))
Esta etapa é diferente da classificação de várias classes. Você precisa aplicar o sigmoid nos logits (saída do ONNX) para obter as pontuações de confiança da classificação de imagem com vários rótulos.
Sem PyTorch
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# we apply a threshold of 0.5 on confidence scores
score_threshold = 0.5
conf_scores = sigmoid(scores)
image_wise_preds = np.where(conf_scores > score_threshold)
for image_idx, class_idx in zip(image_wise_preds[0], image_wise_preds[1]):
print('image: {}, class_index: {}, class_name: {}'.format(image_files[image_idx], class_idx, classes[class_idx]))
Com PyTorch
# we apply a threshold of 0.5 on confidence scores
score_threshold = 0.5
conf_scores = torch.sigmoid(torch.from_numpy(scores))
image_wise_preds = torch.where(conf_scores > score_threshold)
for image_idx, class_idx in zip(image_wise_preds[0], image_wise_preds[1]):
print('image: {}, class_index: {}, class_name: {}'.format(image_files[image_idx], class_idx, classes[class_idx]))
Para classificação de várias classes e de vários rótulos, você pode seguir as mesmas etapas mencionadas anteriormente para todos os algoritmos com suporte no AutoML.
Para detecção de objetos, as previsões são automaticamente na escala de height_onnx, width_onnx. Para transformar as coordenadas de caixa previstas nas dimensões originais, você pode implementar os cálculos a seguir.
Xmin * original_width/width_onnx
Ymin * original_height/height_onnx
Xmax * original_width/width_onnx
Ymax * original_height/height_onnx
Outra opção é usar o código a seguir para adaptar as dimensões da caixa ao intervalo de [0, 1]. Isso permite que as coordenadas da caixa sejam multiplicadas com a altura e a largura das imagens originais com as respectivas coordenadas (conforme descrito na seção Visualizar previsões) para obter caixas nas dimensões originais da imagem.
def _get_box_dims(image_shape, box):
box_keys = ['topX', 'topY', 'bottomX', 'bottomY']
height, width = image_shape[0], image_shape[1]
box_dims = dict(zip(box_keys, [coordinate.item() for coordinate in box]))
box_dims['topX'] = box_dims['topX'] * 1.0 / width
box_dims['bottomX'] = box_dims['bottomX'] * 1.0 / width
box_dims['topY'] = box_dims['topY'] * 1.0 / height
box_dims['bottomY'] = box_dims['bottomY'] * 1.0 / height
return box_dims
def _get_prediction(boxes, labels, scores, image_shape, classes):
bounding_boxes = []
for box, label_index, score in zip(boxes, labels, scores):
box_dims = _get_box_dims(image_shape, box)
box_record = {'box': box_dims,
'label': classes[label_index],
'score': score.item()}
bounding_boxes.append(box_record)
return bounding_boxes
# Filter the results with threshold.
# Please replace the threshold for your test scenario.
score_threshold = 0.8
filtered_boxes_batch = []
for batch_sample in range(0, batch_size*3, 3):
# in case of retinanet change the order of boxes, labels, scores to boxes, scores, labels
# confirm the same from order of boxes, labels, scores output_names
boxes, labels, scores = predictions[batch_sample], predictions[batch_sample + 1], predictions[batch_sample + 2]
bounding_boxes = _get_prediction(boxes, labels, scores, (height_onnx, width_onnx), classes)
filtered_bounding_boxes = [box for box in bounding_boxes if box['score'] >= score_threshold]
filtered_boxes_batch.append(filtered_bounding_boxes)
O código a seguir cria caixas, rótulos e pontuações. Use esses detalhes da caixa delimitadora para executar as mesmas etapas de pós-processamento que o modelo de Faster R-CNN.
Você pode usar as etapas mencionadas para Faster R-CNN (no caso do Mask R-CNN, cada amostra tem quatro caixas de elementos, rótulos, pontuações, máscaras) ou consultar a seção Visualizar previsões para segmentação de instâncias.