[PytorchLearning] Tutorial Case Nanny para segmentação semântica de imagens pulmonares com base na UNet

Segmentação de imagens pulmonares com base na UNet

De um modo geral, o campo da visão computacional inclui três tarefas principais: classificação, detecção e segmentação. Entre eles, a tarefa de classificação tem requisitos relativamente simples para o modelo. Ela foi apresentada em detalhes no tutorial introdutório anterior do Pytorch. Amigos interessados ​​podem conferir o blog anterior; enquanto as tarefas de detecção e segmentação requerem recursos de alto nível em várias escalas. informações, então os requisitos para a estrutura do modelo também são um pouco mais complicados. Neste artigo, apresentarei principalmente todo o processo de tarefas de segmentação semântica com base em imagens de TC de pulmão e rede UNet. Sem mais delongas, vamos ao que interessa.

1 O que é Segmentação Semântica?

A segmentação semântica é a principal tecnologia no campo da visão computacional.Ao classificar cada pixel da imagem, a imagem é dividida em várias regiões com categorias semânticas específicas. Em termos leigos, a tarefa de detecção de alvo é localizar e classificar o primeiro plano (vários objetos alvo) na imagem e detectar objetos de instância, como gatos, gatos, cachorros e pessoas, enquanto a tarefa de segmentação semântica requer que a rede detecte o objetos em primeiro plano na imagem. A categoria de cada pixel é julgada e a segmentação precisa no nível do pixel é executada, o que é amplamente utilizado no campo da direção automática.

2 Estojo de segmentação de imagem de TC de pulmão

Para iniciantes em segmentação semântica, a segmentação de imagens pulmonares é de fato um projeto relativamente fácil de entender e não muito difícil de começar. Abaixo, apresento o caso principalmente de três aspectos: dados, modelo, resultado e previsão.

2.1 Produção de conjunto de dados

2.1.1 Visão geral do conjunto de dados

Essa segmentação semântica usa principalmente imagens 2D, incluindo imagens CT e imagens de rótulos, ambas imagens de canal único com resolução de 512x512 e 267 imagens cada. Os dados são exibidos da seguinte forma:

2.1.2 Pré-processamento de dados

Como o fundo na imagem do rótulo é representado por 0, a imagem do pulmão é representada por 255, mas ao usar a classificação pytorch, as categorias precisam ser representadas na ordem de 0 (a categoria precisa ser um tensor contínuo a partir de Lei, que já foi mencionado antes). Portanto, precisamos alterar o valor 255 da imagem do pulmão da mesa para 1. O código principal relevante é mostrado na 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)

Após a conclusão do mapeamento do valor do pixel, o tamanho da imagem é padronizado através da função resize e, finalmente, obtém-se uma imagem 512*512 contendo apenas valores de 0 e 1 pixel. Como o brilho representado por 1 é muito baixo, a imagem do rótulo processado aparece completamente preta a olho nu, e o rótulo da imagem processada é mostrado na figura.

Alguns alunos aqui podem perguntar: por que as imagens de TC não podem ser vistas após o processamento? Há algum problema? Na verdade, não é esse o caso, pois precisamos mapear os valores dos pixels para 0 e 1, então os pixels da imagem do rótulo são compostos apenas por 0 e 1. Para o olho humano, é difícil distinguir essa diferença sutil de valor de pixel, a menos que seus olhos sejam olhos eletrônicos. ... Se você não estiver à vontade, pode selecionar algumas imagens de rótulos aleatoriamente e lê-las com opencv ou PIL e imprimir os valores de pixel das fotos para verificar os resultados.

2.1.3 Gerar caminho de dados

Para ler a imagem convenientemente, precisamos gerar três arquivos txt para registrar o caminho da imagem original e sua imagem de etiqueta correspondente (a base de processamento de imagem relevante foi mencionada no blog anterior, e quem tiver dúvidas pode conferir sozinhos). O caminho de geração da imagem e a lista de códigos do rótulo correspondente são os seguintes:

# 代码清单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')

Após a execução, três documentos de texto train.txt, val.txt e test.txt são obtidos. train e val são usados ​​para treinar e verificar o modelo, incluindo o caminho de dados e rótulos; test é usado para testar o modelo, incluindo apenas o caminho de dados.

2.1.4 Definir conjunto de dados

No Pytorch, a rede pode lidar com tensores, então precisamos converter as imagens lidas em dados de tensor e inseri-los na rede. Aqui usamos uma biblioteca muito importante: a biblioteca arch.utils.Dataset.
Dataset é uma classe wrapper, que é usada para agrupar dados em uma classe Dataset e, em seguida, passá-los para DataLoader. Em seguida, usamos a classe DataLoader para operar os dados mais rapidamente. Para herdar a classe Dataset, o método __len__ e o método __getitem__ devem ser reescritos. __len__ retorna o comprimento do dataset, e __getitem__ pode obter dados por índice. Sua implementação é mostrada na Listagem 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 Visão geral da estrutura da rede

A estrutura da rede UNet é semelhante a uma grande letra U: primeiro execute a redução de amostragem de Conv+Pooling; depois a deconvolução de Deconv para upsampling, recorte o mapa de recursos de baixo nível antes da fusão e, em seguida, aumente a amostragem novamente. Repita este processo até obter o mapa de características que dá como saída 388 388 2 e, finalmente, o mapa de segmento de saída é obtido através do softmax. Ao contrário da adição ponto a ponto do FCN, o U-Net usa recursos para serem costurados na dimensão do canal para formar recursos mais profundos. Os detalhes da estrutura de rede específica não serão repetidos aqui.

2.3 Resultados e previsões

2.3.1 Transformação dos resultados da previsão

A função de previsão está no link de compartilhamento no final do artigo. O código é muito longo e não será exibido. Falarei principalmente sobre a transformação dos resultados. No processo de produção de dados, mapeamos os dados com um valor de pixel de 255 para 1, e a previsão da rede também é 0 e 1, então precisamos converter 1 em um valor de pixel de 255 para a saída do resultado. processo é o seguinte.

# 代码清单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 Exibição do resultado

2.3.3 Explicação parcial da função de avaliação do modelo

2.3.4

Use o Tensorboard para registrar a perda, precisão e IOU durante o processo de treinamento, com as rodadas de treinamento no eixo horizontal e cada indicador no eixo vertical.As respectivas curvas são mostradas na figura.

As seguintes conclusões podem ser tiradas da curva durante o processo de treinamento:
Primeiro, a perda geral do modelo no processo de treinamento nos dados de treinamento diminui lentamente sem nenhuma oscilação; no entanto, as oscilações são óbvias no conjunto de verificação, indicando que o parâmetros iniciais de treinamento não são apropriados.
Em segundo lugar, a precisão na segmentação semântica não pode representar totalmente o desempenho do algoritmo, e o desempenho real depende mais da interseção média e da pontuação da união. Seja o conjunto de treinamento ou o conjunto de verificação, a lacuna entre a precisão e a pontuação IoU é de cerca de 20 pontos percentuais; portanto, a tarefa de segmentação semântica não deve apenas prestar atenção à precisão, mas também à pontuação IoU do modelo.

3 Código-fonte e compartilhamento de dados

Siga a conta pública do WeChat "Alchemy Little Genius" e responda ao CT para obter dados de imagem e código-fonte!
insira a descrição da imagem aqui


SOBRE

Acho que você gosta

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