百度Apollo无人车车道线检测挑战赛(二)

百度Apollo车道线分割比赛二(模型搭建)

百度Apollo车道线分割比赛一(数据处理)
百度Apollo车道线分割比赛三(模型训练)

本实现暂时实现两种常见的分割模型Unet,deeplabv3plus,最后在实现两个模型的融合(百度比赛主要看分割的miou不侧重模型的实时性),其中Unet的backbone采用resnet101,deeplabv3plus采用的backbone为Xcepthon65。backbone可以灵活选择,而且pytorch中有预训练好的权重,可以加载实现快速的训练。

一.Unet
Resnet系列参数如下图,在Unet模型分为 encoder block 和decoder block ,resnet去掉最后的全连接层作为Unet的backbone。另外decoder block上采样采用转置卷积,上采样止原图的1/4大小,最后计算loss时需要将最后输出双线性插值到原图大小再和label做loss。
resnet系列:
百度Apollo无人车车道线检测挑战赛(二)_第1张图片
Unet结构:
百度Apollo无人车车道线检测挑战赛(二)_第2张图片

处理文件为models.Unet_ResNet101.py,代码如下:

#coding:utf-8
#@author: Jiangnan He
#@date:2019.12.10 18:21
'''
resnet101-unet
1.上采样  还是采用 转置卷积
2.进行高低层特征融合时使用了torch.cat() 替换了 FCN 中的 "+"
3.resnet101 下采样1/32 所以进行了 5次下采样 本实现进行了 3次高低层特征融合  最后得到heatmap为原图的1/4
4.与label做loss 时直接将输出 heatmap 上采样到image尺寸

  layer0         input                                                           
                  ↓                                                                 
  layer1     conv3x3  ch=64          (1/2)                                       
                  ↓                                                                 
  layer2     reslayer(maxpool) ch=256  (1/4)   ---------------------------   256+256---conv3 ch=256
                  ↓                                                                 ↑upconv2
  layer3     reslayer   ch=512       (1/8) ------------------------------ 512+512---conv3 ch=512
                  ↓                                                                 ↑upconv2
  layer4     reslayer   ch=1024     (1/16)---------------------------------- 1024+1024--conv3 ch=1024
                  ↓                                                                 ↑upconv2
  layer5     reslaeyr  ch=2048      (1/32)-----------------------------------------
'''
import torch
import torch.nn as nn
from torch.autograd import Variable
from torchsummary import summary
from torch.nn import functional as F

class Block(nn.Module):
    def __init__(self,in_ch,out_ch,ksize=3,stride=1,padding=1):
        super(Block,self).__init__()
        self.conv1=nn.Conv2d(in_ch,out_ch,kernel_size=ksize,stride=stride,padding=padding)
        self.bn=nn.BatchNorm2d(out_ch)
        self.relu=nn.ReLU(inplace=True)
    def forward(self, x):
        return self.relu(self.bn(self.conv1(x)))

def make_layers(in_channels, layer_list):
    layers = []
    for v in layer_list:
        layers += [Block(in_channels, v)]
        in_channels = v
    return nn.Sequential(*layers)

#层
class Layer(nn.Module):
    def __init__(self, in_channels, layer_list):
        super(Layer, self).__init__()
        self.layer = make_layers(in_channels, layer_list)
    def forward(self, x):
        out = self.layer(x)
        return out

#残差块
class ResBlock(nn.Module):
    def __init__(self,ch_list,downsample,Res):# ch_list=[in_ch,ch,out_ch]
        super(ResBlock,self).__init__()

        self.res=Res# 残差块 还是 瓶颈块的标志位
        self.ds = downsample  # 残差块时  是否下采样
        #第一个1x1 卷积
        self.firconv1x1=Block(ch_list[0],ch_list[1],1,1,0)         #第一个1x1卷积   不下采样
        self.firconv1x1d = Block(ch_list[0],ch_list[1], 1,2,0)   #第一个1x1卷积   下采样
        # 3x3卷积
        self.conv3x3=Block(ch_list[1],ch_list[1],3,1,1)
        #第二个1x1卷积
        self.secconv1x1=Block(ch_list[1],ch_list[2],1,1,0)
        #skip 卷积操作
        self.resconv1x1d=Block(ch_list[0],ch_list[2],1,2,0)        #skip下采样的1x1卷积
        self.resconv1x1=Block(ch_list[0],ch_list[2],1,1,0)         #skip下采样的1x1卷积

    def forward(self,x):
        if self.res==True:#此时为残差块
            if self.ds==True:#skip有卷积操作需要下采样
                residual=self.resconv1x1d(x)
                f1=self.firconv1x1d(x)
            else:#skip有卷积操作不需要下采样
                residual = self.resconv1x1(x)
                f1 = self.firconv1x1(x)

        else:# 瓶颈块sikp无卷积操作
            residual=x
            f1 = self.firconv1x1(x)
        f2=self.conv3x3(f1)
        f3=self.secconv1x1(f2)
        f3+=residual
        return f3

#Res层
class ResLayer(nn.Module):
    def __init__(self,ch_list1,ch_list2,downsample,numBotBlock):
        super(ResLayer,self).__init__()
        self.num = numBotBlock
        self.resb=ResBlock(ch_list1,downsample,True)
        self.botb=ResBlock(ch_list2,downsample,False)
        self.BoB=self.make_layers()
    def make_layers(self):
        layers=[]
        for i in range(self.num):
            layers+=[self.botb]
        return nn.Sequential(*layers)
    def forward(self,x):
        res=self.resb(x)
        return self.BoB(res)

#resnet101
class ResNet101(nn.Module):
    def __init__(self):
        super(ResNet101,self).__init__()
        self.conv0=nn.Conv2d(3,64,kernel_size=3,stride=1,padding=1)
        self.conv1=nn.Conv2d(64,64,kernel_size=3,stride=2,padding=1)    #1/2
        self.pool=nn.MaxPool2d(kernel_size=2,stride=2)                 #1/4
        self.layer1=ResLayer([64,64,256],[256,64,256],False,4)
        self.layer2=ResLayer([256,128,512],[512,128,512],True,3)       #1/8   ch= 512
        self.layer3=ResLayer([512,256,1024],[1024,256,1024],True,23)   #1/16   ch= 1024
        self.layer4=ResLayer([1024,512,2048],[2048,512,2048],True,6)   #1/32   ch=2048
        #权重初始化 conv 以及BN
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')#mode="fan_in" weight方差在前向传播中保持不变 mode="fan_out" weight后向传播方差不变
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def forward(self, x):
        layer0=self.conv0(x)                    #1
        layer1=self.conv1(layer0)               #1/2
        layer2 =self.layer1(self.pool(layer1) ) #1/4
        layer3=self.layer2(layer2)                         #1/8
        layer4 = self.layer3(layer3)                       #1/16
        layer5 = self.layer4(layer4)                       #1/32
        #print(layer0.size(),layer1.size(),layer2.size(),layer3.size(),layer4.size(),layer5.size())
        return [layer2,layer3,layer4,layer5]



#上采样模块
class Upsample(nn.Module):

    def __init__(self,in_ch1,in_ch2,upsampleratio=2):#  layer42048 layer3 1024
        super(Upsample,self).__init__()
        self.conv1=Block(in_ch1,in_ch2)# layer4 2048-->1024
        self.conv2=Block(in_ch2,in_ch2)#layer3 1024-->1024
        self.conv3=Block(in_ch2*2,in_ch2) # cat layer3  2048--> 1024
        self.trans_conv=nn.ConvTranspose2d(in_ch2,in_ch2,upsampleratio,stride=upsampleratio)
    def forward(self,featrue1,featrue2):#layer4 ,layer3
        #
        return self.conv3( torch.cat([ self.trans_conv(self.conv1(featrue1)), self.conv2(featrue2) ],dim=1)   )

class UnetDecode(nn.Module):
    def __init__(self,n,in_ch,out_ch,upsratio,trans_conv=False):     # in_ch  VGG 后通道数  out_ch 为上采样之后的通道数
        super(UnetDecode,self).__init__()
        self.conv1=Layer(in_ch,[out_ch]*n)         #这里加n层Block
        self.trans_conv=nn.ConvTranspose2d(out_ch,out_ch,upsratio,stride=upsratio)#        upsratio 上采样倍数

    def forward(self,x):
        return self.trans_conv(self.conv1(x))

class Unet_resnet101(nn.Module):
    def __init__(self,n,in_ch,out_ch,upsratio):
        super(Unet_resnet101,self).__init__()
        self.encode=ResNet101()
        self.catup1=Upsample(2048,1024)
        self.catup2=Upsample(1024,512)
        self.catup3=Upsample(512,256)
        self.decode=UnetDecode(n,in_ch,out_ch,upsratio)
        self.classfier=nn.Conv2d(256,10,3,padding=1)

    def forward(self,x):
        size=x.shape[-2:]
        features=self.encode(x)
        [layer2,layer3,layer4,layer5]=features#
        #layer1 256*16   layer2 :512*8*8   layer3 :  1024*4*4   layer4 : 2048*2*2
        layer4=self.catup1(layer5,layer4)
        layer3=self.catup2(layer4,layer3)
        layer2=self.catup3(layer3,layer2)
        #return self.classfier(layer2)
        return F.interpolate(self.classfier(layer2), size=size, mode='bilinear', align_corners=False)

if __name__=="__main__":
    if  torch.cuda.is_available():
        model=Unet_resnet101(4,2048,64,4).cuda()
        x = Variable(torch.randn(1, 3, 64, 64)).cuda()
    else:
        model=Unet_resnet101(4,2048,64,4)
        x = Variable(torch.randn(1, 3, 64, 64))
    model.eval()
    y=model(x)
    print("y.size",y.size())
    summary(model,(3,64,64))

二. Deeplabv3plus
Xception系列Xception41 ,Xception65 ,Xception71 其中Xception41结构如图,本文采用的是Xception65作为backbone。
百度Apollo无人车车道线检测挑战赛(二)_第3张图片
Deeplabv3plus结构如下,与Unet不同卷积采用深度可分离卷积,上采样方式为双线性插值:
百度Apollo无人车车道线检测挑战赛(二)_第4张图片
处理文件为models.deeplabv3p_Xception65.py,代码如下:

#coding:utf-8
#@author: Jiangnan He
#@date:2019.12.17 15:03
''
'''
本实现为deeplabv3+,backbone为Xception_65 
'''

import torch.nn as nn
import torch
from torchsummary import summary
from torch.nn import functional as F
#实现深度分离卷积 将卷积分程in_ch 组进行卷积 接着用1x1 卷积进行融合
class depthwiseconv(nn.Module):
    def __init__(self, in_ch, out_ch,ksize=3,stride=1,padding=1):
        super(depthwiseconv, self).__init__()
        self.depth_conv = nn.Conv2d(
            in_channels=in_ch,
            out_channels=in_ch,
            kernel_size=ksize,
            stride=stride,
            padding=padding,
            groups=in_ch
        )
        self.point_conv = nn.Conv2d(
            in_channels=in_ch,
            out_channels=out_ch,
            kernel_size=1,
            stride=1,
            padding=0,
            groups=1
        )
    def forward(self, x):
        return self.point_conv(self.depth_conv(x))

#块
class Block(nn.Module):
    def __init__(self,in_ch,out_ch,ksize=3,stride=1,padding=1,sep=True):
        super(Block,self).__init__()
        #是否为深度可分离卷积模块标志位
        self.issepconv = sep
        self.conv=nn.Conv2d(in_ch,out_ch,kernel_size=ksize,stride=stride,padding=padding,bias=False)
        self.sepconv = depthwiseconv(in_ch, out_ch, ksize, stride, padding)
        self.Bn=nn.BatchNorm2d(out_ch)
        self.relu=nn.ReLU(inplace=True)

    def forward(self,x):
        if self.issepconv==True:
            return self.relu(self.Bn(self.sepconv(x)))
        else:
            return self.relu(self.Bn(self.conv(x)))


#层
class Layer(nn.Module):
    def __init__(self,in_ch,out_ch,ksize,stride,padding=1,upsample=True):# 64 128 3 1 1
        super(Layer,self).__init__()
        self.upsample=upsample#是否进行下采样
        self.block1=Block(in_ch,out_ch,ksize,stride,padding)
        self.block2 = Block(out_ch, out_ch, ksize, stride, padding)
        self.block3 = Block(out_ch, out_ch, ksize, stride, padding)#不进行下采样时ksize=3 padding=1 same
        #下采样时
        self.block4=Block(out_ch, out_ch, ksize, stride=2, padding=1)#进行下采样时
        self.skipconv=nn.Conv2d(in_ch,out_ch,kernel_size=1,stride=2,padding=0)#padding设置注意
        self.skipconv1=nn.Conv2d(in_ch,out_ch,kernel_size=1,stride=1,padding=0)#padding设置注意
        if self.upsample==True:#进行下采样时
            layer = [self.block1, self.block2, self.block4]
        else:#不进行下采样时的组合
            layer=[self.block1,self.block2,self.block3]
        self.layer = self.layer=nn.Sequential(*layer)

    def forward(self,x):
        if self.upsample==True:
            out=self.layer(x)
            skipout=self.skipconv(x)
            return torch.cat([out,skipout],dim=1)
        else:
            out=self.layer(x)
            skipout=self.skipconv1(x)
            return torch.cat([out,skipout],dim=1)



#模型 Xception 作为backbone
class DCNN(nn.Module):
    def __init__(self):
        super(DCNN,self).__init__()
        self.conv1=Block(3,32,3,2,1,False)#conv  1/2
        self.conv2=Block(32,64,3,1,1,False)#conv
        self.layer1=Layer(64,128,3,1,1,True)#128  1/4
        self.layer2=Layer(256,256,3,1,1,True) #512 /8
        self.layer3 =Layer( 512,728, 3, 1, 1, True)# 1456 /16
        self.BlockLayer=Layer(1456,728,3,1,1,False)#repeat 16times
        self.layer4 = self.make_layer(16)
        self.layer5=Layer(1456,1024,3,1,1,True) #1/32
        self.conv3=Block(2048,1536,3,1,1,True)#sepconv
        self.conv4 = Block(1536, 1536, 3, 1, 1, True)  # sepconv
        self.conv5=Block(1536, 2048, 3, 1, 1, True)  # sepconv

    def make_layer(self,numlayer):
        layer=[]
        for i in range(numlayer):
           layer+=[self.BlockLayer]
        return nn.Sequential(*layer)

    def forward(self, x):
        #entry flow
        conv1=self.conv1(x)
        conv2=self.conv2(conv1)
        layer1=self.layer1(conv2)#
        layer2=self.layer2(layer1)
        layer3 = self.layer3(layer2)
        #middlle flow
        layer4 = self.layer4(layer3)
        #exit flow
        layer5 = self.layer5(layer4)
        conv3=self.conv3(layer5)
        conv4=self.conv4(conv3)#
        conv5=self.conv5(conv4)
       # print("DCNN ",conv1.size(), conv2.size(),layer1.size(),layer2.size(),layer3.size() ,layer4.size(), layer5.size(), conv3.size(),conv4.size(),conv5.size())
        return layer2,conv5

class ASPPConv(nn.Sequential):
    def __init__(self, in_channels, out_channels, dilation):
        modules = [
            nn.Conv2d(in_channels, out_channels, 3, padding=dilation, dilation=dilation, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU()
        ]
        super(ASPPConv, self).__init__(*modules)

class ASPPPooling(nn.Sequential):
    def __init__(self, in_channels, out_channels):
        super(ASPPPooling, self).__init__(
            nn.AdaptiveAvgPool2d(1),
            nn.Conv2d(in_channels, out_channels, 1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU())

    def forward(self, x):
        size = x.shape[-2:]
        for mod in self:
            x = mod(x)
        return F.interpolate(x, size=size, mode='bilinear', align_corners=False)


class ASPP(nn.Module):
    def __init__(self, in_channels):
        super(ASPP, self).__init__()
        out_channels = 256
        modules = []
        modules.append(nn.Sequential(
            nn.Conv2d(in_channels, out_channels, 1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU()))

        rate1, rate2, rate3 = (6,12,18)
        modules.append(ASPPConv(in_channels, out_channels, rate1))
        modules.append(ASPPConv(in_channels, out_channels, rate2))
        modules.append(ASPPConv(in_channels, out_channels, rate3))
        modules.append(ASPPPooling(in_channels, out_channels))
        self.convs = nn.ModuleList(modules)

        self.project = nn.Sequential(
            nn.Conv2d(5 * out_channels, out_channels, 1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
            nn.Dropout(0.5))

    def forward(self, x):
        res = []
        for conv in self.convs:
            res.append(conv(x))
        res = torch.cat(res, dim=1)
        return self.project(res)


class deeplabv3p(nn.Module):
    def __init__(self):
        super(deeplabv3p,self).__init__()
        self.DCNN=DCNN()
        self.ASPP=ASPP(2048)
        self.conv1=Block(512,256,ksize=1,padding=0,sep=False)
        self.conv2=Block(2*256,8,ksize=3,padding=1,sep=False)# 原本为 3x3  这里测试模型 输入很小 所以改为1x1
        self.conv3 = Block(256, 256, ksize=1, padding=0, sep=False)


        #初始化参数
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')#mode="fan_in" weight方差在前向传播中保持不变 mode="fan_out" weight后向传播方差不变
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def forward(self,x):
        lowfeature,out=self.DCNN(x)
        lowfeature=self.conv1(lowfeature)#
        #print(lowfeature.size(),'lowfeaturesize1')
        f1=self.conv3(self.ASPP(out))
        #print(f1.size(),'f1')
        hightfeature=F.interpolate(f1,scale_factor=4, mode='bilinear', align_corners=False)
        #print(hightfeature.size(),"hightfeatsize")
        out1=torch.cat([lowfeature,hightfeature],dim=1)
        return F.interpolate(self.conv2(out1),scale_factor=8, mode='bilinear', align_corners=False)

if __name__=="__main__":
    if torch.cuda.is_available():
        x=torch.randn(1,3,128, 384).cuda()
        model=deeplabv3p().cuda()
    else:
        x=torch.randn(1,3,128, 384)
        model=deeplabv3p()
    model.eval()
    y=model(x)
    print(y.size(),"y.size")
    '''
    summary(model, (3, 128, 384))
    '''








你可能感兴趣的:(Python,深度学习)