基于U-Net的的图像分割代码详解及应⽤实现
摘要
U-Net是基于卷积神经⽹络(CNN)体系结构设计⽽成的,由Olaf Ronneberger,Phillip Fischer和Thomas Brox于2015年⾸次提出应⽤于计算机视觉领域完成语义分割任务。(其发表U-Net的作者建议该模型能够很好的应⽤于⽣物医学图像分割)。⽽本⽂将从U-Net的应⽤领域、模型原理、具体应⽤三个⽅⾯具体概述U-Net神经⽹络的实现⽅式及应⽤技术。最后,本⽂基于keras神经⽹络框架设计并实现U-Net模型在EM神经元数据集上的 分割应⽤,其实现结果现实,U-Net在EM 堆栈神经元的分割任务上具有较好的实验结果。
1.计算机视觉任务概述
1.1 语义分割解析(Semantic Segmentation)
1.1.1 定义
图像语义分割(Semantic Segmentation)是图像处理和是机器视觉技术中关于图像理解的重要⼀环,也是 AI 领域中⼀个重要的分⽀。语义分割即是对图像中每⼀个像素点进⾏分类,确定每个点的类别(如属于背景、⼈或车等),从⽽进⾏区域划分。⽬前,语义分割已经被⼴泛应⽤于⾃动驾驶、⽆⼈机落点判定
等场景中。
1.1.2 实例解释
图像分类
举个例⼦进⾏描述:
在图像领域,我们常见的任务是对图像的分类任务,该任务是较为粗粒度的来识别和理解图像。即是我们给定⼀张图像,我们期望模型输出该张图像所属的类别(离散标签),⽽在图像分类任务中,我们假设图像中只有⼀个(⽽不是多个)对象。描述如下图:
但是,语义分割则在图像分类任务上更为细化。其需要经历三个过程:图像定位、⽬标检测、语义分
割(及实例分割)。其中,⽬标检测( Object detection)不仅需要提供图像中物体的类别,还需要提供物体的位置(bounding box)。语义分割( Semantic segmentation)需要预测出输⼊图像的每⼀个像素点属于哪⼀类的标签。实例分割( instance segmentation)在语义分割的基础上,还需要区分出同⼀类不同的个体。
图像定位
在与离散标签⼀起输出时(完成分类任务过程中),我们还期望模型能够准确定位图像中存在该物体的位置。这种定位通常使⽤边界框来实现,边界框可以通过⼀些关于图像边界的数值参数来识别。其如下图所⽰:
⽬标检测
与图像定位不同的是,⽬标检测需要判断图像中物体的类别,还判断指出提供图像中物体的具体位置(bounding box)。即是现在图像不再局限于只有⼀个对象,⽽是可以包含多个对象。任务是对图像中的所有对象进⾏分类和定位。这⾥再次使⽤边界框的概念进⾏定位。其如下图所⽰:
语义分割
语义分割通过对每个像素进⾏密集的预测、推断标签来实现细粒度的推理,从⽽使每个像素都被标记为其封闭对象矿⽯区域的类别。即是需要判断图像每个像素点的类别,进⾏精确分割。图像语义分割是像素级别的!因为我们要对图像中的每个像素进⾏预测,所以这个任务通常被称为密集预测。其如下图所⽰:
实例分割
实例分割⽐语义分割是更为细致的下游任务,其中与像素级的分类任务处理⽅式⼀样,我们希望模型分别对类的每个实例进⾏分类。例如,图中有 3 个⼈,严格来说是“Person”类的 3 个实例。所有这3个分别分类(以不同的颜⾊)。其如下图所⽰:
⽬标检测、语义分割、实例分割三个像素级别的分类任务,其区别如下图所⽰。
1.2 应⽤领域
1.2.1 ⾃动驾驶汽车
⾃动驾驶是⼀项极为复杂的深度学习应⽤任务,其需要模型在不断变化的环境中进⾏感知、规划和执⾏相关任务(既是汽车避免路障等问题)。因为⼈⽣安全是最重要的,所以这项任务需要最为精准的模型运⾏效果。语义分割可提供有关道路上的障碍信息、检测路况标记和交通标志等信息。
1.2.2 ⽣物医学影像诊断(精准医疗)
这就是本⽂将要实现的具体应⽤了。其实现的最终模型可以减少放射科医⽣的分析时间,为相关疾病的诊疗提供辅助和技术⽀持。
1.2.3 地理传感
语义分割问题也可以被视为分类问题(其最终的解决⽅式),其中每个像素被归类为⼀系列对象类别中的⼀个。因此,可⽤于卫星图像的⼟地使⽤制图。例如监测森林砍伐和城市化区域。道路和建筑物检测也是交通管理、城市规划和道路监测的重要研究课题等。
1.2.4 精准农业
农业机器⼈可以减少⽥间需要喷洒的除草剂次数和剂量,通过对作物和杂草进⾏语义分割,实时协助它们触发除草动作。
2. U-Net模型解释
2.1 U-Net模型概述
U-Net的U形结构如图所⽰。⽹络是⼀个经典的全卷积⽹络(即⽹络中没有全连接操作)。⽹络的输⼊是⼀张 572*572 的边缘经过镜像操作的图⽚(input image tile),关于“镜像操作“会在接下来进⾏详细分析,⽹络的左侧(红⾊虚线)是由卷积和Max Pooling构成的⼀系列降采样操作,论⽂中将这⼀部分叫做压缩路径(contracting path)。压缩路径由4个block组成,每个block使⽤了3个有效卷积和1个Max Pooling降采样,每次降采样之后Feature Map的个数乘2,因此有了图中所⽰的Feature Map尺⼨变化。最终得到了尺⼨为
32*32的Feature Map。
⽹络的右侧部分(绿⾊虚线)在论⽂中叫做扩展路径(expansive path)。同样由4个block组成,每个block开始之前通过反卷积将Feature Map的尺⼨乘2,同时将其个数减半(最后⼀层略有不同),然后和左侧对称的压缩路径的Feature Map合并,由于左侧压缩路径和右侧扩展路径的Feature Map的尺⼨不⼀样,U-Net是通过将压缩路径的Feature Map裁剪到和扩展路径相同尺⼨的Feature Map进⾏归⼀化的(即图1中左侧虚线部分)。扩展路径的卷积操作依旧使⽤的是有效卷积操作,最终得到的Feature Map的尺⼨是 388*388。由于该任务是⼀个⼆分类任务,所以⽹络有两个输出Feature Map。
⾸先,数据集我们的原始图像的尺⼨都是 512*512 的。为了能更好的处理图像的边界像素,U-Net使⽤了镜像操作(Overlay-tile Strategy)来解决该问题。镜像操作即是给输⼊图像加⼊⼀个对称的边(图2),那么边的宽度是多少呢?⼀个⽐较好的策略是通过感受野确定。因为有效卷积是会降低Feature Map分辨率的,但是我们希望 515*512的图像的边界点能够保留到最后⼀层Feature Map。所以我们需要通过加边的操作增加图像的分辨率,增加的尺⼨即是感受野的⼤⼩,也就是说每条边界增加感受野的⼀半作为镜像边。
2.2 U-Net 和 autoencoder 架构的区别
U-Net模型与传统的图像分割⽅法(⾃动编码器体系结构)有所区别。传统的经典图像分割⽅法是最初输⼊信息的⼤⼩随着层数的增加其特征信息在不断减少,⾄此,⾃动编码器体系结构的编码器部分完成,开始解码器部分。⽽U-Net模型学习线性特征表⽰,其特征⼤⼩在逐渐增⼤,其最后的输出⼤⼩等于输⼊⼤⼩。
既是整个Auto-Encoder有两个吸引⼈的应⽤:1)利⽤前半部分做特征提取;2)利⽤后半部分做图像⽣成。
然⽽,U-Net结构和Auto-Encoder的传统结构⼗分相似,但是它独特的前传结构让⽹络能够capture到很多空间的信息,U-Net的直接前传可以保留很多空间信息,既其前半部分是降采样,下半部分是升采样。
2.3 U-Net模型代码详解
在这部分代码实现过程中⼀定要注意调整卷积核的⼤⼩和padding的⼤⼩,这样才可以在最后保证图⽚的尺⼨恢复到和原来⼀样。
inputs = Input(input_size)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(inputs)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv3)
pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv4)
drop4 = Dropout(0.5)(conv4)
pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)
conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool4)
conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv5)
drop5 = Dropout(0.5)(conv5)
up6 = Conv2D(512, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(drop5))
merge6 = concatenate([drop4,up6], axis = 3)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge6)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv6)
up7 = Conv2D(256, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv6))
merge7 = concatenate([conv3,up7], axis = 3)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge7)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv7)
up8 = Conv2D(128, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv7))
merge8 = concatenate([conv2,up8], axis = 3)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge8)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv8)
up9 = Conv2D(64, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv8))
merge9 = concatenate([conv1,up9], axis = 3)
aspnet和net的区别conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge9)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv9 = Conv2D(2, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv10 = Conv2D(1, 1, activation = 'sigmoid')(conv9)
model = Model(input = inputs, output = conv10)
from keras.utils.vis_utils import plot_model
plot_model(model, to_file='model1.png',show_shapes=True)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。