图像分割——Encoder-DecoderBasedModels(SegNet)理解和代码分析
Encoder-Decoder Based Models简介:
Encoder-Decoder Based Models是⾮常经典的⼀种图像分割模型(FNC⽹络,严格来说,其实也是这种模式)。模型通常由两部分组成,即Encoder部分和Decoder 部分。Encoder会通过convolutional layers对image进⾏下采样,使得图⽚size减⼩,⽽channel增加。常见使⽤的下采样⽹络有:vgg-16、ResNet。通过去掉下采样⽹络的fn层,只保留卷积层,从⽽达到对图⽚下采样(Encoder)的⽬的。Decoder 有两种⽅法:使⽤deconv对图⽚上采样,从⽽将Encoder的特征图Decoder 为像素分割图;对特征图进⾏unpooling后,做same卷积。
更直观的理解就是,Encoder负责将⼀张图⽚的每个像素点,通过复杂的计算过程,映射到某⼀个⾼维分布上,⽽Decoder则是负责将这个⾼维分布,映射到给定的类别区域。中间的⾼维分布,是我们不可见的,但神经⽹络却可以很好的使⽤它。正是这种借助中间的⾼维分布的思想,搭建起来了原图像到像素级分类图像的桥梁,实现了end-to-end的训练过程。
SegNet的背景:
SegNet是⼀个由剑桥⼤学团队开发的图像分割的开源项⽬,该项⽬可以对图像中的物体所在区域进⾏分
割,例如车,马路,⾏⼈等,并且精确到像素级别。但是由于传统的SegNet的编码器和解码器都依赖于深层的卷积神经⽹络,计算量较⼤,所以在实际使⽤中,我们为了达到实时分割的⽬的,通常使⽤其简化版(对通道数进⾏了削减)——SegNet Basic。
想法和⽅法:
SegNet的编码器,采⽤的是去掉了全连接层的vgg-16⽹络。在Encoder阶段,每⼀层通过bn,relu和conv提取特征,通过maxpool进⾏降维(卷积阶段图⽚size不变,maxpool阶段图⽚size缩⼩⼀倍)。在进⾏maxpool的过程中,SegNet创新性的提出了储存maxpool的选择⽬标的原始位置,留作后⾯unpooling使⽤。在Decoder 阶段,每⼀次通过利⽤前⾯记录的对应层的maxpool位置进⾏unpooling,然后对unpooling后的图⽚进⾏bn,relu和conv进⾏特征还原(unpooling阶段图⽚size增⼤⼀倍,卷积阶段图⽚size不变)。最终将得到和原图像⼀样⼤⼩的图⽚经过softmax激活,得到像素级分割图。
优点:
⽂中使⽤了记录原始位置的maxpool,这在进⾏unpooling时,相⽐较随机分配pooling的位置,或者固定分配pooling的位置可以极⼤的增加像素级分类图像的边缘形状;相⽐较于传统的deconv上采样,则是直接省略掉了学习的过程(deconv既需要进⾏上采样的学习,⼜需要特征还原的学习),使得模型只专注于特征还原的学习。个⼈认为,这⾥的maxpool具有传统的skip connect的整合local和global信息的作⽤,但是却使得计算量下降了⼀半(传统的 lateral connection后,图像通道数增加了⼀倍)。
不⾜:
在SeNet中,是通过从前⽽后的⽅法计算出每⼀个像素点在每个类别的概率,并通过softmax激活(选择概率最⼤的类别)。由于在编码和解码的过渡中,⾼维数据不可控,这就会导致⼀个问题:这种先验概率计算⽅法,我们⽆法保证最后softmax的结果是正确的。
改进:
为了解决前⾯所提到的先验概率结果正确率的不可控性,提出了使⽤后验概率的Bayesian SegNet:
Bayesian SegNet相⽐较于传统的SegNet最⼤的进步就是,可以给出结果的置信度(这⾥可以⾮常明确,Bayesian SegNet对于传统SegNet的性能并没有提升)。结构上看,Bayesian SegNet不过是相对于SegNet添加了⼀个DropOut层。
DropOut层的⼯作原理,是在每⼀轮训练时,对每个神经元添加了⼀个激活概率(激活时,神经元会得到正常训练;未激活时,神经元不会被训练),这⾥我们可以将激活看做1,不激活看做0,论⽂中将激活概率设置为0.5。
由蒙特卡洛抽样可知,当试验次数⾜够多时,频率可以看做事件发⽣的概率,因此通过蒙特卡罗抽样,就可以求出⼀个新分布的均值与⽅差,这样使⽤⽅差⼤⼩就可以知道⼀个分布对于样本的差异性,我们知道⽅差越⼤差异越⼤。反应在图像上,⽅差⼤的地⽅,就是分类不确定性⼤的地⽅。
图⽚的第⼀⾏是分割原图,第⼆⾏是ground true,第三⾏是分割结果,第四⾏是Bayesian SegNet输出的不确定性图,且颜⾊越深,不确定性越⼤。
我们可以看到不确定性⼤的地⽅,主要有3点:
1)两种类别的边界处
2)由于遮挡,或者复杂形状⽽难以识别的物体
3)模糊的分类(如狗和猫,椅⼦和桌⼦)
代码分析(以SegNet Basic为例):
看了好多博主给的代码,都有问题,于是⾃⼰费了⼀番⼒⽓从GitHub上了⼀份⾃认为还⾏的。
下⾯展⽰模型核⼼代码。
class SegNet(nn.Module):
'''初始化⽹络结构'''
def __init__(self,input_nbr,label_nbr):
super(SegNet, self).__init__()
batchNorm_momentum =0.1
self.bn11 = nn.BatchNorm2d(64, momentum= batchNorm_momentum)
self.bn12 = nn.BatchNorm2d(64, momentum= batchNorm_momentum)
self.bn21 = nn.BatchNorm2d(128, momentum= batchNorm_momentum)        v22 = nn.Conv2d(128,128, kernel_size=3, padding=1)
self.bn22 = nn.BatchNorm2d(128, momentum= batchNorm_momentum)
self.bn31 = nn.BatchNorm2d(256, momentum= batchNorm_momentum)        v32 = nn.Conv2d(256,256, kernel_size=3, padding=1)
self.bn32 = nn.BatchNorm2d(256, momentum= batchNorm_momentum)        v33 = nn.Conv2d(256,256, kernel_size=3, padding=1)
self.bn33 = nn.BatchNorm2d(256, momentum= batchNorm_momentum)
self.bn41 = nn.BatchNorm2d(512, momentum= batchNorm_momentum)        v42 = nn.Conv2d(512,512, kernel_size=3, padding=1)
self.bn42 = nn.BatchNorm2d(512, momentum= batchNorm_momentum)        v43 = nn.Conv2d(512,512, kernel_size=3, padding=1)
self.bn43 = nn.BatchNorm2d(512, momentum= batchNorm_momentum)
self.bn51 = nn.BatchNorm2d(512, momentum= batchNorm_momentum)        v52 = nn.Conv2d(512,512, kernel_size=3, padding=1)
self.bn52 = nn.BatchNorm2d(512, momentum= batchNorm_momentum)        v53 = nn.Conv2d(512,512, kernel_size=3, padding=1)
self.bn53 = nn.BatchNorm2d(512, momentum= batchNorm_momentum)
self.bn53d = nn.BatchNorm2d(512, momentum= batchNorm_momentum)        v52d = nn.Conv2d(512,512, kernel_size=3, padding=1)
self.bn52d = nn.BatchNorm2d(512, momentum= batchNorm_momentum)        v51d = nn.Conv2d(512,512, kernel_size=3, padding=1)
self.bn51d = nn.BatchNorm2d(512, momentum= batchNorm_momentum)
self.bn43d = nn.BatchNorm2d(512, momentum= batchNorm_momentum)        v42d = nn.Conv2d(512,512, kernel_size=3, padding=1)
self.bn42d = nn.BatchNorm2d(512, momentum= batchNorm_momentum)        v41d = nn.Conv2d(512,256, kernel_size=3, padding=1)
self.bn41d = nn.BatchNorm2d(256, momentum= batchNorm_momentum)
self.bn33d = nn.BatchNorm2d(256, momentum= batchNorm_momentum)        v32d = nn.Conv2d(256,256, kernel_size=3, padding=1)
self.bn32d = nn.BatchNorm2d(256, momentum= batchNorm_momentum)        v31d = nn.Conv2d(256,128, kernel_size=3, padding=1)
self.bn31d = nn.BatchNorm2d(128, momentum= batchNorm_momentum)
self.bn22d = nn.BatchNorm2d(128, momentum= batchNorm_momentum)        v21d = nn.Conv2d(128,64, kernel_size=3, padding=1)
self.bn21d = nn.BatchNorm2d(64, momentum= batchNorm_momentum)
self.bn12d = nn.BatchNorm2d(64, momentum= batchNorm_momentum)        v11d = nn.Conv2d(64, label_nbr, kernel_size=3, padding=1)
def forward(self, x):
'''⽹络的计算过程'''
'''Stage 1、2、3、4、5为encoder过程'''
'''每⼀个过程可以分解为两次size不变的卷积 + ⼀次size缩⼩⼀半的maxpool'''
# Stage 1
x11 =F.relu(self.v11(x)))
x12 =F.relu(self.v12(x11)))
x1p, id1 =F.max_pool2d(x12,kernel_size=2, stride=2,return_indices=True)
x1p, id1 =F.max_pool2d(x12,kernel_size=2, stride=2,return_indices=True)
# Stage 2
x21 =F.relu(self.v21(x1p)))
x22 =F.relu(self.v22(x21)))
x2p, id2 =F.max_pool2d(x22,kernel_size=2, stride=2,return_indices=True)
# Stage 3
x31 =F.relu(self.v31(x2p)))
x32 =F.relu(self.v32(x31)))
x33 =F.relu(self.v33(x32)))
x3p, id3 =F.max_pool2d(x33,kernel_size=2, stride=2,return_indices=True)
# Stage 4
x41 =F.relu(self.v41(x3p)))
x42 =F.relu(self.v42(x41)))
x43 =F.relu(self.v43(x42)))
x4p, id4 =F.max_pool2d(x43,kernel_size=2, stride=2,return_indices=True)
decoder
# Stage 5
x51 =F.relu(self.v51(x4p)))
x52 =F.relu(self.v52(x51)))
x53 =F.relu(self.v53(x52)))
x5p, id5 =F.max_pool2d(x53,kernel_size=2, stride=2,return_indices=True)
'''Stage 6、7、8、9、10为decoder过程'''
'''每⼀个过程可以分解为⼀次size放⼤⼀倍的带index的unpooling + 三次size不变的卷积'''        # Stage 6
x5d =F.max_unpool2d(x5p, id5, kernel_size=2, stride=2)
x53d =F.relu(self.v53d(x5d)))
x52d =F.relu(self.v52d(x53d)))
x51d =F.relu(self.v51d(x52d)))
# Stage 7
x4d =F.max_unpool2d(x51d, id4, kernel_size=2, stride=2)
x43d =F.relu(self.v43d(x4d)))
x42d =F.relu(self.v42d(x43d)))
x41d =F.relu(self.v41d(x42d)))
# Stage 8
x3d =F.max_unpool2d(x41d, id3, kernel_size=2, stride=2)
x33d =F.relu(self.v33d(x3d)))
x32d =F.relu(self.v32d(x33d)))
x31d =F.relu(self.v31d(x32d)))
# Stage 9
x2d =F.max_unpool2d(x31d, id2, kernel_size=2, stride=2)
x22d =F.relu(self.v22d(x2d)))
x21d =F.relu(self.v21d(x22d)))
# Stage 10
x1d =F.max_unpool2d(x21d, id1, kernel_size=2, stride=2)
x12d =F.relu(self.v12d(x1d)))
x11d = v11d(x12d)
return x11d
def load_from_segnet(self, model_path):
s_dict = self.state_dict()# create a copy of the state dict
th = torch.load(model_path).state_dict() # load the weigths
# for name in th:
# s_dict[corresp_name[name]]= th[name]
self.load_state_dict(th)

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