ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

(八)计算机视觉 -- 7 语义分割和数据集

2020-11-22 15:33:49  阅读:209  来源: 互联网

标签:voc -- 语义 feature crop tf train 128 视觉


7. 语义分割和数据集

在前几节讨论的目标检测问题中,一直使用方形边界框来标注和预测图像中的目标。

本节将探讨语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。

值得一提的是,这些语义区域的标注和预测都是像素级的。

下图展示了语义分割中图像有关狗、猫和背景的标签:

由此可见,与目标检测相比,语义分割标注的像素级的边框显然更加精细。



9.1 图像分割和实例分割

计算机视觉领域还有2个与语义分割相似的重要问题,即:
图像分割(image segmentation)和实例分割(instance segmentation)。

这里将它们与语义分割进行简单的区分:

  • 图像分割
    将图像分割成若干组成区域。通常利用图像中像素之间的相关性。
    训练时无需有关图像像素的标签信息,预测时也无法保证分割出的区域具有希望得到的语义。
    例如,以上文的图像作为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。

  • 实例分割
    又称作同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。
    与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。
    如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。



9.2 Pascal VOC2012语义分割数据集

import os
import sys
import time
import requests
import tarfile

import numpy as np
import math
from tqdm import tqdm

import matplotlib.pyplot as plt
%matplotlib inline

import tensorflow as tf
from tensorflow import keras

sys.path.append("..")

语义分割的一个重要数据集叫作Pascal VOC2012。

先下载这个数据集的压缩包,再解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下。

r = requests.get("http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar")
with open("data/VOCtrainval_11-May-2012.tar", 'wb') as f:
    f.write(r.content)

def extract(tar_path, target_path):
    try:
        t = tarfile.open(tar_path)
        t.extractall(path = target_path)
        return True
    except:
        return False

extract("data/VOCtrainval_11-May-2012.tar", "data/")
True

解压后,得到数据集的不同组成部分:

! ls data/VOCdevkit/VOC2012
Annotations        JPEGImages         SegmentationObject
ImageSets          SegmentationClass

其中,ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件。

! ls data/VOCdevkit/VOC2012/ImageSets/Segmentation
train.txt    trainval.txt val.txt
! head -5 data/VOCdevkit/VOC2012/ImageSets/Segmentation/train.txt
2007_000032
2007_000039
2007_000063
2007_000068
2007_000121

JPEGImagesSegmentationClass路径下分别包含了样本的输入图像和标签。

这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。
标签中颜色相同的像素属于同一个语义类别。

具体示例如下:

root="data/VOCdevkit/VOC2012"
fname="2007_000032"

feature = np.array(Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB"))
label = np.array(Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB"))

# (h, w, c)
print(feature.shape)    
print(label.shape)
(281, 500, 3)
(281, 500, 3)
plt.imshow(feature)
plt.imshow(label)

定义read_voc_images函数,将输入图像和标签读进内存:

def read_voc_images(root="data/VOCdevkit/VOC2012", is_train=True, max_num=None):
    txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')
    
    with open(txt_fname, 'r') as f:
        # Get file name list
        images = f.read().split()
        
    if max_num is not None:
        images = images[:min(max_num, len(images))]
        
    features, labels = [None] * len(images), [None] * len(images)
    
    for i, fname in tqdm(enumerate(images)):
        features[i] = np.array(Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB"))
        labels[i] = np.array(Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB"))
        
    return features, labels # shape=(h, w, c)
voc_dir = "data/VOCdevkit/VOC2012"
train_features, train_labels = read_voc_images(voc_dir, max_num=100)

print(len(train_features))
print(len(train_labels))
100it [00:00, 176.95it/s]
100
100

绘制前5张输入图像和它们的标签:

def show_images(imgs, num_rows, num_cols, scale=5):
    figsize = (num_cols * scale, num_rows * scale)
    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    for i in range(num_rows):
        for j in range(num_cols):
            axes[i][j].imshow(imgs[i * nuarray([[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>],
       [<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>]], dtype=object)
m_cols + j])
            axes[i][j].axes.get_xaxis().set_visible(False)
            axes[i][j].axes.get_yaxis().set_visible(False)
    return axes
n = 5
imgs = train_features[0:n] + train_labels[0:n]

show_images(imgs, 2, n)
array([[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>],
       [<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>]], dtype=object)

在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。


接下来,列出标签中每个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']

由此,可以查找标签中每个像素的类别索引。

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

colormap2label = np.zeros(256 ** 3, dtype=np.uint8)

for i, colormap in enumerate(VOC_COLORMAP):
    colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
colormap2label = tf.convert_to_tensor(colormap2label)

colormap2label
def voc_label_indices(colormap, colormap2label):
    """
    convert colormap (tf image) to colormap2label (uint8 tensor).
    """
    colormap = tf.cast(colormap, dtype=tf.int32)
    idx = tf.add(tf.multiply(colormap[:, :, 0], 256), colormap[:, :, 1])
    idx = tf.add(tf.multiply(idx, 256), colormap[:, :, 2])
    idx = tf.add(idx, colormap[:, :, 2])

    return tf.gather_nd(colormap2label, tf.expand_dims(idx, -1))
y = voc_label_indices(train_labels[0], colormap2label)
y[105:115, 130:140], VOC_CLASSES[1]
(<tf.Tensor: id=641, shape=(10, 10), dtype=uint8, numpy=
 array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
        [0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
        [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
        [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
        [0, 0, 0, 0, 1, 1, 1, 1, 1, 1],
        [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
        [0, 0, 0, 0, 0, 1, 1, 1, 1, 1],
        [0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 1, 1]], dtype=uint8)>,
 'aeroplane')

9.2.1 数据预处理

在之前的章节中,通过缩放图像使其符合模型的输入形状。

然而在语义分割里,这样做需要将预测的像素类别重新映射回原始尺寸的输入图像。该种映射难以做到精确,尤其对于不同语义的分割区域。

为了避免这个问题,将图像裁剪成固定尺寸而不是缩放。具体来说,使用图像增广中的随机裁剪,对输入图像和标签裁剪相同区域。

def voc_rand_crop(feature, label, height, width):
    """
    Random crop feature (tf image) and label (tf image).
    先将channel合并,剪裁之后再分开
    """
    combined = tf.concat([feature, label], axis=2)
    
    last_label_dim = tf.shape(label)[-1]
    last_feature_dim = tf.shape(feature)[-1]
    
    combined_crop = tf.image.random_crop(combined,
                                         size=tf.concat([(height, width), [last_label_dim + last_feature_dim]], 
                                         axis=0))
    return combined_crop[:, :, :last_feature_dim], combined_crop[:, :, last_feature_dim:]
imgs = []
for _ in range(n):
    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)

show_images(imgs[::2] + imgs[1::2], 2, n)

array([[<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>],
       [<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>,
        <AxesSubplot:>]], dtype=object)

9.2.2 自定义语义分割数据集函数

自定义一个获取语义分割数据集函数getVOCSegDataset

由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数作移除。

此外,对输入图像的RGB三个通道的值分别做标准化。

def getVOCSegDataset(is_train, crop_size, voc_dir, colormap2label, max_num=None):
    """
    crop_size: (h, w)
    """
    features, labels = read_voc_images(root=voc_dir, is_train=is_train, max_num=max_num)
    
    def _filter(imgs, crop_size):
        return [img for img in imgs if (img.shape[0] >= crop_size[0] and img.shape[1] >= crop_size[1])]
    
    def _crop(features, labels):
        features_crop = []
        labels_crop = []
        for feature, label in zip(features, labels):
            feature, label = voc_rand_crop(feature, label, height=crop_size[0], width=crop_size[1])
            features_crop.append(feature)
            labels_crop.append(label)
        return features_crop, labels_crop
    
    def _normalize(feature, label):
        rgb_mean = np.array([0.485, 0.456, 0.406])
        rgb_std = np.array([0.229, 0.224, 0.225])
        label = voc_label_indices(label, colormap2label)
        feature = tf.cast(feature, tf.float32)
        feature = tf.divide(feature, 255.)
        return feature, label

    features = _filter(features, crop_size)
    labels = _filter(labels, crop_size)
    features, labels = _crop(features, labels)

    print('read ' + str(len(features)) + ' valid examples')
    dataset = tf.data.Dataset.from_tensor_slices((features, labels))
    dataset = dataset.map(_normalize)
    
    return dataset

9.2.3 数据集读取

通过自定义的getVOCSegDataset来分别创建训练集和测试集的实例。

假设指定随机裁剪的输出图像的形状为320×400,可以查看训练集和测试集所保留的样本个数:

crop_size = (320, 400)
max_num = 100

voc_train = getVOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)
voc_test = getVOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
100it [00:00, 138.95it/s]
read 77 valid examples

100it [00:00, 137.90it/s]
read 82 valid examples

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

batch_size = 64
voc_train = voc_train.batch(batch_size)
voc_test = voc_test.batch(batch_size)


for x, y in iter(voc_train):
    print(x.dtype, x.shape)
    print(y.dtype, y.shape)
    break
<dtype: 'float32'> (64, 320, 400, 3)
<dtype: 'uint8'> (64, 320, 400)




参考

《动手学深度学习》

标签:voc,--,语义,feature,crop,tf,train,128,视觉
来源: https://blog.csdn.net/m0_38111466/article/details/109921328

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有