Visión artificial: teoría y práctica de la segmentación semántica

segmentación semántica

La segmentación semántica se refiere a la tarea de dividir una imagen en varias regiones y asignar etiquetas semánticas a cada región. Es una tecnología importante en la visión por computadora y se usa ampliamente en áreas como la conducción autónoma, el análisis de imágenes médicas y los sistemas de información geográfica.

A diferencia de las tareas tradicionales de segmentación de imágenes, la segmentación semántica no solo necesita dividir la imagen en varias regiones, sino que también debe asignar etiquetas semánticas a cada región. Por ejemplo, en la conducción autónoma, la segmentación semántica puede segmentar áreas como carreteras, vehículos y peatones, y asignar etiquetas semánticas correspondientes a cada área para ayudar a los vehículos a conducir de forma autónoma. En el análisis de imágenes médicas, la segmentación semántica permite segmentar diferentes estructuras tisulares (como órganos, músculos, huesos, etc.) y analizarlas y diagnosticarlas.

Los métodos de implementación de la segmentación semántica se pueden dividir principalmente en métodos basados ​​en regiones y métodos basados ​​en píxeles. Los métodos basados ​​en regiones segmentan una imagen en una serie de regiones y luego clasifican cada región y asignan una etiqueta semántica correspondiente. Los métodos basados ​​en píxeles clasifican directamente cada píxel y le asignan una etiqueta semántica correspondiente. En la actualidad, el método basado en aprendizaje profundo se ha convertido en el principal método de segmentación semántica, entre los cuales el método basado en redes neuronales convolucionales (CNN) es el más común. Los modelos de segmentación semántica de uso común incluyen FCN, SegNet, U-Net, DeepLab, etc.

En el campo de la visión por computadora, hay otros dos problemas importantes similares a la segmentación semántica, a saber, la segmentación de imágenes y la segmentación de instancias. Los distinguimos brevemente de la segmentación semántica aquí.
La segmentación de imágenes divide una imagen en varias regiones de componentes, y los métodos para este tipo de problema suelen aprovechar la correlación entre los píxeles de la imagen. No requiere información de etiqueta sobre los píxeles de la imagen durante el entrenamiento, ni puede garantizar que las regiones segmentadas tengan la semántica que queremos al predecir.
La segmentación de instancias también se denomina detección y segmentación simultáneas, y estudia cómo identificar las regiones a nivel de píxeles de cada instancia de destino en una imagen. A diferencia de la segmentación semántica, la segmentación de instancias debe distinguir no solo la semántica sino también las diferentes instancias de destino. Por ejemplo, si hay dos perros en la imagen, la segmentación de instancias debe distinguir a cuál de los dos perros pertenece el píxel.



conjunto de datos

Uno de los conjuntos de datos más importantes para la segmentación semántica es Pascal VOC2012.

descargar conjunto de datos

%matplotlib inline
import os
import torch
import torchvision
from d2l import torch as d2l
#@save
d2l.DATA_HUB['voc2012'] = (d2l.DATA_URL + 'VOCtrainval_11-May-2012.tar',
                           '4e443f8a2eca6b1dac8a6c57641b67dd40621a49')

voc_dir = d2l.download_extract('voc2012', 'VOCdevkit/VOC2012')

leer conjunto de datos

#@save
def read_voc_images(voc_dir, is_train=True):
    """读取所有VOC图像并标注"""
    txt_fname = os.path.join(voc_dir, 'ImageSets', 'Segmentation',
                             'train.txt' if is_train else 'val.txt')
    mode = torchvision.io.image.ImageReadMode.RGB
    with open(txt_fname, 'r') as f:
        images = f.read().split()
    features, labels = [], []
    for i, fname in enumerate(images):
        features.append(torchvision.io.read_image(os.path.join(
            voc_dir, 'JPEGImages', f'{
      
      fname}.jpg')))
        labels.append(torchvision.io.read_image(os.path.join(
            voc_dir, 'SegmentationClass' ,f'{
      
      fname}.png'), mode))
    return features, labels

train_features, train_labels = read_voc_images(voc_dir, True)

A continuación trazamos las primeras 5 imágenes de entrada y sus etiquetas. En las imágenes de etiquetas, el blanco y el negro representan el borde y el fondo, respectivamente, mientras que otros colores corresponden a diferentes categorías.

n = 5
imgs = train_features[0:n] + train_labels[0:n]
imgs = [img.permute(1,2,0) for img in imgs]
d2l.show_images(imgs, 2, n);

inserte la descripción de la imagen aquí
A continuación, enumeramos los valores de color RGB y los nombres de clase.

VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
                [0, 64, 128]]

#@save
VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
               'diningtable', 'dog', 'horse', 'motorbike', 'person',
               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']

Con las dos constantes definidas anteriormente, podemos buscar convenientemente el índice de clase de cada píxel en la etiqueta. Definimos la función voc_colormap2label para construir un mapeo de los valores de color RGB anteriores a índices de categoría, y la función voc_label_indices para mapear valores RGB a índices de categoría en el conjunto de datos Pascal VOC2012.

#@save
def voc_colormap2label():
    """构建从RGB到VOC类别索引的映射"""
    colormap2label = torch.zeros(256 ** 3, dtype=torch.long)
    for i, colormap in enumerate(VOC_COLORMAP):
        colormap2label[
            (colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    return colormap2label

Específicamente, el código utiliza un método para convertir valores de color RGB en un número entero único, es decir, los valores de los tres canales de R, G y B se multiplican por 25 6 2 256^ 225 62 ,25 6 1 256^125 61 ,25 6 0 256^025 60 y súmalos. Esto convierte un triplete de valores de color RGB en un número entero único.

#@save
def voc_label_indices(colormap, colormap2label):
    """将VOC标签中的RGB值映射到它们的类别索引"""
    colormap = colormap.permute(1, 2, 0).numpy().astype('int32')
    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256
           + colormap[:, :, 2])
    return colormap2label[idx]

Específicamente, la función primero convierte los valores RGB en el tensor "mapa de colores" a un tensor entero "idx" de tamaño (H, W). Esto es multiplicando los valores de los tres canales R, G y B por 25 6 2 256^225 62 ,25 6 1 256^125 61 ,25 6 0 256^025 60 y luego súmalos. Este entero se usa luego como un índice para extraer el índice de clase correspondiente del tensor "colormap2label". Estos índices de categoría forman un nuevo tensor "idx" de la misma forma que el tensor "mapa de colores", donde cada elemento corresponde a un píxel en el tensor "mapa de colores".

inserte la descripción de la imagen aquí

Preprocesar datos

En la segmentación semántica, hacerlo requiere reasignar las clases de píxeles pronosticadas al tamaño original de la imagen de entrada. Tal mapeo puede no ser lo suficientemente preciso, especialmente en diferentes regiones segmentadas semánticamente. Para evitar este problema, recortamos la imagen a un tamaño fijo en lugar de cambiar la escala. Específicamente, recortamos la misma región de la imagen de entrada y la etiquetamos mediante el recorte aleatorio en el aumento de imágenes.

#@save
def voc_rand_crop(feature, label, height, width):
    """随机裁剪特征和标签图像"""
    rect = torchvision.transforms.RandomCrop.get_params(
        feature, (height, width))
    feature = torchvision.transforms.functional.crop(feature, *rect)
    label = torchvision.transforms.functional.crop(label, *rect)
    return feature, label

imgs = []
for _ in range(n):
    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)

imgs = [img.permute(1, 2, 0) for img in imgs]
d2l.show_images(imgs[::2] + imgs[1::2], 2, n);

La función usa torchvision.transforms.RandomCrop.get_params() para obtener un rectángulo de recorte aleatorio y luego usa la función torchvision.transforms.function.crop() para recortar la imagen de la entidad de entrada y etiquetar la imagen. Finalmente, la función devuelve la imagen de la característica recortada y la imagen de la etiqueta. Específicamente, la función logra un recorte aleatorio a través de los siguientes pasos:

  1. Use torchvision.transforms.RandomCrop.get_params() para obtener un rectángulo de recorte aleatorio con alto y ancho respectivamente.
  2. Utilice torchvision.transforms.function.crop() para recortar la imagen de la entidad de entrada y etiquetar la imagen, y el rectángulo recortado es el rectángulo aleatorio obtenido en el paso anterior.
  3. Devuelve la imagen de la característica recortada y la imagen de la etiqueta.

Clase de conjunto de datos de segmentación semántica personalizada

#@save
class VOCSegDataset(torch.utils.data.Dataset):
    """一个用于加载VOC数据集的自定义数据集"""

    def __init__(self, is_train, crop_size, voc_dir):
        self.transform = torchvision.transforms.Normalize(
            mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
        self.crop_size = crop_size
        features, labels = read_voc_images(voc_dir, is_train=is_train)
        self.features = [self.normalize_image(feature)
                         for feature in self.filter(features)]
        self.labels = self.filter(labels)
        self.colormap2label = voc_colormap2label()
        print('read ' + str(len(self.features)) + ' examples')

    def normalize_image(self, img):
        return self.transform(img.float() / 255)

    def filter(self, imgs):
        return [img for img in imgs if (
            img.shape[1] >= self.crop_size[0] and
            img.shape[2] >= self.crop_size[1])]

    def __getitem__(self, idx):
        feature, label = voc_rand_crop(self.features[idx], self.labels[idx],
                                       *self.crop_size)
        return (feature, voc_label_indices(label, self.colormap2label))

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

Esta es una clase de conjunto de datos personalizado VOCSegDataset para cargar conjuntos de datos Pascal VOC. La clase de conjunto de datos proporciona los siguientes métodos:

init (self, is_train, crop_size, voc_dir): constructor, utilizado para inicializar el conjunto de datos. La función acepta tres parámetros:

  1. is_train: un booleano que indica si este conjunto de datos es para entrenamiento o prueba.
  2. crop_size: una tupla que indica el tamaño de la imagen recortada al azar.
  3. voc_dir: ruta de almacenamiento del conjunto de datos.

normalize_image(self, img): Se utiliza para normalizar la imagen.

filter(self, imgs): se usa para filtrar imágenes cuyo tamaño es más pequeño que crop_size.

getitem (self, idx): se utiliza para obtener un elemento en el conjunto de datos. Esta función acepta un parámetro de índice idx y devuelve una tupla (característica, etiqueta), donde la característica representa la imagen de la característica y la etiqueta representa la imagen de la etiqueta correspondiente.

len (auto): se utiliza para obtener el número de muestras en el conjunto de datos.

En el constructor, la clase del conjunto de datos primero usa la función read_voc_images() para leer las imágenes y las etiquetas en el conjunto de datos, y usa el método filter() para filtrar las imágenes cuyo tamaño es menor que crop_size. Luego, la clase de conjunto de datos usa el método normalize_image() para normalizar cada imagen característica y usa los métodos voc_rand_crop() y voc_label_indices() para lograr un recorte aleatorio y el mapeo de etiquetas de imágenes características e imágenes de etiquetas. Finalmente, la clase de conjunto de datos devuelve la característica recortada y etiqueta las imágenes en el método getitem ().

La función principal de esta clase de conjunto de datos es convertir el conjunto de datos VOC de Pascal en el tipo de conjunto de datos en PyTorch, de modo que la clase DataLoader proporcionada por PyTorch se pueda usar para procesar por lotes los datos al entrenar el modelo.

crop_size = (320, 480)
voc_train = VOCSegDataset(True, crop_size, voc_dir)
voc_test = VOCSegDataset(False, crop_size, voc_dir)
batch_size = 64
train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
                                    drop_last=True,
                                    num_workers=d2l.get_dataloader_workers())
for X, Y in train_iter:
    print(X.shape)
    print(Y.shape)
    break

Integrar todos los componentes

#@save
def load_data_voc(batch_size, crop_size):
    """加载VOC语义分割数据集"""
    voc_dir = d2l.download_extract('voc2012', os.path.join(
        'VOCdevkit', 'VOC2012'))
    num_workers = d2l.get_dataloader_workers()
    train_iter = torch.utils.data.DataLoader(
        VOCSegDataset(True, crop_size, voc_dir), batch_size,
        shuffle=True, drop_last=True, num_workers=num_workers)
    test_iter = torch.utils.data.DataLoader(
        VOCSegDataset(False, crop_size, voc_dir), batch_size,
        drop_last=True, num_workers=num_workers)
    return train_iter, test_iter

Red completamente convolucional

El modelo totalmente convolucional es una red neuronal convolucional especial que puede clasificar o segmentar una imagen de entrada a nivel de píxel y generar un resultado de segmentación con el mismo tamaño que la imagen de entrada. Un modelo completamente convolucional generalmente consta de una capa convolucional, una capa deconvolucional y una capa de agrupación, donde la capa convolucional se usa para extraer características de la imagen, la capa deconvolucional se usa para restaurar la imagen de características al tamaño de imagen original y la agrupación La capa se utiliza para reducir el tamaño de las imágenes de características.

Los modelos totalmente convolucionales se utilizan con mayor frecuencia para tareas de segmentación de imágenes, como la segmentación semántica, la segmentación de instancias y la detección de bordes, entre otras. Entre ellas, la tarea de segmentación semántica consiste en asignar cada píxel de la imagen a su categoría correspondiente, mientras que la tarea de segmentación de instancias consiste en asignar cada instancia de objeto de la imagen a una categoría diferente.

Una estructura clásica del modelo totalmente convolucional es U-Net, que consta de dos partes: un codificador y un decodificador. Los codificadores generalmente emplean una combinación de capas convolucionales y de agrupación para extraer características de la imagen y reducir el tamaño de las imágenes de características. El decodificador utiliza capas de deconvolución y omite conexiones para restaurar la imagen característica al tamaño de imagen original y fusiona la imagen característica en el codificador con la imagen característica en el decodificador.

Cuando se entrena un modelo completamente convolucional, la función de pérdida de entropía cruzada generalmente se usa para medir la diferencia entre la salida del modelo y la etiqueta verdadera, y el algoritmo de retropropagación se usa para actualizar los parámetros del modelo. Dado que la salida de un modelo totalmente convolucional es una imagen segmentada, generalmente debe aplanarse en un vector al calcular la función de pérdida.

modelo de construcción

Echemos un vistazo al diseño más básico de un modelo de red totalmente convolucional. Como se muestra en la figura a continuación, la red convolucional completa primero usa la red neuronal convolucional para extraer las características de la imagen y luego pasa 1 × 1 1\times 11×1 La capa convolucional transforma la cantidad de canales en la cantidad de categorías y, finalmente, transforma la altura y el ancho del mapa de características en el tamaño de la imagen de entrada a través de la capa convolucional transpuesta. Por lo tanto, la salida del modelo tiene la misma altura y anchura que la imagen de entrada, y el canal de salida final contiene la predicción de clase para el píxel en esa ubicación espacial.
inserte la descripción de la imagen aquí
A continuación, usamos el modelo ResNet-18 preentrenado en el conjunto de datos de ImageNet para extraer las características de la imagen y denotamos esta red como pretrained_net. Las últimas capas del modelo ResNet-18 incluyen capas de agrupación promedio global y capas totalmente conectadas, mientras que no se requieren en redes totalmente convolucionales.
inserte la descripción de la imagen aquí
A continuación, creamos una red de red totalmente convolucional. Reproduce la mayoría de las capas previas al entrenamiento en ResNet-18, excepto la capa de agrupación promedio global al final y la capa totalmente conectada más cercana a la salida.

net = nn.Sequential(*list(pretrained_net.children())[:-2])

Dada una entrada con una altura de 320 y un ancho de 480, la propagación directa de net reduce la altura y el ancho de la entrada al original 1 32 \frac{1}{32}321, inmediatamente 10 y 15.

X = torch.rand(size=(1, 3, 320, 480))
net(X).shape

inserte la descripción de la imagen aquí
Siguiente uso 1 × 1 1\times 11×1 capa convolucional convierte la cantidad de canales de salida en la cantidad de clases (21 clases) del conjunto de datos Pascal VOC2012. Finalmente, necesitamos aumentar la altura y el ancho del mapa de características por un factor de 32, cambiándolo de nuevo a la altura y el ancho de la imagen de entrada. Como( 320 − 64 + 16 ∗ 2 + 32 ) / 32 = 10 (320-64+16*2+32)/32=10( 32064+dieciséis2+32 ) /32=10 y( 480 − 64 + 16 ∗ 2 + 32 ) / 32 = 15 (480-64+16*2+32)/32=15( 48064+dieciséis2+32 ) /32=15 , construimos una zancada de32 3232 capa de convolución transpuesta y establezca la altura y el ancho del kernel de convolución en64 6464 , lleno de16 1616 _ Podemos ver que si la zancada es sss , lleno des / 2 s/2s /2 (asumiendosss es un número entero) y la altura y el ancho del kernel de convolución son2 s 2s2 s , el núcleo de convolución transpuesto amplificará la altura y el ancho de entrada enssveces .

Inicializar la capa de convolución transpuesta

En el procesamiento de imágenes, a veces necesitamos ampliar la imagen, es decir, aumentar la resolución. La interpolación bilineal es uno de los métodos de muestreo superior comúnmente utilizados y también se usa a menudo para inicializar capas convolucionales transpuestas.

El sobremuestreo es un método para aumentar una imagen o señal de baja resolución a una resolución alta. La interpolación bilineal es uno de los métodos de muestreo superior comúnmente utilizados, que pueden generar imágenes de mayor resolución mediante la interpolación de imágenes existentes de baja resolución.

En la interpolación bilineal, se supone que una imagen de baja resolución se muestreará dos veces, es decir, cada píxel se convierte en un 2 × 2 2 \times 22×Bloques de 2 píxeles. Para los píxeles en cada nuevo bloque de píxeles, la interpolación bilineal se calcula a partir de los cuatro valores de valores de píxeles conocidos circundantes. Específicamente, para un nuevo píxel ( x , y ) en la imagen de destino(x, y)( X ,y ) , su valor de gris se puede calcular mediante los siguientes pasos:

Encuentra el píxel de destino ( x , y ) (x, y)( X ,y ) corresponde a cuatro píxeles ( x 1 , y 1 ) (x_1, y_1)en la imagen original de baja resolución( X1,y1)( x 2 , y 1 ) (x_2, y_1)( X2,y1)( x 1 , y 2 ) (x_1, y_2)( X1,y2)( x 2 , y 2 ) (x_2, y_2)( X2,y2) , en la que( x 1 , y 1 ) (x_1, y_1)( X1,y1) ( x 2 , y 2 ) (x_2, y_2) ( X2,y2) es el más cercano a( x , y ) (x, y)( X ,y ) ,( x 1 , y 2 ) (x_1, y_2)( X1,y2) ( x 2 , y 1 ) (x_2, y_1) ( X2,y1) son los otros dos píxeles.

Calcular píxel objetivo ( x , y ) (x, y)( X ,y ) y la distancia entre cuatro píxeles conocidos, a saber,d 1 = ( x − x 1 ) 2 + ( y − y 1 ) 2 d_{1} = \sqrt{(x-x_1)^2 + (y -y_1) ^ 2}d1=( XX1)2+( yy1)2 re 2 = ( x − x 2 ) 2 + ( y − y 1 ) 2 d_{2} = \sqrt{(x-x_2)^2 + (y-y_1)^2}d2=( XX2)2+( yy1)2 re 3 = ( x − x 1 ) 2 + ( y − y 2 ) 2 d_{3} = \sqrt{(x-x_1)^2 + (y-y_2)^2}d3=( XX1)2+( yy2)2 re 4 = ( x − x 2 ) 2 + ( y − y 2 ) 2 d_{4} = \sqrt{(x-x_2)^2 + (y-y_2)^2}d4=( XX2)2+( yy2)2

Calcular píxel objetivo ( x , y ) (x, y)( X ,y ) , el valor de gris de los cuatro píxeles circundantes se usa para el promedio ponderado, y el peso es inversamente proporcional a la distancia entre el píxel objetivo y los cuatro píxeles conocidos. Ahora mismo:

F ( X , y ) = 1 re 1 re 3 F ( X 1 , y 1 ) + 1 re 2 re 3 F ( X 2 , y 1 ) + 1 re 1 re 4 F ( X 1 , y 2 ) + 1 re 2 re 4 f ( x 2 , y 2 ) f(x, y) = \frac{1}{d_{1}d_{3}}f(x_1, y_1) + \frac{1}{d_{2 }d_{3}}f(x_2, y_1) + \frac{1}{d_{1}d_{4}}f(x_1, y_2) + \frac{1}{d_{2}d_{4}} f(x_2, y_2)f ( x ,y )=d1d31f ( x1,y1)+d2d31f ( x2,y1)+d1d41f ( x1,y2)+d2d41f ( x2,y2)

donde f ( x 1 , y 1 ) f(x_1, y_1)f ( x1,y1)f ( x 2 , y 1 ) f(x_2, y_1)f ( x2,y1) ,f ( x 1 , y 2 ) f(x_1, y_2)f ( x1,y2)f ( x 2 , y 2 ) f(x_2, y_2)f ( x2,y2) representan respectivamente los valores de gris de cuatro píxeles conocidos.

Con la interpolación bilineal, podemos aumentar la muestra de una imagen de baja resolución a una resolución más alta, lo que da como resultado una imagen más nítida.

def bilinear_kernel(in_channels, out_channels, kernel_size):
    factor = (kernel_size + 1) // 2
    if kernel_size % 2 == 1:
        center = factor - 1
    else:
        center = factor - 0.5
    og = (torch.arange(kernel_size).reshape(-1, 1),
          torch.arange(kernel_size).reshape(1, -1))
    filt = (1 - torch.abs(og[0] - center) / factor) * \
           (1 - torch.abs(og[1] - center) / factor)
    weight = torch.zeros((in_channels, out_channels,
                          kernel_size, kernel_size))
    weight[range(in_channels), range(out_channels), :, :] = filt
    return weight

Esta es una función para generar un núcleo de convolución de interpolación bilineal. Su entrada incluye el número de canales de entrada, el número de canales de salida y el tamaño del kernel de convolución, y su salida es un tensor de forma (in_channels, out_channels, kernel_size, kernel_size) que representa el kernel de convolución de interpolación bilineal generado por esta función .

Específicamente, la función primero calcula la posición del centro del kernel de convolución y luego genera un filtro de tensor de forma (kernel_size, kernel_size), donde cada elemento de filt representa el peso de la posición correspondiente en el kernel de convolución de interpolación bilineal. Finalmente, la función genera un tensor de peso de forma (in_channels, out_channels, kernel_size, kernel_size) según el número de canales de entrada y de salida, donde cada elemento de peso representa el peso de la posición correspondiente en el kernel de convolución de interpolación bilineal. Específicamente, peso[i, j, :, :] representa el kernel de interpolación bilineal desde el i-ésimo canal de entrada hasta el j-ésimo canal de salida.

Esta función se puede utilizar para definir una capa convolucional de interpolación bilineal en una red neuronal convolucional, que aumenta la muestra del tensor de entrada a una resolución más alta.

Las redes totalmente convolucionales inicializan capas convolucionales transpuestas con muestreo ascendente con interpolación bilineal. Para 1 × 1 1\veces 11×1 capa convolucional, usamos los parámetros de inicialización de Xavier.

W = bilinear_kernel(num_classes, num_classes, 64)
net.transpose_conv.weight.data.copy_(W);

tren

def loss(inputs, targets):
    return F.cross_entropy(inputs, targets, reduction='none').mean(1).mean(1)

num_epochs, lr, wd, devices = 5, 0.001, 1e-3, d2l.try_all_gpus()
trainer = torch.optim.SGD(net.parameters(), lr=lr, weight_decay=wd)
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)

predecir

Al predecir, necesitamos normalizar la imagen de entrada en cada canal y convertirla al formato de entrada de cuatro dimensiones requerido por la red neuronal convolucional.

def predict(img):
    X = test_iter.dataset.normalize_image(img).unsqueeze(0)
    pred = net(X.to(devices[0])).argmax(dim=1)
    return pred.reshape(pred.shape[1], pred.shape[2])

Para visualizar la clase pronosticada para cada píxel, asignamos las clases pronosticadas a sus colores etiquetados en el conjunto de datos.

def label2image(pred):
    colormap = torch.tensor(VOC_COLORMAP, device=devices[0])
    X = pred.long()
    return colormap[X, :]

Las imágenes en el conjunto de datos de prueba varían en tamaño y forma. Dado que el modelo utiliza una capa de convolución transpuesta con un paso de 32, cuando la altura o el ancho de la imagen de entrada no se pueden dividir entre 32, la altura o el ancho de la salida de la capa de convolución transpuesta se desviará del tamaño de la imagen de entrada. . Para resolver este problema, podemos interceptar múltiples áreas rectangulares en la imagen cuya altura y ancho sean múltiplos enteros de 32, y realizar la propagación hacia adelante en los píxeles de estas áreas respectivamente. Tenga en cuenta que la unión de estas regiones debe cubrir completamente la imagen de entrada. Cuando un píxel está cubierto por múltiples regiones, el valor promedio de la salida de la capa convolucional transpuesta en la propagación directa de diferentes regiones se puede usar como entrada de la operación softmax para predecir la categoría.

Para simplificar, solo leemos algunas imágenes de prueba más grandes y comenzamos desde la esquina superior izquierda de la imagen para interceptar la forma de 320 × 480 320\times 480320×Se utiliza un área de 480 para la predicción. Para estas imágenes de prueba, imprimimos sus regiones interceptadas una por una, luego imprimimos los resultados de la predicción y finalmente imprimimos las categorías etiquetadas.

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_51957239/article/details/131058002
Recomendado
Clasificación