目录

一、模型概述

二、BackBone构建

三、语义分割分支

四、实例分割分支

五、代码汇总


车道线分割 opencv 车道线分割模型_深度学习

一、模型概述

整个模型有一个backbone,以及两个分支,输入图片之后,先进入backbone,这一部分是两个分支共用参数,输出给两个分支,上面的图的彩色部分,是实例分割的分支,黑白部分,是语义分割的分支。将二者结合,通过聚类损失函数,进行反向传播,从而完成训练。

二、BackBone构建

可以使用的backbone有很多,这里我使用了resnet50,对网络修改一下就可以。

先看看resnet的forward部分,输入一张图之后,每一步得到的是怎样的输出,比如我输入的图是(B, 3, 360, 640),使用调试模式,进入模型forward部分,一步一步看输出形状。

之后我选出来八倍下采样之后的结果(B, 512, 45, 80),再写一个8倍上采样部分,结果(B, 8, 360,640)作为输出,给到两个分支。注意通道数的调整。(这里又扩大了一下通道数,目的是使用膨胀卷积,增加一下感受野,之后再一步步减小通道数,最后进行上采样。)

注意:这里我是直接进入torchvision里面,复制出来了resnet,并且直接删去了模型forward中的后半部分,因此才可以直接使用resnet输出的结果。

def __init__(self, embed_dim):
        super(LaneNet, self).__init__()
        self.embedding_dim = embed_dim
        self.backbone = resnet50(pretrained=True)

        self.upsample8x = nn.Sequential(
            # 进行一次膨胀卷积,增加感受野,之后再一步步降下来,最后再上采样。
            nn.Conv2d(512,1024,3, padding=4,dilation=4,bias=False),
            nn.BatchNorm2d(1024),
            nn.ReLU(),
            nn.Conv2d(1024, 256, 3, padding=1, bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(256, 64, 3, padding=1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.Conv2d(64, 8, 3, padding=1, bias=False),
            nn.BatchNorm2d(8),
            nn.ReLU(),
            nn.Upsample(scale_factor=8, align_corners=True,mode='bilinear'))

三、语义分割分支

语义分割所需要的输出,是(B, 2, 360, 640),其中第二个维度的2,可以认为一个是用来判定车道线的,一个是用来判定背景的。这样的话,根据输入的形状,写一个专属分支即可,这里从backbone过来的时候是(B, 8, 360,640),就通过卷积+BN等层,生成目标形状即可。

self.seg = nn.Sequential(
            nn.Conv2d(8,8,1),
            nn.BatchNorm2d(8),
            nn.ReLU(),
            nn.Conv2d(8,2,1)
        )

四、实例分割分支

这部分的目的,是对每一个像素点,给出一个embedding,比如我设定embedding的维度是4,那么,这部分的输出,就应该是(B, 4, 360, 640)。

这里embedding的意思,就是使用一个4维向量,来表示每一个像素点。为什么要用embedding呢?

假如我不用embedding,输出就是(B, 360, 640),也就是说,每一张图,都有360*640个像素点,每一个像素点有不同的值,代表不同的颜色,比如0是黑色等等。

但是,这里的实例分割,是要区别出每条车道线的。我们虽然从图中一眼就看出来了,车道线那一部分的像素点,那一坨就是一根线啊,但是电脑看不出来,它只知道那些点是像素点,并且有一个像素值而已。

因此我们要使用不同的方式,来表示这个点,并且能通过计算距离,得到两个点之间的相似度。假如一个点的像素是200,另一个的像素是201,他们不一定属于同一条车道线。但假如第一个点的向量表示是[2,2,3,3,4],另一个点的向量表示是[2,2,3,3,4.1],那这俩就很相似了,就可以认为他们是属于同一条车道线了。

因此,我们对每一个点,都进行一个embedding,就可以计算每个点与每个点之间的相似度,进一步也可以计算一条线和另一条线的相似度。有了这样一种方法,就可以计算损失了。具体的损失计算,另一篇文章记录。

self.embedding = nn.Sequential(
            nn.Conv2d(8,8,1),
            nn.BatchNorm2d(8),
            nn.ReLU(),
            nn.Conv2d(8,self.embedding_dim,1)
        )

五、代码汇总

import torch
import torch.nn as nn
from resnet import resnet50
import torch.nn.functional as F

class LaneNet(nn.Module):
    def __init__(self, embed_dim):
        super(LaneNet, self).__init__()
        self.embedding_dim = embed_dim
        self.backbone = resnet50(pretrained=True)

        self.upsample8x = nn.Sequential(
            # 进行一次膨胀卷积,增加感受野,之后再一步步降下来,最后再上采样。
            nn.Conv2d(512,1024,3, padding=4,dilation=4,bias=False),
            nn.BatchNorm2d(1024),
            nn.ReLU(),
            nn.Conv2d(1024, 256, 3, padding=1, bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(256, 64, 3, padding=1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.Conv2d(64, 8, 3, padding=1, bias=False),
            nn.BatchNorm2d(8),
            nn.ReLU(),
            nn.Upsample(scale_factor=8, align_corners=True,mode='bilinear'))

        self.embedding = nn.Sequential(
            nn.Conv2d(8,8,1),
            nn.BatchNorm2d(8),
            nn.ReLU(),
            nn.Conv2d(8,self.embedding_dim,1)
        )
        self.seg = nn.Sequential(
            nn.Conv2d(8,8,1),
            nn.BatchNorm2d(8),
            nn.ReLU(),
            nn.Conv2d(8,2,1)
        )
        self.seg_loss = nn.CrossEntropyLoss()

    def forward(self,inputs):
        # 通过Backbone之后的输出是(B,512,45,80),是8倍降采样之后的结果。
        x = self.backbone(inputs)
        # 上采样这里是先膨胀卷积一次,后慢慢降下来通道数,再一个8倍双线性插值上采样。
        out = self.upsample8x(x)
        # 去实例分割的那条路,embedding成4维的(B,4,360,640)
        embedding = self.embedding(out)
        # 去语义分割的那条路,变成2通道的,一个预测背景,一个预测车道线(B,2,360,640)
        seg = self.seg(out)
        return [embedding, seg]