SegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation
SegNet: A deep convolutional encoder-decoder architecture of image segmentation
Motivation:
SegNet的主要目的是旨在设计一种针对道路场景及室内环境的理解高效架构,在内存消耗及计算时间消耗方面表现良好。
网络结构:
SegNet结构:
- 编码器网络;
- 相应的解码器网络;
- 逐像素分类层。
编码器网络的架构与VGG16网络中的13个卷积模块一致。 解码器网络负责将较低分辨率的编码器特征图生成高分辨率特征图,并用于像素分类。

SegNet的核心创新点体现在其独特的下采样与上采样的机制设计中。具体而言,在编码器分支中执行2×2步长池化操作以实现信息压缩,并记录每个区域的最大值位置信息;通过解码器路径访问相应编码层的位置信息来恢复细节进而完成图像重建过程。相比之下,FCN架构则依赖于transposed convolutions以及双线性插值方法来实现特征图的空间还原,在此过程中每一个像素点的结果是由卷积反向传播计算得到的

Training
- RGB输入并进行局部对比度归一化处理
- 权重初始化采用He方法
- 学习率设置为0.1(固定值),动量因子设定为0.9,并采用SGD优化算法
- 基于Caffe框架实现SegNet-Basic模型
- 在训练过程中,在每个epoch开始前会对样本集进行随机打乱排列;随后将数据划分为大小为12的批次,并按照顺序加载到网络中。
- 采用交叉熵损失函数作为目标函数
- 对损失函数进行加权以实现类别平衡(class balancing),其中权重计算公式为:weight = \frac{\text{所有training set 类别的频率中值}}{\text{目标类别频率}}
- 自然频率平衡法(natural frequency balancing)
评价指标
global accuracy(G) :数据集中所有像素正确分类的百分比
class average accuracy(C) :所有类的预测准确度的平均值
在Pascal VOC12挑战中所涉及的所有类别上计算得到。该指标(mIoU)相较于类平均准确率(class average accuracy)更为严格;这是因为对假阳性预测给予更高的惩罚。然而,在分类平衡的交叉熵损失(class balanced cross-entropy loss)下,并非由该损失直接优化。
mIoU指标亦称"雅克指数"(简称雅克指数),常被应用于基准测试中。然而,Csurka等研究者指出,该指标在某些情况下可能并不完全符合人类对高质量分割结果的定性判断标准(即等级标准)。他们的研究表明,mIoU指标有助于减少区域不规则性,但可能影响边界清晰度的评价,而FCN领域的最新研究也对此进行了探讨。为此建议采用基于BFSM分数 的边缘测量方法作为mIoU的一个补充工具,此边缘测量方法通常用于评估无监督图像分割的质量状况。为此研究者将此指标扩展至语义分割场景,并发现其结合使用于mIoU评价体系时能够更好地反映分割质量的一致性。
语义轮廓分数的核心概念在于计算F1分数 ,这是衡量预测边界与实际边界的准确性的关键指标。具体而言,在给定的像素公差范围(此处将公差设定为图像对角线长度的75%)内进行比较。我们采用以下步骤进行评估:首先,在测试集中的每个类别样本上计算其对应的F1分数 ,然后将这些数值求平均来获得该图像级别的F1分数 。随后计算所有测试样本的整体平均,并将其定义为整体测试集上的边界 F1分数 (BF),即BF被定义为整体测试集上的平均F1分数 。
Analysis
- 当encoder的所有特征图被完整存储时,在语义轮廓描绘度量(BF)方面表现出最佳表现。
- 在有限存储条件下,在适当选择 decoder 的情况下(如 SegNet 类型),能够有效利用 encoder 的压缩特征表示(通过 max-pooling indices 实现维度降维),从而显著提升网络性能。
- 较大尺寸的 decoder 网络结构显著提升了网络性能水平。
基于keras实现的网络结构代码如下:
from keras.layers import Input
from keras.layers.convolutional import Convolution2D
from keras.layers.core import Activation, Reshape
from keras.layers.normalization import BatchNormalization
from keras.models import Model
from layers import MaxPoolingWithArgmax2D, MaxUnpooling2D
def segnet(input_shape, n_labels, kernel=3, pool_size=(2, 2), output_mode="softmax"):
# encoder
inputs = Input(shape=input_shape)
conv_1 = Convolution2D(64, (kernel, kernel), padding="same")(inputs)
conv_1 = BatchNormalization()(conv_1)
conv_1 = Activation("relu")(conv_1)
conv_2 = Convolution2D(64, (kernel, kernel), padding="same")(conv_1)
conv_2 = BatchNormalization()(conv_2)
conv_2 = Activation("relu")(conv_2)
pool_1, mask_1 = MaxPoolingWithArgmax2D(pool_size)(conv_2)
......
conv_5 = Convolution2D(256, (kernel, kernel), padding="same")(pool_2)
conv_5 = BatchNormalization()(conv_5)
conv_5 = Activation("relu")(conv_5)
conv_6 = Convolution2D(256, (kernel, kernel), padding="same")(conv_5)
conv_6 = BatchNormalization()(conv_6)
conv_6 = Activation("relu")(conv_6)
conv_7 = Convolution2D(256, (kernel, kernel), padding="same")(conv_6)
conv_7 = BatchNormalization()(conv_7)
conv_7 = Activation("relu")(conv_7)
pool_3, mask_3 = MaxPoolingWithArgmax2D(pool_size)(conv_7)
......
conv_11 = Convolution2D(512, (kernel, kernel), padding="same")(pool_4)
conv_11 = BatchNormalization()(conv_11)
conv_11 = Activation("relu")(conv_11)
conv_12 = Convolution2D(512, (kernel, kernel), padding="same")(conv_11)
conv_12 = BatchNormalization()(conv_12)
conv_12 = Activation("relu")(conv_12)
conv_13 = Convolution2D(512, (kernel, kernel), padding="same")(conv_12)
conv_13 = BatchNormalization()(conv_13)
conv_13 = Activation("relu")(conv_13)
pool_5, mask_5 = MaxPoolingWithArgmax2D(pool_size)(conv_13)
print("Build enceder done..")
# decoder
unpool_1 = MaxUnpooling2D(pool_size)([pool_5, mask_5])
conv_14 = Convolution2D(512, (kernel, kernel), padding="same")(unpool_1)
conv_14 = BatchNormalization()(conv_14)
conv_14 = Activation("relu")(conv_14)
conv_15 = Convolution2D(512, (kernel, kernel), padding="same")(conv_14)
conv_15 = BatchNormalization()(conv_15)
conv_15 = Activation("relu")(conv_15)
conv_16 = Convolution2D(512, (kernel, kernel), padding="same")(conv_15)
conv_16 = BatchNormalization()(conv_16)
conv_16 = Activation("relu")(conv_16)
......
unpool_3 = MaxUnpooling2D(pool_size)([conv_19, mask_3])
conv_20 = Convolution2D(256, (kernel, kernel), padding="same")(unpool_3)
conv_20 = BatchNormalization()(conv_20)
conv_20 = Activation("relu")(conv_20)
conv_21 = Convolution2D(256, (kernel, kernel), padding="same")(conv_20)
conv_21 = BatchNormalization()(conv_21)
conv_21 = Activation("relu")(conv_21)
conv_22 = Convolution2D(128, (kernel, kernel), padding="same")(conv_21)
conv_22 = BatchNormalization()(conv_22)
conv_22 = Activation("relu")(conv_22)
......
unpool_5 = MaxUnpooling2D(pool_size)([conv_24, mask_1])
conv_25 = Convolution2D(64, (kernel, kernel), padding="same")(unpool_5)
conv_25 = BatchNormalization()(conv_25)
conv_25 = Activation("relu")(conv_25)
conv_26 = Convolution2D(n_labels, (1, 1), padding="valid")(conv_25)
conv_26 = BatchNormalization()(conv_26)
conv_26 = Reshape(
(input_shape[0] * input_shape[1], n_labels),
input_shape=(input_shape[0], input_shape[1], n_labels),
)(conv_26)
outputs = Activation(output_mode)(conv_26)
print("Build decoder done..")
model = Model(inputs=inputs, outputs=outputs, name="SegNet")
return model
完整代码参考:基于Keras的SegNet开发 github.com/ykamikawa/tf-keras-SegNet
参考:关于SegNet的详细解析 zhihu.com/zhuanye/p/36525939

