Detección paso a paso de objetos individuales usando PyTorch

1. Descripción

        En la tarea de detección de objetos, queremos encontrar la ubicación de un objeto en una imagen. Podemos buscar un tipo de objeto (detección de un solo objeto, como se muestra en este tutorial) o múltiples objetos (detección de múltiples objetos). Por lo general, usamos un cuadro delimitador para definir la ubicación de un objeto. Hay varias formas de representar cuadros delimitadores:

  1. Puntos de ancho y alto para la esquina superior izquierda: [x0, y0, w, h], donde x0 es el lado izquierdo del cuadro, y0 es la parte superior del cuadro y w y h son el ancho y el alto del cuadro, respectivamente.
  2. Puntos superior izquierdo e inferior derecho: [x0, y0, x1, y1], donde x0 es el lado izquierdo de la caja, y0 es la parte superior de la caja, x1 es el lado derecho de la caja e y1 es la parte inferior de la caja.
  3. Punto central con ancho y alto: [xc, yc, w, h], donde xc es la coordenada x del centro del cuadro, yc es la coordenada y del centro del cuadro, y w y h son el ancho y el alto del cuadro , respectivamente.

Foto de Indiana Barriopedro de Pexels , editada por el autor.

        En este tutorial, nos centraremos en encontrar el centro de la fóvea en imágenes oculares médicas del concurso iChallenge-AMD .

2. Obtener datos

        Usaremos imágenes de los ojos de pacientes con degeneración macular relacionada con la edad (DMAE).

Imágenes oculares del conjunto de datos de AMD

        Hay dos fuentes principales de las que se pueden obtener datos. El primero es el sitio web iChallenge-AMD  https://amd.grand-challenge.org/ . Primero debe registrarse para la competencia y luego puede descargar los datos. La segunda forma no requiere registro, se   descarga desde https://ai.baidu.com/broad/download . Aquí debe descargar las "imágenes [entrenamiento] y etiquetas AMD" de las imágenes y los "discos [entrenamiento] y anotaciones fóvea" de los archivos de Excel con etiquetas.

        Después de descargar y extraer los datos, debe tener una carpeta  Training400 que contenga las subcarpetas AMD (que contiene 89 imágenes) y no  AMD (que contiene 311 imágenes) y un  archivo de Excel Fovea_ubicación.xlsx que contiene la posición del centro cóncavo central.

3. Explora datos

Primero carguemos el archivo Excell usando Pandas

从 pathlib 导入  路径
导入熊猫作为 pd

path_to_parent_dir = 路径('.')
path_to_labels_file = path_to_parent_dir / 'Training400' /'Fovea_location.xlsx'labels_df = pd.read_excel(path_to_labels_file, index_col='ID')print('Head')print(labels_df.head()) # 显示 excell 文件中
的前 5 行 print('\nTail'

)


print(labels_df.tail()) # 显示 excell 文件中的最后 5 行

imprimir el resultado del marco de datos

Vemos que la tabla consta de cuatro columnas:

  • ID: usaremos esto como un índice en el marco de datos
  • imgName: el nombre de la imagen. Notamos que la imagen con AMD comienza con A, mientras que la imagen sin AMD comienza con N.
  • Fovea_X — coordenada x del centroide de la fóvea en la imagen
  • Fovea_Y — Coordenada y del centroide de la fóvea en la imagen

Podemos trazar el centroide de la fóvea en la imagen para tener una idea de la distribución de las ubicaciones de la fóvea.

%matplotlib inline # 如果使用 Jupyter notebook 或 Colab
 import seaborn as sns
 import matplotlib.pyplot as  plt

plt.rcParams['figure.figsize'] = (10, 6)amd_or_non_amd = ['AMD' if name.startswith('A') 

else 'Non-AMD' for name inlabels_df.imgName]
sns.scatterplot(x='Fovea_X', y='Fovea_Y', hue=amd_or_non_amd, data=labels_df, alpha=0.7)

Podemos ver dos grupos principales de ubicaciones foveales, pero lo que es más importante, para algunas imágenes, la etiqueta para el centroide foveal es (0, 0). Sería mejor eliminar estas imágenes del marco de datos.

labels_df = labels_df[(labels_df[['Fovea_X', 'Fovea_Y']] != 0)。all(axis=1)]
amd_or_non_amd = ['AMD' if name.startswith('A') else 'Non-AMD' for name in labels_df.imgName]

Ahora queremos mirar una muestra aleatoria de imágenes y marcar el centro de la fóvea. Para hacer esto, definamos una función para cargar una imagen con etiquetas y otra función para dibujar un cuadro delimitador alrededor de la fóvea basado en las etiquetas.

从 PIL import Image, ImageDraw

def 导入 numpy 作为 np
 load_image_with_label(labels_df, id):
 image_name = labels_df.loc[id, 'imgName']
 data_type = 'AMD' 如果 image_name.startswith('A') else 'Non-AMD'
 image_path = path_to_ parent_dir / 'Training400' / data_type / image_name 图像 =
 图像。open(image_path) label = (labels_df.loc[id, 'Fovea_X'], labels_df.loc[id, 'Fovea_Y'])

 返回图像, label def show_image_with_bounding_box(图像, 标签, w_h_bbox=(50, 50), 厚度=2):
 W, h =
 w_h_bbox c_x , c_y = label


image = image.copy() ImageDraw.Draw(image).rectangle(((c_x-w//2, c_y-h//2), (c_x+w//2, c_y+h//2)), outline='green', width=thick) plt.imshow(image)

Tomamos muestras aleatorias de seis imágenes y las mostramos.

rng = np.random.default_rng(42) # 创建具有种子的生成器对象 42 n_rows = 2 # 图像子图中的行数 n_cols = 3 # # 图像子图中
的列数 索引 = rng.choice(labels_df.index, 
n_rows * 
n_cols)

对于 ii, 枚举中的 id (索引, 1):
image, label = load_image_with_label(labels_df, id) plt.subplot(n_rows, n_cols, ii) show_image_with_bounding_box(image, label, (250, 250), 20)


 plt.title(labels_df.loc[id, 'imgName'])

Lo primero que debemos notar de la imagen de arriba es que el tamaño de la imagen es diferente para diferentes imágenes. Ahora queremos entender la distribución de tamaños de imagen. Para esto, recopilamos la altura y el ancho de las imágenes en el conjunto de datos.

heights = []widths = []for image_name, data_type in zip(labels_df['imgName']


, amd_or_non_amd):
 image_path = path_to_parent_dir / 'Training400' / data_type / image_name
 h, w = Image。open(image_path).size
 heights.append(h) widths.append(w)sns.histplot(x=heights, hue=amd_or_non_amd)

sns.histplot(x=widths, hue=amd_or_non_amd) 

4. Aumento y transformación de datos

        El aumento de datos es un paso muy importante que nos permite expandir el conjunto de datos (especialmente cuando tenemos un conjunto de datos pequeño, como en nuestro caso) y hacer que la red sea más robusta. También queremos aplicar algunas transformaciones para que la entrada de la red sea consistente (en nuestro caso, necesitamos cambiar el tamaño de las imágenes para que tengan dimensiones constantes).

        Además de la mejora y transformación de imágenes, también debemos cuidar las etiquetas. Por ejemplo, si volteamos la imagen verticalmente, el centroide de la fóvea obtendrá nuevas coordenadas que debemos actualizar. Para actualizar las transformaciones de etiquetas e imágenes, escribiremos algunas clases de transformación nosotros mismos.

import torch
import torchvision.transforms.functional as tf

class Resize:
  '''Resize the image and convert the label
     to the new shape of the image'''
  def __init__(self, new_size=(256, 256)):
    self.new_width = new_size[0]
    self.new_height = new_size[1]

  def __call__(self, image_label_sample):
    image = image_label_sample[0]
    label = image_label_sample[1]
    c_x, c_y = label
    original_width, original_height = image.size
    image_new = tf.resize(image, (self.new_width, self.new_height))
    c_x_new = c_x * self.new_width /original_width
    c_y_new = c_y * self.new_height / original_height
    return image_new, (c_x_new, c_y_new)


class RandomHorizontalFlip:
  '''Horizontal flip the image with probability p.
     Adjust the label accordingly'''
  def __init__(self, p=0.5):
    if not 0 <= p <= 1:
      raise ValueError(f'Variable p is a probability, should be float between 0 to 1')
    self.p = p  # float between 0 to 1 represents the probability of flipping

  def __call__(self, image_label_sample):
    image = image_label_sample[0]
    label = image_label_sample[1]
    w, h = image.size
    c_x, c_y = label
    if np.random.random() < self.p:
      image = tf.hflip(image)
      label = w - c_x, c_y
    return image, label


class RandomVerticalFlip:
  '''Vertically flip the image with probability p.
    Adjust the label accordingly'''
  def __init__(self, p=0.5):
    if not 0 <= p <= 1:
      raise ValueError(f'Variable p is a probability, should be float between 0 to 1')
    self.p = p  # float between 0 to 1 represents the probability of flipping

  def __call__(self, image_label_sample):
    image = image_label_sample[0]
    label = image_label_sample[1]
    w, h = image.size
    c_x, c_y = label
    if np.random.random() < self.p:
      image = tf.vflip(image)
      label = c_x, h - c_y
    return image, label


class RandomTranslation:
  '''Translate the image by randomaly amount inside a range of values.
     Translate the label accordingly'''
  def __init__(self, max_translation=(0.2, 0.2)):
    if (not 0 <= max_translation[0] <= 1) or (not 0 <= max_translation[1] <= 1):
      raise ValueError(f'Variable max_translation should be float between 0 to 1')
    self.max_translation_x = max_translation[0]
    self.max_translation_y = max_translation[1]

  def __call__(self, image_label_sample):
    image = image_label_sample[0]
    label = image_label_sample[1]
    w, h = image.size
    c_x, c_y = label
    x_translate = int(np.random.uniform(-self.max_translation_x, self.max_translation_x) * w)
    y_translate = int(np.random.uniform(-self.max_translation_y, self.max_translation_y) * h)
    image = tf.affine(image, translate=(x_translate, y_translate), angle=0, scale=1, shear=0)
    label = c_x + x_translate, c_y + y_translate
    return image, label


class ImageAdjustment:
  '''Change the brightness and contrast of the image and apply Gamma correction.
     No need to change the label.'''
  def __init__(self, p=0.5, brightness_factor=0.8, contrast_factor=0.8, gamma_factor=0.4):
    if not 0 <= p <= 1:
      raise ValueError(f'Variable p is a probability, should be float between 0 to 1')
    self.p = p
    self.brightness_factor = brightness_factor
    self.contrast_factor = contrast_factor
    self.gamma_factor = gamma_factor

  def __call__(self, image_label_sample):
    image = image_label_sample[0]
    label = image_label_sample[1]

    if np.random.random() < self.p:
      brightness_factor = 1 + np.random.uniform(-self.brightness_factor, self.brightness_factor)
      image = tf.adjust_brightness(image, brightness_factor)

    if np.random.random() < self.p:
      contrast_factor = 1 + np.random.uniform(-self.brightness_factor, self.brightness_factor)
      image = tf.adjust_contrast(image, contrast_factor)

    if np.random.random() < self.p:
      gamma_factor = 1 + np.random.uniform(-self.brightness_factor, self.brightness_factor)
      image = tf.adjust_gamma(image, gamma_factor)

    return image, label

class ToTensor:
  '''Convert the image to a Pytorch tensor with
     the channel as first dimenstion and values 
     between 0 to 1. Also convert the label to tensor
     with values between 0 to 1'''
  def __init__(self, scale_label=True):
    self.scale_label = scale_label

  def __call__(self, image_label_sample):
    image = image_label_sample[0]
    label = image_label_sample[1]
    w, h = image.size
    c_x, c_y = label

    image = tf.to_tensor(image)

    if self.scale_label:
      label = c_x/w, c_y/h
    label = torch.tensor(label, dtype=torch.float32)

    return image, label


class ToPILImage:
  '''Convert a tensor image to PIL Image. 
     Also convert the label to a tuple with
     values with the image units'''
  def __init__(self, unscale_label=True):
    self.unscale_label = unscale_label

  def __call__(self, image_label_sample):
    image = image_label_sample[0]
    label = image_label_sample[1].tolist()

    image = tf.to_pil_image(image)
    w, h = image.size

    if self.unscale_label:
      c_x, c_y = label
      label = c_x*w, c_y*h

    return image, label

Probemos una nueva transformación. Creamos objetos para cada clase de transformación y los conectamos usando torchvision. Luego aplicamos la transformación completa a la imagen etiquetada.Compose

from torchvision.transforms import Compose
image, label = load_image_with_label(labels_df, 1)
transformation = Compose([Resize(), RandomHorizontalFlip(), RandomVerticalFlip(), RandomTranslation(), ImageAdjustment(), ToTensor()])
new_image, new_label = transformation((image, label))
print(f'new_im type {new_image.dtype}, shape = {new_image.shape}')
print(f'{new_label=}')

# new_im type torch.float32, shape = torch.Size([3, 256, 256]
# new_label=tensor([0.6231, 0.3447])

Obtuvimos el resultado esperado. También queremos convertir el nuevo tensor en una imagen PIL y volver a convertir la etiqueta en coordenadas de imagen para que podamos mostrarla usando nuestro método show.

new_image, new_label = ToPILImage()((new_image, new_label))
show_image_with_bounding_box(new_image, new_label)

5. Crear conjunto de datos y cargador de datos

        Para cargar datos en nuestro modelo, primero debemos crear una clase de conjunto de datos personalizada (que es una subclase de la clase de conjunto de datos PyTorch). Para hacer esto, necesitamos implementar tres métodos:

  • __init__()- Construir e inicializar el objeto del conjunto de datos.
  • __getitem__()- maneja la forma en que podemos indexar imágenes y etiquetas de todo el conjunto de datos
  • __len__()- devuelve la longitud del conjunto de datos que tenemos
import torch
from torch.utils.data import Dataset, DataLoader

device = 'cuda' if torch.cuda.is_available() else 'cpu'

class AMDDataset(Dataset):
  def __init__(self, data_path, labels_df, transformation):
    self.data_path = Path(data_path)
    self.labels_df = labels_df.reset_index(drop=True)
    self.transformation = transformation

  def __getitem__(self, index):
    image_name = self.labels_df.loc[index, 'imgName']
    image_path = self.data_path / ('AMD' if image_name.startswith('A') else 'Non-AMD') / image_name
    image = Image.open(image_path)
    label = self.labels_df.loc[index, ['Fovea_X','Fovea_Y']].values.astype(float)
    image, label = self.transformation((image, label))
    return image.to(device), label.to(device)

  def __len__(self):
    return len(self.labels_df)

Antes de crear realmente el objeto del conjunto de datos, necesitamos dividir los datos en conjuntos de entrenamiento y validación. Solemos dividirlo en un marco de datos de entrenamiento y un marco de datos de validación.scikit-learnlabels_df

from sklearn.model_selection import train_test_split
labels_df_train, labels_df_val = train_test_split(labels_df, test_size=0.2, shuffle=True, random_state=42)

train_transformation = Compose([Resize(), RandomHorizontalFlip(), RandomVerticalFlip(), RandomTranslation(), ImageAdjustment(), ToTensor()])
val_transformation = Compose([Resize(), ToTensor()])

train_dataset = AMDDataset('Training400', labels_df_train, train_transformation)
val_dataset = AMDDataset('Training400', labels_df_val, val_transformation)

Podemos inspeccionar nuestro objeto de conjunto de datos mostrando una imagen de muestra.

image, label = train_dataset[0]
show_image_with_bounding_box(*(ToPILImage()((image, label))))

image, label = val_dataset[0]
show_image_with_bounding_box(*(ToPILImage()((image, label))))

El siguiente paso es definir un cargador de datos, uno para el conjunto de datos de entrenamiento y otro para el conjunto de datos de validación.

train_dataloader = DataLoader(train_dataset, batch_size=8)
val_dataloader = DataLoader(val_dataset, batch_size=16)

No tenemos que barajar en DataLoader porque ya barajamos los datos cuando los dividimos en conjuntos de datos de entrenamiento y validación. Ahora echemos un vistazo a un lote y veamos si los resultados son los esperados.

image_batch, labels_batch = next(iter(train_dataloader))
print(image_batch.shape, image_batch.dtype)
print(labels_batch, labels_batch.dtype)

# torch.Size([8, 3, 256, 256]) torch.float32
# tensor([[0.4965, 0.3782],
#        [0.6202, 0.6245],
#         [0.5637, 0.4887],
#         [0.5114, 0.4908],
#         [0.3087, 0.4657],
#         [0.5330, 0.5309],
#         [0.6800, 0.6544],
#         [0.5828, 0.4034]], device='cuda:0') torch.float32

6. Construye un modelo

        Queremos construir un modelo que tome una imagen RGB redimensionada y devuelva dos valores para las coordenadas x e y. Usaremos bloques residuales de manera similar a ResNet con conexiones de salto. Comenzamos definiendo el rebloqueo básico

from torch.nn.modules.batchnorm import BatchNorm2d
import torch.nn as nn

class ResBlock(nn.Module):
  def __init__(self, in_channels, out_channels):
    super().__init__()
    self.base1 = nn.Sequential(
        nn.Conv2d(in_channels, in_channels, kernel_size=3, padding='same'),
        nn.BatchNorm2d(in_channels),
        nn.ReLU(True) 
    )
    self.base2 = nn.Sequential(
        nn.Conv2d(in_channels, out_channels, kernel_size=3, padding='same'),
        nn.BatchNorm2d(out_channels),
        nn.ReLU(True)
    )

  def forward(self, x):
    x = self.base1(x) + x
    x = self.base2(x)
    return x

Este bloque tiene dos pasos. Primero, utiliza capas convolucionales, seguido de normalización por lotes y ReLU. Luego, agregamos la entrada original al resultado y aplicamos un segundo paso, que nuevamente consta de capas convolucionales, seguidas de normalización por lotes y ReLU, pero esta vez cambiamos la cantidad de filtros. Ahora, estamos listos para construir el modelo.

class FoveaNet(nn.Module):
  def __init__(self, in_channels, first_output_channels):
    super().__init__()
    self.model = nn.Sequential(
        ResBlock(in_channels, first_output_channels),
        nn.MaxPool2d(2),
        ResBlock(first_output_channels, 2 * first_output_channels),
        nn.MaxPool2d(2),
        ResBlock(2 * first_output_channels, 4 * first_output_channels),
        nn.MaxPool2d(2),
        ResBlock(4 * first_output_channels, 8 * first_output_channels),
        nn.MaxPool2d(2),
        nn.Conv2d(8 * first_output_channels, 16 * first_output_channels, kernel_size=3),
        nn.MaxPool2d(2),
        nn.Flatten(),
        nn.Linear(7 * 7 * 16 * first_output_channels, 2)
    )
  
  def forward(self, x):
    return self.model(x)

Podemos ver mejor nuestros modelos usando el paquetetorchinfo

! pip install torchinfo -q  # install torchinfo
from torchinfo import summary
net = FoveaNet(3, 16)

summary(model=net, 
        input_size=(8, 3, 256, 256), # (batch_size, color_channels, height, width)
        col_names=["input_size", "output_size", "num_params"],
        col_width=20,
        row_settings=["var_names"]
)

7. Pérdida y optimizador

        Primero definimos la función de pérdida utilizando la pérdida suave L1  . En general, esta pérdida se comporta como L1 cuando la diferencia absoluta es menor que 2, y como L1 en caso contrario.

loss_func = nn.SmoothL1Loss()

        Para el optimizador, usaremos Adam.

optimizer = torch.optim.Adam(net.parameters(), lr=1e-4)

        Como métrica de rendimiento, utilizamos la métrica "Intersección sobre unión" (IoU). Esta métrica calcula la relación entre la intersección de dos cuadros delimitadores y su unión.

        Primero, necesitamos definir una función que tome el centroide como entrada y devuelva un cuadro delimitador de la forma [x0, y0, x1, y1] como salida

def centroid_to_bbox(centroids, w=50/256, h=50/256):
  x0_y0 = centroids - torch.tensor([w/2, h/2]).to(device)
  x1_y1 = centroids + torch.tensor([w/2, h/2]).to(device)
  return torch.cat([x0_y0, x1_y1], dim=1)

        y una función para calcular el IoU de un lote de etiquetas

from torchvision.ops import box_iou
def iou_batch(output_labels, target_labels):
  output_bbox = centroid_to_bbox(output_labels)
  target_bbox = centroid_to_bbox(target_labels)
  return torch.trace(box_iou(output_bbox, target_bbox)).item()

A continuación, definimos una función de pérdida para el procesamiento por lotes.

def batch_loss(loss_func, output, target, optimizer=None):
  loss = loss_func(output, target)
  with torch.no_grad():
    iou_metric = iou_batch(output, target)
  if optimizer is not None:
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
  return loss.item(), iou_metric

8. Modelo de formación

        En este paso, entrenaremos al modelo para encontrar la fóvea. Primero definimos una función de ayuda para realizar el paso de entrenamiento, lo que significa iterar a través de todos los datos en el cargador de datos, usando nuestra función anterior para obtener la pérdida (y actualizar los pesos en el caso de entrenamiento) y realizar un seguimiento de la pérdida y IoU métrica.batch_loss

def train_val_step(dataloader, model, loss_func, optimizer=None):
  if optimizer is not None:
    model.train()
  else:
    model.eval()

  running_loss = 0
  running_iou = 0
  
  for image_batch, label_batch in dataloader:
    output_labels = model(image_batch)
    loss_value, iou_metric_value = batch_loss(loss_func, output_labels, label_batch, optimizer)
    running_loss += loss_value
    running_iou += iou_metric_value
  
  return running_loss/len(dataloader.dataset), running_iou/len(dataloader.dataset)

Ahora tenemos todo lo que necesitamos para entrenar. Definimos dos diccionarios para realizar un seguimiento de la pérdida y las métricas de IoU para el entrenamiento y la validación después de cada época. También guardamos los pesos del modelo que dieron mejores resultados.

num_epoch = 100
loss_tracking = {'train': [], 'val': []}
iou_tracking = {'train': [], 'val': []}
best_loss = float('inf')

model = FoveaNet(3, 16).to(device)
loss_func = nn.SmoothL1Loss(reduction="sum")
optimizer = torch.optim.Adam(model.parameters(), lr=5e-5)


for epoch in range(num_epoch):
  print(f'Epoch {epoch+1}/{num_epoch}')

  training_loss, trainig_iou = train_val_step(train_dataloader, model, loss_func, optimizer)
  loss_tracking['train'].append(training_loss)
  iou_tracking['train'].append(trainig_iou)

  with torch.inference_mode():
    val_loss, val_iou = train_val_step(val_dataloader, model, loss_func, None)
    loss_tracking['val'].append(val_loss)
    iou_tracking['val'].append(val_iou)
    if val_loss < best_loss:
      print('Saving best model')
      torch.save(model.state_dict(), 'best_model.pt')
      best_loss = val_loss
  
  print(f'Training loss: {training_loss:.6}, IoU: {trainig_iou:.2}')
  print(f'Validation loss: {val_loss:.6}, IoU: {val_iou:.2}')

Tracemos la pérdida promedio y el IoU promedio por época como una función de la época.

plt.plot(range(1, num_epoch+1), loss_tracking['train'], label='train')
plt.plot(range(1, num_epoch+1), loss_tracking['val'], label='validation')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend()

plt.plot(range(1, num_epoch+1), iou_tracking['train'], label='train')
plt.plot(range(1, num_epoch+1), iou_tracking['val'], label='validation')
plt.xlabel('epoch')
plt.ylabel('iou')
plt.legend()

Finalmente, queremos ver algunas imágenes para ver qué tan cerca están las predicciones del modelo de las verdaderas coordenadas de la fóvea. Para hacer esto, definimos una nueva función basada en la función anterior, pero esta vez dibujamos cuadros delimitadores para la predicción (verde) y el objetivo (rojo).show_image_with_bounding_box

def show_image_with_2_bounding_box(image, label, target_label, w_h_bbox=(50, 50), thickness=2):
  w, h = w_h_bbox
  c_x , c_y = label
  c_x_target , c_y_target = target_label
  image = image.copy()
  ImageDraw.Draw(image).rectangle(((c_x-w//2, c_y-h//2), (c_x+w//2, c_y+h//2)), outline='green', width=thickness)
  ImageDraw.Draw(image).rectangle(((c_x_target-w//2, c_y_target-h//2), (c_x_target+w//2, c_y_target+h//2)), outline='red', width=thickness)
  plt.imshow(image)

Ahora cargamos el mejor modelo que obtuvimos y hacemos predicciones en una muestra de imágenes y vemos los resultados

model.load_state_dict(torch.load('best_model.pt'))
model.eval()
rng = np.random.default_rng(0)  # create Generator object with seed 0
n_rows = 2  # number of rows in the image subplot
n_cols = 3  # # number of cols in the image subplot
indexes = rng.choice(range(len(val_dataset)), n_rows * n_cols, replace=False)

for ii, id in enumerate(indexes, 1):
  image, label = val_dataset[id]
  output = model(image.unsqueeze(0))
  iou = iou_batch(output, label.unsqueeze(0))
  _, label = ToPILImage()((image, label))
  image, output = ToPILImage()((image, output.squeeze()))
  plt.subplot(n_rows, n_cols, ii)
  show_image_with_2_bounding_box(image, output, label)
  plt.title(f'{iou:.2f}')

9. Conclusión

        En este tutorial, hemos cubierto todos los pasos principales necesarios para construir una red para una tarea de detección de un solo objeto. Primero exploramos los datos, los limpiamos y organizamos, luego construimos funciones de aumento de datos junto con objetos Dataset y DataLoader, y finalmente construimos y entrenamos el modelo. Obtuvimos resultados relativamente buenos y le invitamos a intentar mejorar el rendimiento cambiando la arquitectura y los parámetros aprendidos del modelo.

Supongo que te gusta

Origin blog.csdn.net/gongdiwudu/article/details/132248188
Recomendado
Clasificación