【手把手教你】搭建神经网络(语义分割)

大家好,我是羽峰,今天要和大家分享的是一个基于tensorflow的语义分割项目,网络与U-Net很像。文章会把整个代码进行分割讲解,完整看完,相信你一定会有所收获。

图像分割:提取图像中哪些像素是用于表述已知目标的目标种类与数量问题、目标尺度问题、外在环境干扰问题、物体边缘等,目前分为语义分割、实例分割、全景分割。

目录

1. 认识语义分割

2. 实例演示语义分割

2.1 数据下载

2.2 准备输入与真值

2.3 定义模型

2.4 网络训练

2.5 网络预测


1. 认识语义分割

语义分割结合了图像分类、目标检测和图像分割,通过一定的方法将图像分割成具有一定语义含义的区域块,并识别出每个区域块的语义类别,实现从底层到高层的语义推理过程,最终得到一幅具有逐像素语义标注的分割图像。

图像语义分割方法有传统方法和基于卷积神经网络的方法,其中传统的语义分割方法又可以分为基于统计的方法和基于几何的方法。随着深度学习的发展,语义分割技术得到很大的进步,基于卷积神经网络的语义分割方法与传统的语义分割方法最大不同是,网络可以自动学习图像的特征,进行端到端的分类学习,大大提升语义分割的精确度。

CNN已经在图像分类分方面取得了巨大的成就,涌现出如VGG和Resnet等网络结构,并在ImageNet中取得了好成绩。CNN的强大之处在于它的多层结构能自动学习特征,并且可以学习到多个层次的特征:

  1. 较浅的卷积层感知域较小,学习到一些局部区域的特征;
  2. 较深的卷积层具有较大的感知域,能够学习到更加抽象一些的特征。

这些抽象特征对物体的大小、位置和方向等敏感性更低,从而有助于分类性能的提高。这些抽象的特征对分类很有帮助,可以很好地判断出一幅图像中包含什么类别的物体。图像分类是图像级别的,与分类不同的是,语义分割需要判断图像每个像素点的类别,进行精确分割。图像语义分割是像素级别的!但是由于CNN在进行convolution和pooling过程中丢失了图像细节,即feature map size逐渐变小,所以不能很好地指出物体的具体轮廓、指出每个像素具体属于哪个物体,无法做到精确的分割。

针对这个问题,Jonathan Long等人提出了Fully Convolutional Networks(FCN)用于图像语义分割。自从提出后,FCN已经成为语义分割的基本框架,后续算法其实都是在这个框架中改进而来。

FCN原文:https://arxiv.org/abs/1411.4038

FCN原文代码:https://github.com/shelhamer/fcn.berkeleyvision.org

之后又提出了U-Net网络,当然之后还提出了很多方法,但本文是基于U-Net网络来进行语义分割。因为U-Net网络比较简单,所以理解起来会比较容易。

语义分割网络在特征融合时也有2种办法:

  1. FCN式的逐点相加,对应caffe的EltwiseLayer层,对应tensorflow的tf.add()
  2. U-Net式的channel维度拼接融合,对应caffe的ConcatLayer层,对应tensorflow的tf.concat()

CNN图像语义分割的基本思路:

  1. 下采样+上采样:Convlution + Deconvlution/Resize
  2. 多尺度特征融合:特征逐点相加/特征channel维度拼接
  3. 获得像素级别的segement map:对每一个像素点进行判断类别

2. 实例演示语义分割

2.1 数据下载

!curl -O https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz
!curl -O https://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz
!tar -xf images.tar.gz
!tar -xf annotations.tar.gz

数据集下载好,就开始准备输入图像和目标分割蒙板的路径,也就是将输入与真值图像放入到指定路径下。

import os
#数据存放地址
input_dir = "images/"
target_dir = "annotations/trimaps/"
#图像大小
img_size = (160, 160)
num_classes = 3
batch_size = 32
#存入图像
input_img_paths = sorted(
    [
        os.path.join(input_dir, fname)
        for fname in os.listdir(input_dir)
        if fname.endswith(".jpg")
    ]
)
target_img_paths = sorted(
    [
        os.path.join(target_dir, fname)
        for fname in os.listdir(target_dir)
        if fname.endswith(".png") and not fname.startswith(".")
    ]
)

print("Number of samples:", len(input_img_paths))

for input_path, target_path in zip(input_img_paths[:10], target_img_paths[:10]):
    print(input_path, "|", target_path)

输出结果为

 

一个输入图像和相应的分割蒙版是什么样的呢,将其中一组可视化一下,看看输入图像和真值是怎样的

from IPython.display import Image, display
from tensorflow.keras.preprocessing.image import load_img
import PIL
from PIL import ImageOps

# 展示输入图像
display(Image(filename=input_img_paths[9]))

# 显示真值
img = PIL.ImageOps.autocontrast(load_img(target_img_paths[9]))
display(img)

 

2.2 准备输入与真值

准备Sequence类用于以加载和向量化批量数据,Sequence 是进行多进程处理的更安全的方法。这种结构保证网络在每个时期每个样本只训练一次,这与生成器不同。每一个 Sequence 必须实现 __getitem__ 和 __len__ 方法。 如果你想在迭代之间修改你的数据集,你可以实现 on_epoch_end。 __getitem__ 方法应该范围一个完整的批次。

from tensorflow import keras
import numpy as np
from tensorflow.keras.preprocessing.image import load_img


class OxfordPets(keras.utils.Sequence):
    """Helper to iterate over the data (as Numpy arrays)."""
    #数据一些出事参数
    def __init__(self, batch_size, img_size, input_img_paths, target_img_paths):
        self.batch_size = batch_size
        self.img_size = img_size
        self.input_img_paths = input_img_paths
        self.target_img_paths = target_img_paths

    def __len__(self):
        return len(self.target_img_paths) // self.batch_size
    #返回输入与真值的序列
    def __getitem__(self, idx):
        """Returns tuple (input, target) correspond to batch #idx."""
        i = idx * self.batch_size
        batch_input_img_paths = self.input_img_paths[i : i + self.batch_size]
        batch_target_img_paths = self.target_img_paths[i : i + self.batch_size]
        x = np.zeros((self.batch_size,) + self.img_size + (3,), dtype="float32")
        for j, path in enumerate(batch_input_img_paths):
            img = load_img(path, target_size=self.img_size)
            x[j] = img
        y = np.zeros((self.batch_size,) + self.img_size + (1,), dtype="uint8")
        for j, path in enumerate(batch_target_img_paths):
            img = load_img(path, target_size=self.img_size
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羽峰码字

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值