医学图像语义分割最佳⽅法的全⾯⽐较:UNet和UNet++
作者:Sergey Kolchenko
编译:ronghuaiyang
导读
在不同的任务上对⽐了UNet和UNet++以及使⽤不同的预训练编码器的效果。
介绍
语义分割是计算机视觉的⼀个问题,我们的任务是使⽤图像作为输⼊,为图像中的每个像素分配⼀个类。在语义分割的情况下,我们不关⼼是否有同⼀个类的多个实例(对象),我们只是⽤它们的类别来标记它们。有多种关于不同计算机视觉问题的介绍课程,但⽤⼀张图⽚可以总结不同的计算机视觉问题:
从技术⾓度来看,如果我们考虑语义分割问题,对于N×M×3(假设我们有⼀个RGB图像)的图像,我们希望⽣成对应的映射N×M×k(其中k是类的数量)。有很多架构可以解决这个问题,但在这⾥我想谈谈两个特定的架构,Unet和Unet++。
有许多关于Unet的评论,它如何永远地改变了这个领域。它是⼀个统⼀的⾮常清晰的架构,由⼀个编码器和⼀个解码器组成,前者⽣成图像的表⽰,后者使⽤该表⽰来构建分割。每个空间分辨率的两个映射连接在⼀起(灰⾊箭头),因此可以将图像的两种不同表⽰组合在⼀起。并且它成功了!
接下来是使⽤⼀个训练好的编码器。考虑图像分类的问题,我们试图建⽴⼀个图像的特征表⽰,这样不同的类在该特征空间可以被分开。我们可以(⼏乎)使⽤任何CNN,并将其作为⼀个编码器,从编码器中获取特征,并将其提供给我们的解码器。据我所知,Iglovikov & Shvets 使⽤了VGG11和resnet34分别为Unet解码器以⽣成更好的特征和提⾼其性能。
TernausNet (VGG11 Unet)
Unet++是最近对Unet体系结构的改进,它有多个跳跃连接。
根据论⽂, Unet++的表现似乎优于原来的Unet。就像在Unet中⼀样,这⾥可以使⽤多个编码器(⾻⼲)来为输⼊图像⽣成强特征。
我应该使⽤哪个编码器?
这⾥我想重点介绍Unet和Unet++,并⽐较它们使⽤不同的预训练编码器的性能。为此,我选择使⽤胸部x光数据集来分割肺部。这是⼀个⼆值分割,所以我们应该给每个像素分配⼀个类为“1”的概率,然后我们可以⼆值化来制作⼀个掩码。⾸先,让我们看看数据。
来⾃胸⽚X光数据集的标注数据的例⼦
这些是⾮常⼤的图像,通常是2000×2000像素,有很⼤的mask,从视觉上看,到肺不是问题。使⽤segmentation_models_pytorch 库,我们为Unet和Unet++使⽤100+个不同的预训练编码器。我们做了⼀个快速的pipeline来训练模型,使⽤Catalyst (pytorch的另⼀个库,这可以帮助你训练模型,⽽不必编写很多⽆聊的代码)和Albumentations(帮助你应⽤不同的图像转换)。
1. 定义数据集和增强。我们将调整图像⼤⼩为256×256,并对训练数据集应⽤⼀些⼤的增强。
import albumentations as A
from torch.utils.data import Dataset, DataLoader
from collections import OrderedDict
class ChestXRayDataset(Dataset):
def __init__(
self,
images,
masks,
transforms):
self.images = images
self.masks = masks
def __len__(self):
return(len(self.images))
def __getitem__(self, idx):
def __getitem__(self, idx):
"""Will load the mask, get random coordinates around/with the mask,
load the image by coordinates
"""
sample_image = imread(self.images[idx])
if len(sample_image.shape) == 3:
sample_image = sample_image[..., 0]
sample_image = np.expand_dims(sample_image, 2) / 255
sample_mask = imread(self.masks[idx]) / 255
if len(sample_mask.shape) == 3:
sample_mask = sample_mask[..., 0]
augmented = ansforms(image=sample_image, mask=sample_mask) sample_image = augmented['image']
sample_mask = augmented['mask']
splitwisesample_image = anspose(2, 0, 1) # channels first
sample_mask = np.expand_dims(sample_mask, 0)
data = {'features': torch.from_numpy(py()).float(),
'mask': torch.from_numpy(py()).float()}
return(data)
def get_valid_transforms(crop_size=256):
return A.Compose(
[
A.Resize(crop_size, crop_size),
],
p=1.0)
def light_training_transforms(crop_size=256):
return A.Compose([
A.RandomResizedCrop(height=crop_size, width=crop_size),
A.OneOf(
[
A.Transpose(),
A.VerticalFlip(),
A.HorizontalFlip(),
A.RandomRotate90(),
A.NoOp()
], p=1.0),
]
)
def medium_training_transforms(crop_size=256):
return A.Compose([
A.RandomResizedCrop(height=crop_size, width=crop_size),
A.OneOf(
[
A.Transpose(),
A.VerticalFlip(),
A.HorizontalFlip(),
A.RandomRotate90(),
A.NoOp()
]
, p=1.0),
A.OneOf(
[
A.CoarseDropout(max_holes=16, max_height=16, max_width=16), A.NoOp()
], p=1.0),
])
def heavy_training_transforms(crop_size=256):
return A.Compose([
A.RandomResizedCrop(height=crop_size, width=crop_size),
A.OneOf(
[
A.Transpose(),
A.VerticalFlip(),
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论