语义分割
2024-04-10 10:10:35  阅读数 377

(一)语义分割和数据集

(1)什么是语义分割?

语义分割将图片的每一个像素分类到对应的类别。神经网络能够在像素级别上能够将图片的每一个像素分类,即对每一个像素点分类。


应用:背景虚化、无人驾驶的路面分割。

另一个应用是实例分割,这个技术和语义分割很相似。但是他在语义的基础上加上了不仅要区分类,还要把列里面的实例标注出来。例如一张图片里有猫有狗,实例分割能知道有两只不同的狗和一只猫

(2)语义分割的数据集

最重要的语义分割的数据集之一是Pascal VOC2012

(二)代码实现加载数据集

关于数据集可以直接用浏览器下载,也可以通过代码下载
下载地址:http://d2l-data.s3-accelerate.amazonaws.com/VOCtrainval_11-May-2012.tar

如果你想知道语义分割的具体代码可以看我后续的文章:
【全连接卷积神经网络】:https://www.jianshu.com/p/c12882cd99b1

%matplotlib inline 
import os 
import torch 
import torchvision 
from d2l import torch as d2l 

# 下载数据集
if os.path.isdir('../data/VOCdevkit/'):
    voc_dir = "../data/VOCdevkit/VOC2012"
else: 
    d2l.DATA_HUB['voc2012'] = (d2l.DATA_URL + 'VOCtrainval_11-May-2012.tar',
                           '4e443f8a2eca6b1dac8a6c57641b67dd40621a49')

    voc_dir = d2l.download_extract('voc2012', 'VOCdevkit/VOC2012')
# 由于对图片中的而每一个像素都要进行标号的话,所以label不能像以前一样,
# 他的做法是将所有的标号转换成一种颜色,存成一张图片
# 这个数据集中图片和label是不同文件夹下的同名文件下,文件名存储在.txt文件中
# 使用png格式是为了label不被压缩。
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() # f.read().split()把文件内容当成一个列表返回
    features, labels = [],[]
    for i ,name in enumerate(images):
        features.append(torchvision.io.read_image(os.path.join(voc_dir,'JPEGImages',f'{name}.jpg')))
        labels.append(torchvision.io.read_image(os.path.join(voc_dir,'SegmentationClass',f'{name}.png'),mode))
    return features, labels

train_features, train_labels = read_voc_images(voc_dir,is_train=True)
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)
# 列举RGB颜色值和类名
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]]

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']
# 查找标签中的每个像素的类索引,如果说每次都遍历数组的话是一件非常耗时的事情
# 所以这里构建了一个类似dictionary的数组
# 下标:256颜色,值:对应的label
def voc_colormap2label():
    """构建从RGB到VOC类别索引的映射"""
    # 一维向量
    colormap2label = torch.zeros(256**3,dtype=torch.long)
    for i, colormap in enumerate(VOC_COLORMAP):
        # 这就是256进制转十进制,可以这么理解
        colormap2label[(colormap[0]*256+colormap[1])*256 + colormap[2]] = i
    return colormap2label

# 这里是给你一个分类后的像素点,获取label
def voc_label_indices(colormap, colormap2label):
    """将VOC标签中的RGB值映射到他们的类别索引"""
    # 因为一般卷积的输入都是,通道x高x宽,图象是高x宽x通道,这里把卷积输入的格式变成图象格式
    colormap = colormap.permute(1,2,0).numpy().astype('int32')
    # print(colormap.shape) # (281, 500, 3),方便计算下标
    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2])
    # print(idx.shape) # (281, 500)
    return colormap2label[idx]

y = voc_label_indices(train_labels[0], voc_colormap2label())
print(len(y))
y[105:115, 130:140], VOC_CLASSES[1]
y = voc_label_indices(train_labels[0], voc_colormap2label())
print(train_labels[0].shape) # torch.Size([3, 281, 500])
print(len(y)) # 281
y[105:115, 130:140], VOC_CLASSES[1]
# 使用图片增广,裁剪,翻转,变换颜色等
# 这里需要注意的是,这里是语义分割,如果我们对图片进行了裁剪,那么我们也需要对他的label和边缘框进行裁剪
# 参数列表(需要裁剪的img,需要裁剪的label,目标高,目标宽)
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 = []
# n=5
# 随机裁剪5个观察结果
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]
# 因为他输出的是feature,label,所需需要隔一个跳一个
d2l.show_images(imgs[::2] + imgs[1::2], 2, n)
d2l.show_images(imgs,2,n)
# 构建数据集
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')

    # 因为模型使用resnet,所以沿用了这个
    def normalize_image(self, img):
        return self.transform(img.float() / 255)

    # 过滤器,过滤那些宽高比size小的图片。因为我们不能使用resize,因为label不能resiaze
    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])]

    # 重写__getitem__方法,每次调用获取feature和label
    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)
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=0)
for X, Y in train_iter:
    print(X.shape)
    print(Y.shape)
    break
# 整合所有组件
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

训练语义分割的网络:
https://www.jianshu.com/p/c12882cd99b1