Segmentación semántica UNet de imágenes de tumores hepáticos

Tabla de contenido

1. Segmentación semántica

2. Conjunto de datos

3. Aumento de datos

Pasos de procesamiento de datos de imagen

Método de mejora de imagen CT: método de ventanas

Ecualización de histograma

Obtener profundidad de imagen de máscara

Extracción de tumores en imágenes de TC de tumores

guardar datos de tumores

 4. Carga de datos

procesamiento por lotes de datos

​Editar​Editar

Carga de conjunto de datos

  5. Construcción del modelo de red neuronal UNet

         Mapa de predicción de imagen única


1. Segmentación semántica

La tercera generación de segmentación de imágenes: segmentación semántica

        La segmentación de imágenes (Image Segmentation) es una tecnología básica importante en el campo de la visión artificial. La segmentación de imágenes es el proceso de subdividir una imagen digital en múltiples subregiones de imágenes, lo que facilita la comprensión de la imagen al simplificar o cambiar la representación de la imagen. Más simplemente, la segmentación de imágenes consiste en adjuntar una etiqueta a cada píxel de una imagen digital, de modo que los píxeles con la misma etiqueta tengan algunas características visuales comunes.

        Las modalidades (formatos) de imágenes médicas son más diversas, como rayos X, CT, MRI, ultrasonido, etc. y, por supuesto, también se incluyen algunas imágenes RGB comunes (como imágenes de fondo de retina). El énfasis en la información de las diferentes respuestas de imagen modal es diferente. Por ejemplo, la observación de rayos X de los huesos es más clara, la TC puede reflejar el sangrado de tejidos y órganos, y la RM es adecuada para la observación de tejidos blandos. Además, los resultados de formación de imágenes obtenidos por diferentes tipos de dispositivos de formación de imágenes son algo diferentes.

La primera imagen es el resultado de la visualización de un corte de imagen de TC de hígado, que se ha convertido en una imagen en escala de grises después del preprocesamiento, y la línea divisoria entre tejidos y órganos está borrosa.

La segunda imagen muestra las imágenes de tomografía computarizada del hígado de diferentes individuos, y la diferencia es enorme, lo que trae grandes dificultades para la extracción de tejido hepático.

2. Conjunto de datos

3D-IRCADb (3D Image Reconstruction for Comparison of Algorithm Database, 3D Image Reconstruction for Algorithm Database Comparison), la base de datos consta de tomografías computarizadas de 10 mujeres y 10 hombres con tumores hepáticos en el 75 % de los casos, y cada carpeta corresponde a Dependiendo del paciente, se proporcionaba información sobre la imagen, como el tamaño del hígado (ancho, profundidad, altura) o la ubicación del tumor según la segmentación de Couninurd. También muestra que la segmentación del hígado puede encontrar dificultades significativas debido al contacto con órganos vecinos, forma o densidad atípica del hígado, o incluso artefactos en la imagen.

Enlace: https://pan.baidu.com/s/1P76AF-wvrFjElc6tR82tRA 
Código de extracción: 5el7 

3. Aumento de datos

Pasos de procesamiento de datos de imagen

1. Carga de datos

2. Mejora de la imagen de TC

3. Ecualización de histograma mejorada

4. Obtener la imagen de TC correspondiente al tumor y la imagen de máscara del tumor hepático

5. Guarda la imagen

Lectura de datos DICOM

Use la biblioteca pydicom para leer el archivo, la función pydicom.dcmread lee el archivo, ordena el archivo, el atributo pixel_array extrae la información de píxeles de la imagen y muestra la imagen:

lectura de datos por lotes

# 批量读取数据
img_slices = [pydicom.dcmread(os.path.join(data_path, file_name)) for file_name in os.listdir(data_path)]
os.listdir(data_path)
# 排序,避免CT图乱序
img_slices.sort(key=lambda x:x.InstanceNumber)  # 顺序属性
img_array = np.array([i.pixel_array for i in img_slices])		# 提取像素值

 

Método de mejora de imagen CT: método de ventanas

La amplia gama de imágenes de TC conduce a un contraste deficiente y debe procesarse para órganos específicos.

El significado físico del valor CT es el grado de atenuación de la intensidad de la radiación cuando los rayos CT irradian su cuerpo y la radiación pasa a través de su cuerpo.

La característica de la TC es que puede distinguir ligeras diferencias en la densidad del tejido humano, y el estándar adoptado se determina de acuerdo con el coeficiente de absorción lineal (valor μ ) de varios tejidos a los rayos X.

Filtra las imágenes de las partes que queremos según el valor de Hu ( CT ), y todas las demás partes se oscurecen o blanquean para aumentar el contraste de la imagen. Utilice el método de ventanas. Rango de valores CT observado: ancho de ventana. El valor de CT central observado es el nivel de la ventana, y luego se binariza la parte del tumor.

def windowing(img, window_width, window_center):
    # params:需要增强的图片, 窗口宽度, 窗中心   通过窗口最小值来线性移动窗口增强
    min_windows = float(window_center)-0.5*float(window_width)
    new_img = (img-min_windows)/float(window_width)
    new_img[new_img<0] = 0			# 二值化处理 抹白
    new_img[new_img>1] = 1			# 抹黑
    return (new_img * 255).astype('uint8')  # 把数据整理成标准图像格式
img_ct = windowing(img_array, 500, 150)

Ecualización de histograma

Función de ecualización de histograma: divida la imagen completa en muchos bloques pequeños (por ejemplo, 10*10 como un bloque pequeño) y ecualice cada bloque pequeño. Es más práctico para imágenes cuyo histograma de imagen no es tan único (por ejemplo, hay picos múltiples). En 0pencv, este método es: cv2.createCLAHE()

def clahe_equalized(imgs):
    # 输入imgs的形状必须是3维
    assert (len(imgs.shape) == 3)
    # 定义均衡化函数
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    # 新数组存放均衡化后的数据
    img_res = np.zeros_like(imgs)
    for i in range(len(imgs)):
        img_res[i,:,:] = clahe.apply(np.array(imgs[i,:,:], dtype=np.uint8))
    return img_res/255

plt.hist(img_array.reshape(-1,),bins=50) # reducir a una dimensión, dividida en 50 puntos

 

Obtener profundidad de imagen de máscara

Para acelerar la velocidad de entrenamiento, la función personalizada realiza la extracción de la imagen CT correspondiente (después del procesamiento) y la imagen de máscara correspondiente del tumor hepático, y las guarda en diferentes carpetas, respectivamente, como entrada y salida del modelo. Leer imagen de TC tumoral:

tumor_slice = [pydicom.dcmread(os.path.join(data_path_mask, file_name)) for file_name in os.listdir(data_path_mask)]
#避免CT图乱序,顺序属性
tumor_slice.sort(key=lambda x: x.InstanceNumber) 
#提取像素值
tumor_array = np.array([i.pixel_array for i in tumor_slice])
print(tumor_array.shape)    # (129, 512, 512)

La parte blanca es el mapa de la máscara del tumor, y la matriz de píxeles correspondiente a la parte negra es todo 0.

Extracción de tumores en imágenes de TC de tumores

index = [i.sum() > 0 for i in tumor_array]  # 提取含肿瘤部分
# print(len(index))   # = tumor_array.shape[0] 129
# 提取掩模图的肿瘤部分
img_tumor = tumor_array[index]
# 对增强后的CT图提取肿瘤部分
img_patient = img_clahe[index]

   

guardar datos de tumores

# 设置保存文件路径
patient_save_path = r'E:/datasets/liver/tmp/patient/'
tumor_save_path = r'E:/datasets/liver/tmp/tumor/'
for path in [patient_save_path, tumor_save_path]:
    if os.path.exists(path):  # 判断文件夹是否存在
        shutil.rmtree(path)  # 清空
    os.mkdir(path)
# 保留一个肿瘤的数据
# plt.imsave(os.path.join(patient_save_path, '0.jpg'), img_patient[0],cmap='gray')
for i in range(len(img_patient)):
    plt.imsave(os.path.join(patient_save_path, f'{i}.jpg'), img_patient[i], cmap='gray')
    plt.imsave(os.path.join(tumor_save_path, f'{i}.jpg'), img_tumor[i], cmap='gray')

 4. Carga de datos

procesamiento por lotes de datos

Tome los datos en la carpeta 3dircadb1 como objeto experimental, tome los datos de los primeros 1-10 pacientes como muestras de entrenamiento y tome los datos de 10-20 pacientes como muestras de prueba.

  • 1. Leer fragmentos de imágenes CT
  • 2. Extraer valor de píxel
  • 3. Mejora y ecualización de imágenes de TC
  • 4. Mapa de máscara de tumor de procesamiento
  • 5. Ordene el mapa tumoral de cada paciente y extraiga el valor de píxel del segmento tumoral
  • 6. Extraiga el número de píxel de la parte del tumor
  • 7. Encuentre la posición correspondiente en la imagen CT
  • 8. Guardar todos los datos del tumor
def processImage(start, end):
    for num in range(start, end):
        print('正在处理第%d号病人' % num)
        data_path = fr'G:/dataPack/基于深度学习的肝脏肿瘤图像分割/3dircadb1/3dircadb1.{num}/PATIENT_DICOM'
        # 读取CT图图像片段
        image_slices = [pydicom.dcmread(os.path.join(data_path, file_name)) for file_name in os.listdir(data_path)]
        os.listdir(data_path)
        image_slices.sort(key=lambda x: x.InstanceNumber)  # 排序
        # 提取像素值
        image_array = np.array([i.pixel_array for i in image_slices])
        # CT图增强-windowing
        img_ct = windowing(image_array, 250, 0)
        # 直方图均衡化
        img_clahe = clahe_equalized(img_ct)
        # 肿瘤掩模图处理
        livertumor_path = fr'G:/dataPack/基于深度学习的肝脏肿瘤图像分割/3dircadb1/3dircadb1.{num}/MASKS_DICOM'
        tumor_paths = [os.path.join(livertumor_path, i) for i in os.listdir(livertumor_path) if 'livertumor' in i]
        # 重新排序
        tumor_paths.sort()
        # 提取所有肿瘤数据
        j = 0
        for tumor_path in tumor_paths:
            print("正在处理第%d个肿瘤" % j)
            tumor_slices = [pydicom.dcmread(os.path.join(tumor_path, file_name)) for file_name in
                            os.listdir(tumor_path)]
            # 重新对肿瘤片段图排序
            tumor_slices.sort(key=lambda x: x.InstanceNumber)
            # 提取像素值
            tumor_array = np.array([i.pixel_array for i in tumor_slices])
            # 没有肿瘤的掩模图全为黑色,对应像素全为0
            index = [i.sum() > 0 for i in tumor_array]  # 提取肿瘤部分编号
            img_tumor = tumor_array[index]
            # 对增强后的CT图提取肿瘤
            img_patient = img_clahe[index]
            # 保存所有肿瘤数据
            for i in range(len(img_patient)):
                plt.imsave(os.path.join(patient_save_path, f'{num}_{j}_{i}.jpg'), img_patient[i], cmap='gray')  # 保存CT图
                plt.imsave(os.path.join(tumor_save_path, f'{num}_{j}_{i}.jpg'), img_tumor[i], cmap='gray')  # 保存肿瘤掩模图
            j += 1
    return img_patient, img_tumor

 Imagen CT mejorada e imagen de máscara tumoral guardada después del procesamiento

Carga de conjunto de datos

Defina el cargador de datos del conjunto de datos, procese aún más cada imagen de la muestra, conviértala en una matriz np y convierta las dimensiones para facilitar el entrenamiento de la red UNet, y establezca el tamaño de cada lote de datos en 2 definiendo lote_tamaño en el cargador de datos (para mejorar el entrenamiento). eficiencia desde una perspectiva informática)

transform = transforms.Compose([
    transforms.Resize((512, 512)),
    transforms.ToTensor()
])

 Primero lea el archivo local, establezca la ruta del archivo del conjunto de entrenamiento y el conjunto de prueba, y convierta el mapa del tumor en una matriz de un solo canal a través de torch.squeeze(anno_tensor).type(torch.long)

Y binarizar el mapa tumoral

# 读取之前保存的处理后的病人CT图片与肿瘤图片
train_img_patient, train_img_tumor = processImage(1, 5)
test_img_patient, test_img_tumor = processImage(5, 7)
patient_images = glob.glob(r'E:\\datasets\liver\tmp\patient\*.jpg')
tumor_images = glob.glob(r'E:\\datasets\liver\tmp\tumor\*.jpg')
train_images = [p for p in patient_images if '1_' in p]
train_labels = [p for p in tumor_images if '1_' in p]
test_images = [p for p in patient_images if '2_' in p]
test_labels = [p for p in tumor_images if '2_' in p]
train_images = np.array(train_images)
train_labels = np.array(train_labels)
test_images = np.array(test_images)
test_labels = np.array(test_labels)
# img = Image.open(train_images[1])
# plt.imshow(img)
# plt.show()

class Portrait_dataset(data.Dataset):
    def __init__(self, img_paths, anno_paths):
        self.imgs = img_paths
        self.annos = anno_paths

    def __getitem__(self, index):
        img = self.imgs[index]
        anno = self.annos[index]
        pil_img = Image.open(img)
        img_tensor = transform(pil_img)
        pil_anno = Image.open(anno)
        anno_tensor = transform(pil_anno)
        # 由于蒙版图都是黑白图,会产生channel为1的维度。经过转换后,256x256x1,这个1并不是我们需要的。
        anno_tensor = torch.squeeze(anno_tensor).type(torch.long)
        anno_tensor[anno_tensor > 0] = 1    # 语义分割。二分类。
        return img_tensor, anno_tensor

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

BATCH_SIZE = 2
train_set = Portrait_dataset(train_images, train_labels)
test_set = Portrait_dataset(test_images, test_labels)
train_dataloader = data.DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True)
test_dataloader  = data.DataLoader(test_set, batch_size=BATCH_SIZE)
img_batch, anno_batch = next(iter(train_dataloader))

Lea el conjunto de datos a través de DataSet y configure el número de imágenes de procesamiento por lotes = 2 a través de DataLoader (para mejorar la eficiencia informática)

Seleccione aleatoriamente la imagen de TC del paciente y la imagen del tumor para mostrar:

 5. Construcción del modelo de red neuronal UNet

La estructura de la red Unet es simétrica, el cuadro azul/blanco representa el mapa de características; la flecha azul representa la convolución de 3x3, que se utiliza para la extracción de características; la flecha gris representa la conexión de salto, que se utiliza para la fusión de características; la flecha roja representa agrupación, que se usa para reducir la dimensión; la flecha verde indica sobremuestreo sobremuestreo, que se usa para restaurar la dimensión; la flecha cian indica convolución 1x1, que se usa para generar el resultado .

class downSample(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(downSample, self).__init__()
        # 两层*(卷积+激活)
        self.conv_relu = nn.Sequential(
            # padding=1,希望图像经过卷积之后大小不变
            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        )
        # 下采样(池化)
        self.pool = nn.MaxPool2d(kernel_size=2)

    def forward(self, x, is_pool=True):
        if is_pool:
            x = self.pool(x)
        print("downSample forward x.shape",x.shape)
        x = self.conv_relu(x)
        print("downSample forward after conv_relu(x) x.shape",x.shape)
        return x


# 上采样模型。卷积、卷积、上采样(反卷积实现上采样)
class upSample(nn.Module):
    def __init__(self, channels):
        # 两层*(卷积层+激活层)
        super(upSample, self).__init__()
        self.conv_relu = nn.Sequential(
            nn.Conv2d(2 * channels, channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(channels, channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        )
        # 上采样激活层(ConvTransposed)将输出层的channel变成原来的一半
        self.upConv_relu = nn.Sequential(
            nn.ConvTranspose2d(channels, channels // 2,
                               kernel_size=3, stride=2,
                               padding=1, output_padding=1),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        print("upSample - forward x.shape",x.shape)
        x = self.conv_relu(x)
        x = self.upConv_relu(x)
        return x

# 创建Unet。要初始化上、下采样层,还有其他的一些层
class Unet(nn.Module):
    def __init__(self):
        super(Unet, self).__init__()
        self.down1 = downSample(3, 64)
        self.down2 = downSample(64, 128)
        self.down3 = downSample(128, 256)
        self.down4 = downSample(256, 512)
        self.down5 = downSample(512, 1024)
        self.up = nn.Sequential(
            nn.ConvTranspose2d(1024, 512,
                               kernel_size=3,
                               stride=2,
                               padding=1,
                               output_padding=1),
            nn.ReLU(inplace=True)
        )
        self.up1 = upSample(512)
        self.up2 = upSample(256)
        self.up3 = upSample(128)
        self.conv_2 = downSample(128, 64)  # 最后两层卷积
        self.last = nn.Conv2d(64, 2, kernel_size=1)  # 输出层为2分类

    def forward(self, x):
        x1 = self.down1(x, is_pool=False)
        x2 = self.down2(x1)
        x3 = self.down3(x2)
        x4 = self.down4(x3)     # ([512, 64, 64])
        print("x4.shape",x4.shape)  # x4.shape torch.Size([512, 64, 64])
        x5 = self.down5(x4)
        print("x5.shape",x5.shape)  # x5.shape torch.Size([1024, 32, 32])
        x6 = self.up(x5)
        print("x6.shape",x6.shape)  # x6.shape torch.Size([512, 64, 64])
        # 将下采用过程x4的输出与上采样过程x5的输出做一个合并
        x6 = torch.cat([x4, x6], dim=0) # dim=0
        print("x6.shape",x6.shape)  # x6.shape torch.Size([512, 128, 64])
        x7 = self.up1(x6)

        x7 = torch.cat([x3, x7], dim=0)
        x8 = self.up2(x7)
        x8 = torch.cat([x2, x8], dim=0)
        x9 = self.up3(x8)
        x9 = torch.cat([x1, x9], dim=0)
        x10 = self.conv_2(x9, is_pool=False)
        result = self.last(x10)
        return result

Gráfico de cambio de forma de imagen en red neuronal:

 Mapa de predicción de imagen única

Supongo que te gusta

Origin blog.csdn.net/qq_37504771/article/details/129291247
Recomendado
Clasificación