【语义分割】【Tensorflow2】语义分割实战1---斑马线识别

every blog every motto: You will never know unless you try

0. 前言

本文旨在讲解有关语义分割的基础内容,并进行实战。

1. 正文

1.1 前提梗概

1.1.1 引言

不要强求不可知,要从已知推未知
为了更好的了解语义分割,会先简单回顾一下作为深度学习“Hello World ”的mnist / fashion-mnist

1.1.2 mnist / fashion_mnsit

mnist是一个手写数字识别的数据集,一张图像上只有一个数字,如下图所示

在这里插入图片描述
上面每张图片即是一个原图/样本(x),他的标签是对应数字的字符(y / label)
在这里插入图片描述
主要任务是分类,即对识别每张图片的数字!
有关fashion_mnist数据集也是类似,具体可参考想看就点我
总结: mnist/fashion_mnist ,原图 / 训练样本(x)是一张仅含一种物体的图片,标签(y) 是一个字符或者说one_hot编码。对其处理也较为简单,属于分类任务

1.2 语义分割基础

1.2.1 概念

  • 直观理解:
    一言以蔽之: 是将一张(含有多种物体的)图片,识别出其中的每一种物体。
    语义分割的直观理解,如下图所示。
    在这里插入图片描述
    动画理解(实际意义):
    在这里插入图片描述
    最简单的语义分割,当属于“二分类”,即识别出图片中的两种地物,一种归为背景,另一种归为想要提取的物体(如我们接下来要说的斑马线)
  • 进一步理解:
    我们以遥感影像为例,如下图所示:
    左边是原图/样本(x),右图是标签(label,黑色的是背景,红色的是标注的建筑物)
    注: 标注自己的训练样本
    这里的标签是一张图片,不同于mnsit(标签是一个字符串)
    这里的标签是一张图片,不同于mnsit(标签是一个字符串)
    这里的标签是一张图片,不同于mnsit(标签是一个字符串)
    在这里插入图片描述
  • 更进一步理解:
    通过如下过程,不断计算损失函数,调整参数,直至loss函数最小,即参数最优为止。
    在这里插入图片描述
  • 深入理解:
    整个网络如下图所示,分为两部分,分别是编码、解码部分。
    在这里插入图片描述
    图像shape的变化:
    编码:图像(size)不断变小,同时不断变厚(通道数增加)
    解码:图像(size)不断变大(最终恢复到原图的1/2),同时不断变薄(通道数减小)
    在这里插入图片描述

1.2.2 网络结构

说明: 网络结构如下图所示,但其中size和shape的具体数字与代码中不对,原因是我用此网络跑了其他的图片,也是基于别的图片画的网络结构,由此可以看出,各输入图片经过模型后,各特征图的缩放比例没有变化,在于初始大小不同而已。
在这里插入图片描述
链接: 用Plotneuralnet画神经网络图

1.2.3 结果展示

使用下方代码,进行预测,最终结果。
原图:
在这里插入图片描述
预测结果:
可以发现,基本将斑马线标出,由于训练数据(191个)较少,有这样的结果还算不错。
在这里插入图片描述

1.3 代码部分

说明: 具体代码,文后附链接。

1.3.1 模型部分(model.py)

主函数如下:
如上所说,模型分为两部分,编码、解码。

def main():
    """
     model 的主程序,语义分割,分为两部分,第一部分特征提取,第二部分放大图片
    :param Height: 图片长
    :param Width: 图片宽
    :return: (H,W,3) -> (h,w,2)
    """

    # 第一部分 编码,提取特征,图像size减小,通道增加
    img_input, feature_map_list = encoder(input_height=Height, input_width=Width)

    # 第二部分 解码,将图像上采样,size放大,通道减小
    output = decoder(feature_map_list, class_number=class_number, input_height=Height, input_width=Width,
                     encoder_level=3)

    # 构建模型
    model = Model(img_input, output)

    # model.summary()
    print('模型输入shape:', img_input.shape, '模型输出图像shape:', output.shape)
    print('-'*100)
    return model

模型的前半部分,编码部分:
下采样过程,下采样包括卷积和池化。
基于VGG16的编码网络,用于提取特征,有关卷积运算可参考文章1文章2

def encoder(input_height, input_width):
    """
    语义分割的第一部分,特征提取,主要用到VGG网络,函数式API
    :param input_height: 输入图像的长
    :param input_width: 输入图像的宽
    :return: 返回:输入图像,提取到的5个特征
    """

    # 输入
    img_input = Input(shape=(input_height, input_width, 3))
    # print('--')
    # print(img_input.shape)

    # 三行为一个结构单元,size减半
    # 416,416,3 -> 208,208,64,
    x = Conv2D(64, (3, 3), activation='relu', padding='same')(img_input)
    x = Conv2D(64, (3, 3), activation='relu', padding='same')(x)
    x = MaxPool2D((2, 2), strides=(2, 2))(x)
    f1 = x  # 暂存提取的特征

    # 208,208,64 -> 104,104,128
    x = Conv2D(128, (3, 3), activation='relu', padding='same')(x)
    x = Conv2D(128, (3, 3), activation='relu', padding='same')(x)
    x = MaxPool2D((2, 2), strides=(2, 2))(x)
    f2 = x  # 暂存提取的特征

    # 104,104,128 -> 52,52,256
    x = Conv2D(256, (3, 3), activation='relu', padding='same')(x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same')(x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same')(x)
    x = MaxPool2D((2, 2), strides=(2, 2))(x)
    f3 = x  # 暂存提取的特征

    # 52,52,256 -> 26,26,512
    x = Conv2D(512, (3, 3), activation='relu', padding='same')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same')(x)
    x = MaxPool2D((2, 2), strides=(2, 2))(x)
    f4 = x  # 暂存提取的特征

    # 26,26,512 -> 13,13,512
    x = Conv2D(512, (3, 3), activation='relu', padding='same')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same')(x)
    x = MaxPool2D((2, 2), strides=(2, 2))(x)
    f5 = x  # 暂存提取的特征

    # print(img_input.shape[1:])
    return img_input, [f1, f2, f3, f4, f5]

模型的后半部分,解码部分:
上采样过程,
关于模型后半部分的经过softmax的理解,2020.9.26添加

def decoder(feature_map_list, class_number, input_height=416, input_width=416, encoder_level=3):
    """
    语义分割的后半部分,上采样,将图片放大,
    :param feature_map_list: 特征图(多个),encoder得到
    :param class_number: 分类数
    :param input_height: 输入图像长
    :param input_width: 输入图像宽
    :param encoder_level: 利用的特征图,这里利用f4
    :return: output , 返回放大后的特征图 (208*208,2)
    """
    # 获取一个特征图,特征图来源encoder里面的f1,f2,f3,f4,f5; 这里获取f4
    feature_map = feature_map_list[encoder_level]

    # 解码过程 ,以下 (26,26,512) -> (208,208,64)

    # f4.shape=(26,26,512) -> 26,26,512
    x = ZeroPadding2D((1, 1))(feature_map)
    x = Conv2D(512, (3, 3), padding='valid')(x)
    x = BatchNormalization()(x)

    # 上采样,图像长宽扩大2倍,(26,26,512) -> (52,52,256)
    x = UpSampling2D((2, 2))(x)
    x = ZeroPadding2D((1, 1))(x)
    x = Conv2D(256, (3, 3), padding='valid')(x)
    x = BatchNormalization()(x)

    # 上采样,图像长宽扩大2倍 (52,52,512) -> (104,104,128)
    x = UpSampling2D((2, 2))(x)
    x = ZeroPadding2D((1, 1))(x)
    x = Conv2D(128, (3, 3), padding='valid')(x)
    x = BatchNormalization()(x)

    # 上采样,图像长宽扩大2倍,(104,104,128) -> (208,208,64)
    x = UpSampling2D((2, 2))(x)
    x = ZeroPadding2D((1, 1))(x)
    x = Conv2D(64, (3, 3), padding='valid')(x)
    x = BatchNormalization()(x)

    # 再进行一次卷积,将通道数变为2(要分类的数目) (208,208,64) -> (208,208,2)
    x = Conv2D(class_number, (3, 3), padding='same')(x)
    # print(x.shape)
    # reshape: (208,208,2) -> (208*208,2)
    # y = Reshape((208,208,-1))(x)
    # print('y shape:',y.shape)
    x = Reshape((int(input_height / 2) * int(input_width / 2), -1))(x)
    # print(x.shape)
    # 求取概率
    output = Softmax()(x)

    return output

网络结构,summary:
在这里插入图片描述

1.3.2 训练部分(train.py)

主函数,如下:

def main():
    # 获取已建立的模型,并加载官方与训练参数,模型编译
    model = get_model()
    # 打印模型摘要
    # model.summary()

    # 获取样本(训练集&验证集) 和标签的对应关系,trian_num,val_num
    lines, train_nums, val_nums = get_data()

    # 设置回调函数 并返回保存的路径
    callbacks, logdir = set_callbacks()

    # 生成样本和标签
    # generate_arrays_from_file(lines, batch_size=batch_size)

    # 训练
    model.fit_generator(generate_arrays_from_file(lines[:train_nums], batch_size),
                        steps_per_epoch=max(1, train_nums // batch_size),
                        epochs=50, verbose=1, callbacks=callbacks,
                        validation_data=generate_arrays_from_file(lines[train_nums:], batch_size),
                        validation_steps=max(1, val_nums // batch_size),
                        initial_epoch=0)

    save_weight_path = os.path.join(logdir, 'last.h5')  # 保存模型参数的路径

    model.save_weights(save_weight_path)

1.3.3 预测部分(predict.py)

axis参数辨析,点我
主函数,如下:

def main():
    """ 模型预测"""
    model = get_model()
    predicting(model)

1.4 源码及视频讲解

1.4.1 源码

https://github.com/onceone/Semantic-segmentation
数据集:提取码pp6w —>数据集来源

1.4.2 视频讲解

感谢评论区shu_0233的指出。
https://www.bilibili.com/video/BV1Bi4y1G7PE?from=search&seid=9616538879256765990


知识无价,如果帮助到你,不妨请喝一杯奶茶~~

在这里插入图片描述在这里插入图片描述

参考文献

[1] https://blog.csdn.net/weixin_39190382/article/details/105890812
[2] https://blog.csdn.net/weixin_44791964/article/details/102979289
[3] https://baijiahao.baidu.com/s?id=1602428106371812559&wfr=spider&for=pc
[4] https://blog.csdn.net/weixin_40446557/article/details/85624579
[5] https://www.sohu.com/a/301097998_120054440
[6] https://www.cnblogs.com/xianhan/p/9145966.html
[7] https://blog.csdn.net/weixin_39190382/article/details/105692853
[8] https://blog.csdn.net/weixin_39190382/article/details/105702100
[9] https://blog.csdn.net/weixin_39190382/article/details/104083347
[10] https://blog.csdn.net/weixin_39190382/article/details/108803650

  • 34
    点赞
  • 164
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胡侃有料

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

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

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

打赏作者

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

抵扣说明:

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

余额充值