9.9. 语义分割和数据集

    图 9.10 语义分割中图像有关狗、猫和背景的标签

    计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(imagesegmentation)和实例分割(instancesegmentation)。我们在这里将它们与语义分割简单区分一下。

    • 图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以图9.10的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。
    • 实例分割又叫同时检测并分割(simultaneous detection andsegmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。

    语义分割的一个重要数据集叫作Pascal VOC2012[1]。为了更好地了解这个数据集,我们先导入实验所需的包或模块。

    1. %matplotlib inline
    2. import d2lzh as d2l
    3. from mxnet import gluon, image, nd
    4. from mxnet.gluon import data as gdata, utils as gutils
    5. import os
    6. import sys
    7. import tarfile

    我们下载这个数据集的压缩包到路径下。压缩包大小是2GB左右,下载需要一定时间。解压之后的数据集将会放置在../data/VOCdevkit/VOC2012路径下。

    1. In [2]:
    1. # 本函数已保存在d2lzh包中方便以后使用
    2. def download_voc_pascal(data_dir='../data'):
    3. voc_dir = os.path.join(data_dir, 'VOCdevkit/VOC2012')
    4. url = ('http://host.robots.ox.ac.uk/pascal/VOC/voc2012'
    5. '/VOCtrainval_11-May-2012.tar')
    6. sha1 = '4e443f8a2eca6b1dac8a6c57641b67dd40621a49'
    7. fname = gutils.download(url, data_dir, sha1_hash=sha1)
    8. with tarfile.open(fname, 'r') as f:
    9. f.extractall(data_dir)
    10. return voc_dir
    11.  
    12. voc_dir = download_voc_pascal()
    1. In [3]:
    1. # 本函数已保存在d2lzh包中方便以后使用
    2. def read_voc_images(root=voc_dir, is_train=True):
    3. txt_fname = '%s/ImageSets/Segmentation/%s' % (
    4. root, 'train.txt' if is_train else 'val.txt')
    5. with open(txt_fname, 'r') as f:
    6. images = f.read().split()
    7. features, labels = [None] * len(images), [None] * len(images)
    8. for i, fname in enumerate(images):
    9. features[i] = image.imread('%s/JPEGImages/%s.jpg' % (root, fname))
    10. labels[i] = image.imread(
    11. '%s/SegmentationClass/%s.png' % (root, fname))
    12. return features, labels
    13.  
    14. train_features, train_labels = read_voc_images()

    我们画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。

      1. n = 5
      2. imgs = train_features[0:n] + train_labels[0:n]
      3. d2l.show_images(imgs, 2, n);

      ../_images/chapter_computer-vision_semantic-segmentation-and-dataset_7_0.png

      接下来,我们列出标签中每个RGB颜色的值及其标注的类别。

      1. In [5]:

      有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引。

      1. In [6]:
      1. colormap2label = nd.zeros(256 ** 3)
      2. for i, colormap in enumerate(VOC_COLORMAP):
      3. colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
      4.  
      5. # 本函数已保存在d2lzh包中方便以后使用
      6. def voc_label_indices(colormap, colormap2label):
      7. colormap = colormap.astype('int32')
      8. idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256
      9. + colormap[:, :, 2])
      10. return colormap2label[idx]

      例如,第一张样本图像中飞机头部区域的类别索引为1,而背景全是0。

      1. In [7]:
      1. y = voc_label_indices(train_labels[0], colormap2label)
      2. y[105:115, 130:140], VOC_CLASSES[1]
      1. Out[7]:
      1. (
      2. [[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
      3. [0. 0. 0. 0. 0. 0. 0. 1. 1. 1.]
      4. [0. 0. 0. 0. 0. 0. 1. 1. 1. 1.]
      5. [0. 0. 0. 0. 0. 1. 1. 1. 1. 1.]
      6. [0. 0. 0. 0. 0. 1. 1. 1. 1. 1.]
      7. [0. 0. 0. 0. 1. 1. 1. 1. 1. 1.]
      8. [0. 0. 0. 0. 0. 1. 1. 1. 1. 1.]
      9. [0. 0. 0. 0. 0. 1. 1. 1. 1. 1.]
      10. [0. 0. 0. 0. 0. 0. 1. 1. 1. 1.]
      11. [0. 0. 0. 0. 0. 0. 0. 0. 1. 1.]]
      12. <NDArray 10x10 @cpu(0)>, 'aeroplane')
      1. In [8]:
      1. # 本函数已保存在d2lzh包中方便以后使用
      2. def voc_rand_crop(feature, label, height, width):
      3. feature, rect = image.random_crop(feature, (width, height))
      4. label = image.fixed_crop(label, *rect)
      5. return feature, label
      6.  
      7. imgs = []
      8. for _ in range(n):
      9. imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
      10. d2l.show_images(imgs[::2] + imgs[1::2], 2, n);

      我们通过继承Gluon提供的Dataset类自定义了一个语义分割数据集类。通过实现getitem函数,我们可以任意访问数据集中索引为idx的输入图像及其每个像素的类别索引。由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,我们还定义了函数,从而对输入图像的RGB三个通道的值分别做标准化。

      1. # 本类已保存在d2lzh包中方便以后使用
      2. class VOCSegDataset(gdata.Dataset):
      3. def __init__(self, is_train, crop_size, voc_dir, colormap2label):
      4. self.rgb_std = nd.array([0.229, 0.224, 0.225])
      5. self.crop_size = crop_size
      6. features, labels = read_voc_images(root=voc_dir, is_train=is_train)
      7. self.features = [self.normalize_image(feature)
      8. for feature in self.filter(features)]
      9. self.labels = self.filter(labels)
      10. self.colormap2label = colormap2label
      11. print('read ' + str(len(self.features)) + ' examples')
      12.  
      13. def normalize_image(self, img):
      14. return (img.astype('float32') / 255 - self.rgb_mean) / self.rgb_std
      15.  
      16. def filter(self, imgs):
      17. return [img for img in imgs if (
      18. img.shape[0] >= self.crop_size[0] and
      19. img.shape[1] >= self.crop_size[1])]
      20.  
      21. def __getitem__(self, idx):
      22. feature, label = voc_rand_crop(self.features[idx], self.labels[idx],
      23. *self.crop_size)
      24. return (feature.transpose((2, 0, 1)),
      25. voc_label_indices(label, self.colormap2label))
      26.  
      27. def __len__(self):
      28. return len(self.features)

      我们通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。假设我们指定随机裁剪的输出图像的形状为

      9.9. 语义分割和数据集 - 图4 。下面我们可以查看训练集和测试集所保留的样本个数。

      1. In [10]:
      1. crop_size = (320, 480)
      2. voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label)
      3. voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label)
      1. read 1114 examples
      2. read 1078 examples

      设批量大小为64,分别定义训练集和测试集的迭代器。

      1. In [11]:
      1. batch_size = 64
      2. num_workers = 0 if sys.platform.startswith('win32') else 4
      3. train_iter = gdata.DataLoader(voc_train, batch_size, shuffle=True,
      4. last_batch='discard', num_workers=num_workers)
      5. test_iter = gdata.DataLoader(voc_test, batch_size, last_batch='discard',
      6. num_workers=num_workers)
      1. In [12]:
      1. for X, Y in train_iter:
      2. print(X.shape)
      3. print(Y.shape)
      4. break
      • 语义分割关注如何将图像分割成属于不同语义类别的区域。
      • 语义分割的一个重要数据集叫作Pascal VOC2012。
      • 回忆“图像增广”一节中的内容。哪些在图像分类中使用的图像增广方法难以用于语义分割?

      [1] PascalVOC2012数据集。