全卷积神经网络与图像分割
一、图像分割简介
1.图像分割
- 图像分割(image segmentation)是指将图像分成若干具有相似性质的区域的过程
- 从数学角度来看,图像分割是将图像划分成互不相交的区域的过程
- 图像分割技术相关的场景如物体分割、人体前背景分割、三维重建等技术已经在无人驾驶、增强现实、安防监控等行业得到广泛的应用
2.语义分割
- 在实际场景的图片中,一些物体的结构比较复杂,内部差异性较大,仅利用像素点的颜色、亮度、纹理等较低层次的内容信息不足以生成好的分割效果
- 更多地结合图像提供的中高层内容信息辅助图像分割,称为图像语义分割
3.图像分割技术发展
图像分割技术从算法演进历程上,大体可划分为
- 基于图论的方法
- 基于像素聚类的方法
- 基于深度语义的方法
4.基于图论的分割方法
- 利用图论领域的理论和方法,将图像映射为带权无向图,把像素视作节点,将图像分割问题看作是图的顶点划分问题
- 将待分割的图像映射为带权无向图G=(V,E)。其中,V={ v1,…,vn }是顶点的集合,E为边的集合
- 图中每个节点N ∈ V对应于图像中的每个像素,每条边连接着一对相邻的像素,边的权值w(vi,vj ),其中(vvj)∈E,表示了相邻像素之间在灰度、颜色或纹理方面的非负相似度
- 而图像的一个分割S就是对图的一个剪切,被分割的每个区域C∈S对应着图中的一个子图
- 分割的原则就是使划分后的子图在内部保持相似度最大,而子图之间的相似度保持最小
2.基于聚类的分割方法
聚类方法用于解决图像分割问题的一般步骤是:
- 初始化一个粗糙的聚类
- 使用迭代的方式将颜色、亮度、纹理等特征相似的像素点聚类到同一超像素,迭代直至收敛,从而得到最终的图像分割结果
3.K-means
将N个数据对象划分为K个类别以便使获得的类别满足:同一类别中的对象相似度较高,而不同类别中的对象相似度较小 算法过程如下: 1)从N个数据样本随机选取K个数据作为质心(聚类中心) 2)对每个数据样本计算其到每个质心的距离,并把它归到最近的质心的类别中 3)重新计算已经得到的各个类的质心 4)迭代 (2)~(3)直至新的质心与原质心相等或小于指定阈值,算法结束
4.谱聚类
谱聚类(Spectral Clustering)是一种基于图论的聚类方法——将带权无向图划分为两个或两个以上的最优子图,使子图内部尽量相似,而子图间距离尽量距离较远,以达到常见的聚类目的 算法步骤如下: (1)构建相似度矩阵W (2)根据相似度矩阵W构建拉普拉斯矩阵L (3)对L进行特征分解,选取特征向量组成特征空间(4)在特征空间中利用K-Means算法,输出聚类结果
5.SLIC
- SLIC (Simple Linear lterative Clustering)是Achanta等人2010年提出的一种思想简单、实现方便的算法
- 将彩色图像转化为CIELAB颜色空间和XY坐标下的5维特征向量,然后构造距离度量标准,对图像像素进行局部聚类的过程
- SLIC算法的实质是将K-Means算法用于超像素聚类
二、反卷积
1.FCN的引入
- CNN能够对图片进行分类,但如何识别图片中特定部分的物体,Jonathan Long发表了《Fully Convolutional Networks for Semantic Segmentation》,开启了神经网络在图像分割领域的应用
- 通常CNN网络在卷积层之后会接上若干个全连接层,将卷积层产生的特征图映射成一个固定长度的特征向量
2.CNN和FCN的区别
- FCN对图像进行像素级的分类,接受任意尺寸的输入图像,采用反卷积层对最后一个卷积层的特征图进行上采样,使它恢复到输入图像相同的尺寸,从而可以对每个像素都产生一个预测,同时保留了原始输入图像中的空间信息,最后在上采样的特征图上进行逐像素分类
- 最后逐个像素计算Softmax损失,相当于每一个像素对应一个训练样本
- FCN与CNN的区别在把于CNN最后的全连接层换成卷积层,输出的是一张已经打好标签的图片
- 卷积化相较于全连接层学习能力偏弱,但它有2个好处,第一权值共享,学习速度更快,需要的参数量更少,网络更轻量化,第二可以控制h和w,而不会像全连接层一样丢失位置信息。
- 为了对一个像素分类,使用该像素周围的一个图像块作为CNN的输入用于训练和预测,这种方法有几个缺点:
存储开销很大 计算效率低下 - 全卷积网络(FCN)从抽象的特征中恢复出每个像素所属的类别,即从图像级别的分类进一步延伸到像素级别的分类
3.全连接层->卷积层
- 全连接层和卷积层之间唯一的不同就是卷积层中的神经元只与输入数据中的一个局部区域连接,并且在卷积列中的神经元共享参数
- 任何全连接层都可以被转化为卷积层
- 卷积核不共享参数;卷积大小和图片当前figure map大小一样大时卷积层等价于全连接层
- 在两种变换中,将全连接层转化为卷积层在实际运用中更加有效
- 如下图所示,FCN将传统CNN中的全连接层转化成卷积层,对应CNN网络FCN把最后三层全连接层转换成为三层卷积层
- 卷积跟全连接是不一样的概念和计算过程,权值和偏置有自己的范围,属于自己的一个卷积核。因此FCN网络中所有的层都是卷积层,故称为全卷积网络。
- 经过多次卷积和pooling以后,得到的图像越来越小,分辨率越来越低
- H/32*W/32时图片最小,所产生图叫做热图(heatmap),也是最重要的高维特征图,之后对图像进行upsampling,把图像进行放大,放大到原图像的大小
- 输出是1000张heatmap经过upsampling变为原图大小的图片,最后通过逐个像素地求其在1000张图像该像素位置的最大数值描述(概率))作为该像素的分类,因此产生了一张已经分类好的图片,如下图右侧有狗和猫的图
4.二维卷积vs二维反卷积
三、经典图像分割模型
1.FCN
- 把全连接层改为卷积层,权值共享,计算更快
- 保留了图像的位置信息
2.SegNet
- 把maxpooling操作进行了改进,记住了取maxpooling的位置
- 不等间隔的插
3.DeepLab
- V1:使用深度卷积网络和完全连接的CRF进行语义图像分割
- V2:使用深度卷积网络,Atrous卷积和完全连接的CRF进行语义图像分割
- V3:重新思考用于语义图像分割的Atrous卷积
- 特别慢
4.RefineNet
6.PSPNet
7.Large Kernel Matters
四、实例
1.实例1
dir_data="/dataset1/"
dir_seg=dir_data+"/annotations_prepped_train/"
dir_img=dir_data+'/images_prepped_train/'
import cv2,os
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid",{"axes.grid":False})
ldseg=np.array(os.listdir(dir_seg))
fnm=ldseg(0)
print(fnm)
seg=cv2.imread(dir_seg+fnm)
img_is =cv2.imread(dir_img+fnm)
print("seg.shape={},img_is.shape={}".format(seg.shape, img_is.shape))
mi,ma=np.min(seg),np.max(seg)
n_classes=ma-mi+1
print("minimum seg= {},maximum seg={},Total number of segmentation classes={} ".format(mi,ma,n_classes))
fig =plt.figure(figsize=(5,5))
ax=fig.add_subplot(1,1,1)
ax.show(img_is)
ax.set_title("original image")
plt.show()
import random
def give_color_to_seg_img(seg,n_classes):
if len(seg.shape)==3:
seg=seg[:,:,0]
seg_img=np.zeros((seg.shape[0],seg.shape[1],3)).astypr("float")
colors=sns.color_palette("hls",n_classes)
for c in range(n_classes):
segc=(seg==c)
seg_img[:,:,0]+=(segc*(colors[c][0]))
seg_img[:,:,1]+=(segc*(colors[c][1]))
seg_img[:,:,2]+=(segc*(colors[c][2]))
return (seg_img)
input_height,input_width=224,224
output_height,output_width=224,224
ldseg=np.array(os.listdir(dir_seg))
for fnm in ldseg[np.random.choice(len(ldseg),3,repalce=False)]:
fnm=fnm.split(".")[0]
seg=cv2.imread(dir_seg+fnm+".png")
img_is=cv2.imread(dir_img+fnm+".png")
seg_img=give_color_to_seg_img(seg,n_classes)
fig=plt.figure(figsize=(20,40))
ax=fig.add_subplot(1,4,1)
ax.imshow(seg_img)
ax=fig.add_subplot(1,4,2)
ax.imshow(img_is/255.0)
ax.set_title("original image{}".format(img_is.shape[:2]))
ax=fig.add_subplot(1,4,3)
ax.imshow(cv2.resize(seg_img,(input_height,input_width)))
ax=fig.add_subplot(1,4,4)
ax.set_title("resized to {}".format((output_height,output_width)))
plt.show()
def getImageArr(path,width,height):
img=cv2.imread(path,1)
img=np.float32(cv2.resize(img,(width,height))) / 127.5 - 1
return img
def getSegmentationArr(path,nClasses,width,height):
seg_labels=np.zeros((height,width,nClasses))
img=cv2.imread(path,1)
img=cv2.resize(img,(width,height))
img=img[:,:,0]
for c in range(nClasses):
seg_labels[:,:,c]=(img==c).astype(int)
return seg_labels
images=os.listdir(dir_img)
images.sort()
segmentations=os.listdir(dir_seg)
X=[]
Y=[]
for im,seg in zip(images,segmentations):
X.append(getImageArr(dir_img+im,input_width,input_height))
Y.append(getSegmentationArr(dir_seg+seg,n_classes,output_width,output_height))
X,Y=np.array(X),np.array(Y)
print(X.shape,Y.shape)
import tensorflow as tf
from keras.backend.tensorflow_backend import set_seesion
import keras,sys,time,warnings
from keras.models import *
from keras.layers import *
import pandas as pd
warnings.filterwarnings("ignore")
os.environ["CUDA_DEVICE_ORDER"]= "PCI_BUS_ID"
config =tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction =0.95
config.gpu_options_visable_device_list='1'
set_session(tf.Session(config=config))
VGG_Weight_path="pretrained_vgg16_path.h5"
def FCN8(nClasses,input_height=224,input_width=224):
IMAGE_ORDERING='channels_last'
img_input=Input(shape=(input_height,input_width,3))
x = Conv2D(64,(3,3),activation="relu",padding = 'same',name="block1_conv1",data_format=IMAGE_ORDERING)(img_input)
x = Conv2D(64,(3,3),activation="relu",padding = 'same',name="block1_conv2",data_format=IMAGE_ORDERING)(x)
x = MAXPooling((2,2),strides=(2,2),name='block1_pool',data_format=IMAGE_ORDERING)(x)
f1=x
x = Conv2D(128,(3,3),activation="relu",padding = 'same',name="block2_conv1",data_format=IMAGE_ORDERING)(x)
x = Conv2D(128,(3,3),activation="relu",padding = 'same',name="block2_conv2",data_format=IMAGE_ORDERING)(x)
x = MAXPooling((2,2),strides=(2,2),name='block2_pool',data_format=IMAGE_ORDERING)(x)
f2=x
x = Conv2D(256,(3,3),activation="relu",padding = 'same',name="block3_conv1",data_format=IMAGE_ORDERING)(x)
x = Conv2D(256,(3,3),activation="relu",padding = 'same',name="block3_conv2",data_format=IMAGE_ORDERING)(x)
x = Conv2D(256,(3,3),activation="relu",padding = 'same',name="block3_conv3",data_format=IMAGE_ORDERING)(x)
x = MAXPooling((2,2),strides=(2,2),name='block3_pool',data_format=IMAGE_ORDERING)(x)
f3=x
x = Conv2D(512,(3,3),activation="relu",padding = 'same',name="block4_conv1",data_format=IMAGE_ORDERING)(x)
x = Conv2D(512,(3,3),activation="relu",padding = 'same',name="block4_conv2",data_format=IMAGE_ORDERING)(x)
x = Conv2D(512,(3,3),activation="relu",padding = 'same',name="block4_conv3",data_format=IMAGE_ORDERING)(x)
pool4 = MAXPooling((2,2),strides=(2,2),name='block4_pool',data_format=IMAGE_ORDERING)(x)
x = Conv2D(512,(3,3),activation="relu",padding = 'same',name="block5_conv1",data_format=IMAGE_ORDERING)(x)
x = Conv2D(512,(3,3),activation="relu",padding = 'same',name="block5_conv2",data_format=IMAGE_ORDERING)(x)
x = Conv2D(512,(3,3),activation="relu",padding = 'same',name="block5_conv3",data_format=IMAGE_ORDERING)(x)
pool5 = MAXPooling((2,2),strides=(2,2),name='block5_pool',data_format=IMAGE_ORDERING)(x)
vgg=Model(img_inout,pool5)
vgg.load_weight(VGG_Weight_path)
n=4096
o=(Conv2D(n,(7,7),activation="relu",padding='same',name='conv6',data_format=IMAGE_ORDERING))(pool5)
conv7=(Conv2D(n,(1,1),activation="relu",padding='same',name='conv7',data_format=IMAGE_ORDERING))(o)
conv7_4=Conv2DTranspose(nClasses,kernel_size=(4,4),strides=(4,4),use_bias=False,data_format=IMAGE_ORDERING)(conv7)
pool411=(Conc2D(nClasses,(1,1),activation="relu",padding='same',name="pool4_11",data_format=IMAGE_ORDERING))(pool4)
pool411_2=(Conv2DTranspose(nClasses,kernel_size=(2,2),strides=(2,2),use_bias=False,data_format=IMAGE_ORDERING))(pool411)
pool311=(Conc2D(nClasses,(1,1),activation="relu",padding='same',name="pool3_11",data_format=IMAGE_ORDERING))(pool3)
o=ADD(name="add")(pool411_2,pool311,conv7_4)
o= Conv2DTranspose(nClasses,kernal_size=(8,8),use_bias=False,data_format=IMAGE_ORDERING)(o)
model=Model(image_input,o)
return model
model=FCN8(nClasses=8)
model.summary()
from keras import optimizers
sgd=optimizers.SGD(lr=0.01,decay=5**(-4),momentum=0.9,nesterov=True)
model.compile(loss="categorical_crossentropy",optimizers=sgd,metrics=["accuracy"])
model.fit(X[:-50],Y[:-50],validation_data=(X[-50:],Y[-50:]),bacth_size=32,epoch=200,verbose=2)
2.实例2
import cv2
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid", dict([('axes.grid',False)]))
def give_color_to_seg_img(seg, n_classes):
'''
seg:图像分割标签,数组
n_classes:分割标签数目
'''
if len(seg.shape)==3:
seg = seg[:,:,0]
seg_img = np.zeros( (seg.shape[0],seg.shape[1],3) ).astype('float')
colors = sns.color_palette("hls", n_classes)
for c in range(n_classes):
segc = (seg == c)
seg_img[:,:,0] += (segc*( colors[c][0] ))
seg_img[:,:,1] += (segc*( colors[c][1] ))
seg_img[:,:,2] += (segc*( colors[c][2] ))
return(seg_img)
n_classes = 12
seg_img = give_color_to_seg_img(seg,n_classes)
fig = plt.figure(figsize=(10,8))
plt.imshow(seg_img)
|