(一)语义分割和数据集
(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
网友评论