像素方式图像分割是计算机视觉中充分研究的问题。语义图像分割的任务是对图像中的每个像素进行分类。在这篇文章中,我们将讨论如何使用深度卷积神经网络进行图像分割。我们还将深入研究管道的实施 - 从准备数据到构建模型。
代码地址:https://github.com/divamgupta/image-segmentation-keras
深度学习和卷积神经网络(CNN)在计算机视觉领域极为普遍。CNN在几个计算机视觉任务中很受欢迎,例如图像分类,物体检测,图像生成等。与所有其他计算机视觉任务一样,深度学习已经超越了其他图像分割方法。
什么是语义分割?
语义图像分割是从预定义的一组类中对图像中的每个像素进行分类的任务。在以下示例中,对不同的实体进行分类。
卧室图像的语义分割
在上面的例子中,属于床的像素被分类在类“床”中,对应于墙的像素被标记为“墙”等。
特别地,我们的目标是拍摄尺寸为W×H×3的图像并生成包含对应于所有像素的预测类ID的W×H矩阵。
图片来源:jeremyjordan.me
通常,在具有各种实体的图像中,我们想知道哪个像素属于哪个实体,例如在室外图像中,我们可以分割天空,地面,树木,人等。
语义分割与对象检测不同,因为它不预测对象周围的任何边界框。我们不区分同一对象的不同实例。例如,场景中可能有多辆汽车,并且所有汽车都具有相同的标签。
存在同一对象类的多个实例的示例
为了执行语义分割,需要对图像的更高级别的理解。 该算法应该找出存在的对象以及对应于该对象的像素。 语义分割是完整场景理解的基本任务之一。
应用
有几种应用程序对语义分段非常有用。
医学图像
身体扫描的自动分割可以帮助医生进行诊断测试。例如,可以训练模型以分割肿瘤。
肿瘤分割脑MRI扫描
自动驾驶汽车
自动驾驶汽车和无人驾驶飞机等自动驾驶汽车可以从自动分割中受益。例如,自动驾驶汽车可以检测可行驶区域。
道路场景的分割
卫星图像分析
航拍图像可用于划分不同类型的土地。也可以进行自动地面绘制。
卫星图像的分割
使用深度学习的图像分割
像大多数其他应用程序一样,使用CNN进行语义分割是显而易见的选择。当使用CNN进行语义分割时,输出也是图像而不是固定长度的矢量。
用于分割的卷积神经网络
通常,模型的体系结构包含多个卷积层,非线性**,批量标准化和池化层。初始层学习边缘和颜色等低级概念,后级层次学习更高级别的概念,如不同的对象。
在较低级别,神经元包含图像的小区域的信息,而在较高级别,神经元包含图像的大区域的信息。因此,随着我们添加更多层,图像的大小不断减小,并且通道的数量不断增加。下采样由池化层完成。
对于图像分类的情况,我们需要将空间张量从卷积层映射到固定长度矢量。为此,使用完全连接的层,这会破坏所有空间信息。
空间张量被下采样并转换为矢量
对于语义分割的任务,我们需要保留空间信息,因此不使用完全连接的层。这就是他们被称为完全卷积网络的原因。与下采样层耦合的卷积层产生包含高级信息的低分辨率张量。
采用包含高级信息的低分辨率空间张量,我们必须产生高分辨率的分割输出。为此,我们添加了更多的卷积层与上采样层相结合,这增加了空间张量的大小。随着我们提高分辨率,我们在回到低级别信息时减少了通道数量。
这称为编码器 - 解码器结构。对输入进行下采样的层是编码器的一部分,而上采样的层是解码器的一部分。
编码器 - 解码器架构
当模型被训练用于语义分割的任务时,编码器输出包含关于对象及其形状和大小的信息的张量。解码器获取该信息并产生分割图。
跳过连接
如果我们只是堆叠编码器和解码器层,可能会丢失低级信息。因此,解码器产生的分割图中的边界可能是不准确的。
为了弥补丢失的信息,我们让解码器访问编码器层产生的低级特征。这是通过跳过连接完成的。编码器的中间输出在适当的位置处与解码器的中间层的输入相加/连接。
带跳过连接的编码器 - 解码器
来自较早层的跳过连接向解码器层提供了创建精确边界所需的必要信息。
转换学习
训练用于图像分类的CNN模型包含有意义的信息,其也可用于分割。我们可以在分割模型的编码器层中重复使用预训练模型的卷积层。使用在ImageNet数据集上预先训练的Resnet或VGG是一种流行的选择。
转移学习分段
损失功能
将网络输出的每个像素与地面实况分割图像中的对应像素进行比较。我们对每个像素应用标准交叉熵损失。
履行
我们将使用Keras来构建和训练分割模型。首先,安装keras_segmentation(https://github.com/divamgupta/image-segmentation-keras),其中包含所需的所有实用程序。
pip install keras-segmentation
数据集
训练我们的分割模型的第一步是准备数据集。我们需要输入RGB图像和相应的分割图像。如果要创建自己的数据集,可以使用labelme或GIMP等工具手动生成地面实况分割蒙版。
为每个类分配一个唯一的ID。在分割图像中,像素值应该表示相应像素的类ID。这是大多数数据集和keras_segmentation使用的常见格式。对于分割图,不要使用jpg格式,因为jpg是有损的,像素值可能会改变。请改用bmp或png格式。当然,输入图像和分割图像的大小应该相同。
在下面的示例中,像素(0,0)标记为类2,像素(3,4)标记为类1,其余像素标记为类0。
import cv2
import numpy as np
ann_img = np.zeros((30,30,3)).astype('uint8')
ann_img[ 3 , 4 ] = 1 # this would set the label of pixel 3,4 as 1
ann_img[ 0 , 0 ] = 2 # this would set the label of pixel 0,0 as 2
cv2.imwrite( "ann_1.png" ,ann_img )
生成分割图像后,将它们放在training / testing文件夹中。为输入图像和分割图像创建单独的文件夹。输入图像的文件名和相应的分割图像应该相同。
请参阅以下格式:
dataset/
train_images/
- img0001.png
- img0002.png
- img0003.png
train_segmentation/
- img0001.png
- img0002.png
- img0003.png
val_images/
- img0004.png
- img0005.png
- img0006.png
val_segmentation/
- img0004.png
- img0005.png
- img0006.png
您可以https://github.com/divamgupta/image-segmentation-keras/tree/master/test/example_dataset参考示例数据集。
数据增加
如果你的训练对数量较少,结果可能不太好,因为模型可能过度拟合。我们可以通过在图像上应用随机变换来增加数据集的大小。我们可以改变输入图像的色调,饱和度,亮度等颜色属性。我们还可以应用旋转,缩放和翻转等变换。对于改变像素位置的变换,分割图像也应该以相同的方式变换。
Imgaug是一个执行图像增强的神奇工具。请参阅下面的代码片段,它将随机应用Crop,Flip和GaussianBlur变换。
import imgaug as ia
import imgaug.augmenters as iaa
seq = iaa.Sequential([
iaa.Crop(px=(0, 16)), # crop images from each side by 0 to 16px (randomly chosen)
iaa.Fliplr(0.5), # horizontally flip 50% of the images
iaa.GaussianBlur(sigma=(0, 3.0)) # blur images with a sigma of 0 to 3.0
])
def augment_seg( img , seg ):
aug_det = seq.to_deterministic()
image_aug = aug_det.augment_image( img )
segmap = ia.SegmentationMapOnImage( seg , nb_classes=np.max(seg)+1 , shape=img.shape )
segmap_aug = aug_det.augment_segmentation_maps( segmap )
segmap_aug = segmap_aug.get_arr_int()
return image_aug , segmap_aug
这里aug_det定义了变换的参数,它既适用于输入图像,也适用img于分割图像seg。
准备好数据集后,您可能需要对其进行验证并将其可视化。
python -m keras_segmentation verify_dataset \
--images_path="dataset_path/images_prepped_train/" \
--segs_path="dataset_path/annotations_prepped_train/" \
--n_classes=50
python -m keras_segmentation visualize_dataset \
--images_path="dataset_path/images_prepped_train/" \
--segs_path="dataset_path/annotations_prepped_train/" \
--n_classes=50
建立模型
现在,让我们使用Keras API来定义具有跳过连接的分段模型。
我们来定义编码器层。这里,每个块包含两个卷积层和一个最大池化层,它将对图像进行下采样两倍。
img_input = Input(shape=(input_height,input_width , 3 ))
conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(img_input)
conv1 = Dropout(0.2)(conv1)
conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv1)
pool1 = MaxPooling2D((2, 2))(conv1)
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1)
conv2 = Dropout(0.2)(conv2)
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv2)
pool2 = MaxPooling2D((2, 2))(conv2)
conv1并conv2包含将由解码器使用的编码器输出的中间值。pool2是编码器的最终输出。
让我们定义解码器层。我们将中间编码器输出与作为跳过连接的中间解码器输出连接起来。
conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2)
conv3 = Dropout(0.2)(conv3)
conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3)
up1 = concatenate([UpSampling2D((2, 2))(conv3), conv2], axis=-1)
conv4 = Conv2D(64, (3, 3), activation='relu', padding='same')(up1)
conv4 = Dropout(0.2)(conv4)
conv4 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv4)
up2 = concatenate([UpSampling2D((2, 2))(conv4), conv1], axis=-1)
conv5 = Conv2D(32, (3, 3), activation='relu', padding='same')(up2)
conv5 = Dropout(0.2)(conv5)
conv5 = Conv2D(32, (3, 3), activation='relu', padding='same')(conv5)
这里conv1连接起来conv4,并conv2与之连接起来conv3。要获得最终输出,请添加一个带有与类数相同的过滤器的卷积。(类似于我们的分类工作)。
out = Conv2D( n_classes, (1, 1) , padding='same')(conv5)
from keras_segmentation.models.model_utils import get_segmentation_model
model = get_segmentation_model(img_input , out ) # this would build the segmentation model
keras_segmentation包含几个随时可用的模型,因此在使用现成的模型时不需要编写自己的模型。
选择模型
有几种模型可用于语义分割。应根据使用情况适当选择模型架构。有几件事情应该考虑在内:
- 训练图像的数量
- 图像的大小
- 图像的域
通常,基于深度学习的分割模型建立在基础CNN网络上。通常为基础网络选择标准模型,例如ResNet,VGG或MobileNet。基础网络的一些初始层用于编码器中,并且分段网络的其余部分构建在其上。对于大多数分段模型,可以使用任何基础网络。
选择基本模型
为了选择分段模型,我们的首要任务是选择合适的基础网络。对于许多应用,选择在ImageNet上预先训练的模型是最佳选择。
ResNet:这是微软提出的模型,在ImageNet 2016竞赛中获得了96.4%的准确率。ResNet用作多种应用的预训练模型。ResNet具有大量的层以及剩余连接,这使得它的培训可行。
VGG-16:这是牛津大学提出的模型,在ImageNet 2013竞赛中获得92.7%的准确率。与Resnet相比,它具有较少的层次,因此训练速度更快。对于大多数现有的分段基准,VGG在准确性方面的表现不如ResNet。在ResNet之前,VGG是大量应用程序的标准预训练模型。
MobileNet:此模型由Google提出,针对小型号和更快的推理时间进行了优化。这是在移动电话和资源受限设备上运行的理想选择。由于尺寸小,模型的准确性可能会受到轻微影响。
自定义CNN:除了使用ImageNet预训练模型外,自定义网络还可用作基础网络。如果分段应用程序非常简单,则不需要ImageNet预训练。使用自定义基本模型的另一个优点是我们可以根据应用程序对其进行自定义。
如果分割任务的图像的域类似于ImageNet,那么ImageNet预训练的模型将是有益的。对于具有诸如汽车,动物,人等常见物体的室内/室外图像的输入图像,ImageNet预训练可能是有帮助的。根据任务的输入图像的类型,也可以在其他数据集上训练预训练的模型。
选择基础网络后,我们必须选择分段架构。让我们来看看一些流行的细分模型。
FCN:FCN是最早提出的端到端语义分割模型之一。在这里,通过使FC层1x1卷积,将诸如VGG和AlexNet的标准图像分类模型转换为完全卷积。在FCN,转置卷积用于上采样,与使用数学插值的其他方法不同。这三种变体是FCN8,FCN16和FCN32。在FCN8和FCN16中,使用跳过连接。
SegNet:SegNet架构采用编码器 - 解码器框架。编码器和解码器层彼此对称。解码器层的上采样操作使用相应编码器层的最大池索引。SegNet没有任何跳过连接。与FCN不同,没有可学习的参数用于上采样。
UNet:UNet架构采用带跳过连接的编码器 - 解码器框架。与SegNet一样,编码器和解码器层彼此对称。
PSPNet:金字塔场景分析网络经过优化,可以更好地学习场景的全局上下文表示。首先,将图像传递到基础网络以获得特征图。特征图被下采样到不同的比例。卷积应用于合并的要素图。之后,所有要素图都被上采样到一个共同的比例并连接在一起。最后,使用另一个卷积层来产生最终的分割输出。在这里,通过汇集到高分辨率的特征很好地捕获较小的对象,而通过汇集到较小尺寸的特征捕获较大的对象。
对于包含室内和室外场景的图像,PSPNet是首选,因为对象通常以不同的尺寸存在。这里的模型输入大小应该相当大,大约500x500左右。
对于医学领域的图像,UNet是受欢迎的选择。由于跳过连接,UNet不会错过细节。UNet也可用于小尺寸物体的室内/室外场景。
对于具有大尺寸和少量对象的简单数据集,UNet和PSPNet可能是一种过度杀伤力。在这里,简单的模型,如FCN或Segnet就足够了。
建议尝试使用具有不同模型输入大小的多个分段模型。
如果您不想编写自己的模型,可以从keras_segmentation导入可立即使用的模型 。
import keras_segmentation
model = keras_segmentation.models.unet.vgg_unet(n_classes=51 , input_height=416, input_width=608 )
选择输入大小
除了选择模型的架构外,选择模型输入大小也非常重要。如果图像中有大量对象,则输入大小应更大。在某些情况下,如果输入大小很大,模型应该有更多层来补偿。标准输入大小介于200x200到600x600之间。具有大输入尺寸的模型消耗更多GPU内存并且还需要更多时间来训练。
训练
在准备好数据集并构建模型之后,我们必须训练模型。
model.train(
train_images = "dataset_path/images_prepped_train/",
train_annotations = "dataset_path/annotations_prepped_train/",
checkpoints_path = "checkpoints/vgg_unet_1" , epochs=5
)
这里dataset是训练图像checkpoints的目录,是保存所有模型权重的目录。
现在我们可以在训练集中不存在的新图像上看到模型的输出。
out = model.predict_segmentation(
inp="dataset_path/images_prepped_test/0016E5_07965.png",
out_fname="output.png"
)
我们还可以从保存的模型中获得预测,该模型将自动加载模型和权重。
from keras_segmentation import predict
predict(
checkpoints_path="checkpoints/vgg_unet_1",
inp="dataset_path/images_prepped_test/0016E5_07965.png",
out_fname="output.png"
)
获取目录中多个图像的预测。
from keras_segmentation import predict_multiple
predict_multiple(
checkpoints_path="checkpoints/vgg_unet_1",
inp_dir="dataset_path/images_prepped_test/",
out_dir="outputs/"
)
结论
在这篇文章中,我们讨论了基于深度学习的分割的概念。然后我们讨论了各种流行的模型。使用Keras,我们实现了完整的管道来训练任何数据集的分割模型。我们讨论了如何根据应用选择合适的模型。
相关资源关注微信公众号:“图像算法”或者微信搜索imalg_cn 获取