[PytorchLearning] Tutorial de Case Nanny para la segmentación semántica de imágenes pulmonares basado en UNet

Segmentación de imágenes pulmonares basada en UNet

En términos generales, el campo de la visión artificial incluye tres tareas principales: clasificación, detección y segmentación. Entre ellos, la tarea de clasificación tiene requisitos relativamente simples para el modelo. Se ha presentado en detalle en el tutorial introductorio anterior de Pytorch. Los amigos interesados ​​pueden consultar el blog anterior; mientras que las tareas de detección y segmentación requieren características de alto nivel de múltiples escalas. información, por lo que los requisitos para la estructura del modelo también son un poco más complicados. En este artículo, presentaré principalmente todo el proceso de tareas de segmentación semántica basadas en imágenes de TC de pulmón y la red UNet. Sin más preámbulos, vayamos al grano.

1 ¿Qué es la Segmentación Semántica?

La segmentación semántica es la tecnología central en el campo de la visión por computadora.Al clasificar cada píxel en la imagen, la imagen se divide en varias regiones con categorías semánticas específicas. En términos simples, la tarea de detección de objetivos es ubicar y clasificar el primer plano (varios objetos objetivo) en la imagen y detectar objetos de instancia como gatos, gatos, perros y personas, mientras que la tarea de segmentación semántica requiere que la red detecte el objetos de primer plano en la imagen Se juzga la categoría de cada píxel y se realiza la segmentación precisa a nivel de píxel, que se usa ampliamente en el campo de la conducción automática.

2 Caso de segmentación de imagen de TC de pulmón

Para los principiantes en la segmentación semántica, la segmentación de imágenes pulmonares es de hecho un proyecto que es relativamente fácil de entender y no demasiado difícil de comenzar A continuación, presento el caso principalmente desde tres aspectos: datos, modelo, resultado y predicción.

2.1 Producción de conjuntos de datos

2.1.1 Descripción general del conjunto de datos

Esta segmentación semántica utiliza principalmente imágenes 2D, incluidas imágenes CT e imágenes de etiquetas, las cuales son imágenes de un solo canal con una resolución de 512x512 y 267 imágenes cada una. Los datos se muestran de la siguiente manera:

2.1.2 Preprocesamiento de datos

Dado que el fondo de la imagen de la etiqueta está representado por 0, la imagen del pulmón está representada por 255, pero cuando se usa la clasificación de Pytorch, las categorías deben representarse en orden desde 0 (la categoría debe ser un tensor continuo a partir de Lei, que se ha mencionado antes). Por lo tanto, necesitamos cambiar el valor 255 de la imagen del pulmón de la mesa a 1. El código principal relevante se muestra en la lista de códigos 1.

# 代码清单1
# 介绍:读入原始2D图像数据,对像素标签进行映射:0=>0  255=>1
        image = cv.imread(image_fullpath,0)
        img_array = np.asarray(image)
        for i in img_array:
            for j in i:
                if j == 255:
                   label_img.append(1)
                else:
                   label_img.append(0)
        output_img = op_dir + each_image
        label_img = np.array(label_img)
        label_img = label_img.reshape((512, 512))
        cv.imwrite(output_img, label_img)
        n = n + 1
        print("处理完成label: %d" % n)

Una vez que se completa el mapeo de valores de píxeles, el tamaño de la imagen se estandariza a través de la función de cambio de tamaño y, finalmente, se obtiene una imagen de 512 * 512 que contiene solo valores de 0 y 1 píxel. Dado que el brillo representado por 1 es muy bajo, la imagen de la etiqueta procesada aparece completamente negra a simple vista y la etiqueta de la imagen procesada se muestra en la figura.

Algunos estudiantes aquí pueden preguntar, ¿por qué no se pueden ver las imágenes de TC después del procesamiento?, ¿hay algún problema? De hecho, este no es el caso, porque necesitamos asignar valores de píxeles a 0 y 1, por lo que los píxeles de la imagen de la etiqueta solo se componen de 0 y 1. Para el ojo humano, es difícil distinguir esta sutil diferencia de valor de píxel, a menos que sus ojos sean ojos electrónicos... Si no se siente cómodo, puede seleccionar algunas imágenes de etiquetas al azar y leerlas con opencv o PIL, e imprimir los valores de píxel de las imágenes para comprobar los resultados.

2.1.3 Generar ruta de datos

Para poder leer la imagen convenientemente, necesitamos generar tres archivos txt para registrar la ruta de la imagen original y su correspondiente imagen de etiqueta (la base de procesamiento de imágenes relevante se ha mencionado en el blog anterior, y aquellos que tengan dudas pueden verificarlo salen solos). La ruta de generación de imágenes y la lista de códigos de la etiqueta correspondiente son las siguientes:

# 代码清单2
# 介绍:读入原始2D图像数据,生成路径及标签
import os

def walk_dir(dir):
    dir_list=[]
    for image in os.listdir(dir):
        dir_list.append(os.path.join(dir,image))
    return dir_list

original_dir=r'CT_image'
save_dir=r'CT_txt'
if not save_dir:
    os.mkdir(save_dir)

img_dir=os.listdir(original_dir)
img_test=walk_dir(os.path.join(original_dir,img_dir[0]))
img_test_label=walk_dir(os.path.join(original_dir,img_dir[1]))
img_t_v=walk_dir(os.path.join(original_dir,img_dir[2]))
img_t_v_label=walk_dir(os.path.join(original_dir,img_dir[3]))
img_train=img_t_v[:188]
img_val=img_t_v[188:]
img_train_label=img_t_v_label[:188]
img_val_label=img_t_v_label[188:]

# 查看每个图片与标签是否对应
# sum=0
# for index in range(len(img_train)):
#     train=img_train[index].split("\\")[-1]
#     train_label=img_train_label[index].split("\\")[-1]
#     if train==train_label:
#         print(train," ",train_label)
#         sum+=1
# print(sum)

# 将训练集写入train.txt
with open(os.path.join(save_dir, 'train.txt'), 'a')as f:
    for index in range(len(img_train)):
        f.write(img_train[index]+'\t' +img_train_label[index]+'\n')
    print("训练集及标签写入完毕")
# 将验证集写入val.txt
with open(os.path.join(save_dir, 'val.txt'), 'a')as f:
    for index in range(len(img_val)):
        f.write(img_val[index] + '\t' +img_val_label[index]  + '\n')
    print("验证集及标签写入完毕")
# 测试集
with open(os.path.join(save_dir, 'test.txt'), 'a')as f:
    for index in range(len(img_test)):
        f.write(img_test[index] + '\t' +img_test_label[index]+ '\n')

Después de la ejecución, se obtienen tres documentos de texto de train.txt, val.txt y test.txt. train y val se usan para entrenar y verificar el modelo, incluida la ruta de datos y las etiquetas; test se usa para probar el modelo, incluida solo la ruta de datos.

2.1.4 Definir conjunto de datos

En Pytorch, la red puede manejar tensores, por lo que necesitamos convertir las imágenes leídas en datos de tensores e ingresarlos en la red. Aquí usamos una biblioteca muy importante: la biblioteca torch.utils.Dataset.
Dataset es una clase contenedora, que se usa para envolver datos en una clase Dataset y luego pasarlos a DataLoader.Luego usamos la clase DataLoader para operar con los datos más rápidamente. Para heredar la clase Dataset, se deben reescribir el método __len__ y el método __getitem__. __len__ devuelve la longitud del conjunto de datos, y __getitem__ puede obtener datos por índice. Su implementación se muestra en el Listado 3.

# 代码清单3
# 介绍:将读取到的图像数据转化为张量
import torch
import numpy as np
from PIL import Image
from torch.utils.data.dataset import Dataset

def read_txt(path):
    # 读取文件
    ims, labels = [], []
    with open(path, 'r') as f:
        for line in f.readlines():
            im, label = line.strip().split("\t")
            ims.append(im)
            labels.append(label)
    return ims, labels

class UnetDataset(Dataset):
    def __init__(self, txtpath, transform):
        super().__init__()
        self.ims, self.labels = read_txt(txtpath)
        self.transform = transform
    def __getitem__(self, index):
        im_path = self.ims[index]
        label_path = self.labels[index]
        image = Image.open(im_path)
        image = self.transform(image).float().cuda()
        label = torch.from_numpy(np.asarray(Image.open(label_path), dtype=np.int32)).long().cuda()
        return image, label

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

2.2 Descripción general de la estructura de la red

La estructura de la red UNet es similar a una letra U grande: primero realice la reducción de muestreo de Conv+Pooling; luego, la deconvolución de Deconv para el muestreo ascendente, recorte el mapa de características de bajo nivel antes de la fusión y, a continuación, realice un muestreo ascendente de nuevo. Repita este proceso hasta obtener el mapa de características que genera 388 388 2 y, finalmente, el mapa de segmentos de salida se obtiene a través de softmax. A diferencia de la adición punto por punto de FCN, U-Net utiliza funciones que se unen en la dimensión del canal para formar funciones más profundas. Los detalles de la estructura de red específica no se repetirán aquí.

2.3 Resultados y predicciones

2.3.1 Transformación de resultados de predicción

La función de predicción está en el enlace para compartir al final del artículo. El código es demasiado largo y no se mostrará. Hablaré principalmente sobre la transformación de los resultados. En el proceso de producción de datos, mapeamos los datos con un valor de píxel de 255 a 1, y la predicción de la red también es 0 y 1, por lo que necesitamos convertir 1 en un valor de píxel de 255 para la salida de resultados. proceso es el siguiente.

# 代码清单4
# 介绍:将预测结果转化为实际黑白影像
def translabeltovisual(save_label, path):
    visual_img = []
    im = cv2.imread(save_label, 0)
    img_array = np.asarray(im)
    for i in img_array:
        for j in i:
            if j == 1:
                visual_img.append(255)
            else:
                visual_img.append(0)
    visual_img = np.array(visual_img)
    visual_img = visual_img.reshape((Height, Width))
    cv2.imwrite(path, visual_img)

2.3.2 Visualización de resultados

2.3.3 Explicación parcial de la función de evaluación del modelo

2.3.4

Use Tensorboard para registrar la pérdida, la precisión y el IOU durante el proceso de entrenamiento, con las rondas de entrenamiento como el eje horizontal y cada indicador como el eje vertical. Las curvas respectivas se muestran en la figura.

Se pueden sacar las siguientes conclusiones de la curva durante el proceso de entrenamiento:
Primero, la pérdida general del modelo en el proceso de entrenamiento en los datos de entrenamiento disminuye lentamente sin ninguna oscilación; sin embargo, las oscilaciones son obvias en el conjunto de verificación, lo que indica que el Los parámetros iniciales de entrenamiento no son apropiados.
En segundo lugar, la precisión en la segmentación semántica no puede representar completamente el rendimiento del algoritmo, y el rendimiento real depende más de la puntuación media de intersección y unión. Ya sea el conjunto de entrenamiento o el conjunto de verificación, la brecha entre la precisión y la puntuación de IoU es de aproximadamente 20 puntos porcentuales, por lo que la tarea de segmentación semántica no solo debe prestar atención a la precisión, sino también a la puntuación de IoU del modelo.

3 Código fuente e intercambio de datos

¡Siga la cuenta pública de WeChat "Alchemy Little Genius" y responda a CT para obtener datos de imagen y código fuente!
inserte la descripción de la imagen aquí


ENCIMA

Supongo que te gusta

Origin blog.csdn.net/weixin_43427721/article/details/125255837
Recomendado
Clasificación