1289766752

  • 2

    获得赞
  • 2

    发布的文章
  • 0

    答辩的项目

PyTorch 深度学习:FCN 做语义分割

深度学习

最后更新 2020-07-16 10:58 阅读 5893

最后更新 2020-07-16 10:58

阅读 5893

深度学习

语义分割是一种像素级别的处理图像方式,对比于目标检测其更加精确,能够自动从图像中划分出对象区域并识别对象区域中的类别  在 2015 年 CVPR 的一篇论文 Fully Convolutional Networks for Semantic Segmentation 这篇文章提出了全卷积的概念,第一次将端到端的卷积网络推广到了语义分割的任务当中,随后出现了很多基于 FCN 实现的网络结构,比如 U-Net 等。  数据集 首先我们需要下载数据集,这里我们使用 PASCAL VOC 数据集,其是一个正在进行的目标检测,目标识别,语义分割的挑战,我们可以进行数据集的下载 下载完成数据集之后进行解压,我们可以再 ImageSets/Segmentation/train.txt 和 ImageSets/Segmentation/val.txt 中找到我们的训练集和验证集的数据,图片存放在 /JPEGImages 中,后缀是 .jpg,而 label 存放在 /SegmentationClass 中,后缀是 .png 我们可以可视化一下 

# 导入需要的包
import os
import torch
import numpy as np
from torch.autograd import Variable
from torch import nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from PIL import Image
from mxtorch import transforms as tfs
from datetime import datetime
 
import matplotlib.pyplot as plt
%matplotlib inline
 
im_show1 = Image.open('./dataset/VOCdevkit/VOC2012/JPEGImages/2007_005210.jpg') label_show1 = Image.open('./dataset/VOCdevkit/VOC2012/SegmentationClass/2007_005210.png').convert('RGB') im_show2 = Image.open('./dataset/VOCdevkit/VOC2012/JPEGImages/2007_000645.jpg') label_show2 = Image.open('./dataset/VOCdevkit/VOC2012/SegmentationClass/2007_000645.png').convert('RGB')
 
_, figs = plt.subplots(2, 2, figsize=(10, 8))
figs[0][0].imshow(im_show1)
figs[0][0].axes.get_xaxis().set_visible(False)
figs[0][0].axes.get_yaxis().set_visible(False)
figs[0][1].imshow(label_show1)
figs[0][1].axes.get_xaxis().set_visible(False)
figs[0][1].axes.get_yaxis().set_visible(False)
figs[1][0].imshow(im_show2)
figs[1][0].axes.get_xaxis().set_visible(False)
figs[1][0].axes.get_yaxis().set_visible(False)
figs[1][1].imshow(label_show2)
figs[1][1].axes.get_xaxis().set_visible(False)
figs[1][1].axes.get_yaxis().set_visible(False)
print(im_show1.size)
print(im_show2.size)

image.png首先输出图片的大小,左边就是真实的图片,右边就是分割之后的结果 然后我们定义一个函数进行图片的读入,根据 `train.txt` 和 `val.txt` 中的文件名进行图片读入,我们不需要这一步就读入图片,只需要知道图片的路径,之后根据图片名称生成 batch 的时候再读入图片,并做一些数据预处理 

voc_root = './dataset/VOCdevkit/VOC2012'
 
def read_images(root=voc_root, train=True):
    txt_fname = root + '/ImageSets/Segmentation/' + ('train.txt' if train else 'val.txt')
    with open(txt_fname, 'r') as f:
        images = f.read().split()
    data = [os.path.join(root, 'JPEGImages', i+'.jpg') for i in images]
    label = [os.path.join(root, 'SegmentationClass', i+'.png') for i in images]
    return data, label

可能你已经注意到了前面展示的两张图片的大小是不一样的,如果我们要使用一个 batch 进行计算,我们需要图片的大小保持一致,在前面使用卷积网络进行图片分类的任务中,我们通过 resize 的办法对图片进行了缩放,使得他们的大小相同,但是这里会遇到一个问题,对于输入图片我们当然可以 resize 成任意我们想要的大小,但是 label 也是一张图片,且是在 pixel 级别上的标注,所以我们没有办法对 label 进行有效的 resize 似的其也能达到像素级别的匹配,所以为了使得输入的图片大小相同,我们就使用 crop 的方式来解决这个问题,也就是从一张图片中 crop 出固定大小的区域,然后在 label 上也做同样方式的 crop。 使用 crop 可以使用 pytorch 中自带的 transforms,不过要稍微改一下,不仅输出 crop 出来的区域,同时还要输出对应的坐标便于我们在 label 上做相同的 crop 

def random_crop(data, label, crop_size):
    height, width = crop_size
    data, rect = tfs.RandomCrop((height, width))(data)
    label = tfs.FixedCrop(*rect)(label)
    return data, label

下面我们可以验证一下随机 crop

_, figs = plt.subplots(2, 2, figsize=(10, 8))
crop_im1, crop_label1 = random_crop(im_show1, label_show1, (200, 300))
figs[0][0].imshow(crop_im1)
figs[0][1].imshow(crop_label1)
figs[0][0].axes.get_xaxis().set_visible(False)
figs[0][1].axes.get_yaxis().set_visible(False)
crop_im2, crop_label2 = random_crop(im_show1, label_show1, (200, 300))
figs[1][0].imshow(crop_im2)
figs[1][1].imshow(crop_label2)
figs[1][0].axes.get_xaxis().set_visible(False)
figs[1][1].axes.get_yaxis().set_visible(False)

image.png上面就是我们做两次随机 crop 的结果,可以看到图像和 label 能够完美的对应起来 接着我们根据数据知道里面有 21 中类别,同时给出每种类别对应的 RGB 值

classes = ['background','aeroplane','bicycle','bird','boat',
           'bottle','bus','car','cat','chair','cow','diningtable',
           'dog','horse','motorbike','person','potted plant',
           'sheep','sofa','train','tv/monitor']
 
# RGB color for each class
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]]
 
len(classes), len(colormap)

接着可以建立一个索引,也就是将一个类别的 RGB 值对应到一个整数上,通过这种一一对应的关系,能够将 label 图片变成一个矩阵,矩阵和原图片一样大,但是只有一个通道数,也就是 (h, w) 这种大小,里面的每个数值代表着像素的类别

cm2lbl = np.zeros(256**3) # 每个像素点有 0 ~ 255 的选择,RGB 三个通道
for i,cm in enumerate(colormap):
    cm2lbl[(cm[0]*256+cm[1])*256+cm[2]] = i # 建立索引
 
def image2label(img):
    data = np.array(img, dtype='int32')
    idx = (data[:, :, 0] * 256 + data[:, :, 1]) * 256 + data[:, :, 2]
    return np.array(cm2lbl[idx], dtype='int64') # 根据索引得到 label 矩阵

定义完成之后,我们可以验证一下

label_im = Image.open('./dataset/VOCdevkit/VOC2012/SegmentationClass/2007_000033.png').convert('RGB') label_im 

image.png

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可,转载请附上原文出处链接和本声明。
本文链接地址:https://www.flyai.com/article/602
讨论
500字
表情
发送
删除确认
是否删除该条评论?
取消 删除