无人驾驶车道线检测论文主要思路大集合

首先需要明确这里研究的场景是,被遮挡的车道线也要能检测到,否则难度会大大降低;

Ultra Fast Structure-aware Deep Lane Detection

思路上加入了先验知识,感觉特别好,详见博文https://zhuanlan.zhihu.com/p/157530787
号称性能跟SOTA差不多,速度能达到300+FPS.
大致看了一下其开源的代码

class parsingNet(torch.nn.Module):
    def __init__(self, size=(288, 800), pretrained=True, backbone='50', cls_dim=(37, 10, 4), use_aux=False):
        super(parsingNet, self).__init__()

        self.size = size
        self.w = size[0]
        self.h = size[1]
        #这里的cls_dim=(101,56,4)
        #其中101代表一行有100个grid,预测哪个grid有车道线,如果该行没有,则增加一个背景类,该行的累加值为1,只负责预测一个车道线
        #56代表row_anchors的分类,也就相当于对高、的划分
        #4代表4条车道线,从左到右
        self.cls_dim = cls_dim # (num_gridding, num_cls_per_lane, num_of_lanes)
        # num_cls_per_lane is the number of row anchors
        self.use_aux = use_aux
        #这里的total_dim=101*56*4=22624
        self.total_dim = np.prod(cls_dim)

        # input : nchw,
        # output: (w+1) * sample_rows * 4 
        self.model = resnet(backbone, pretrained=pretrained)

        if self.use_aux:
            self.aux_header2 = torch.nn.Sequential(
                conv_bn_relu(128, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(512, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            self.aux_header3 = torch.nn.Sequential(
                conv_bn_relu(256, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(1024, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            self.aux_header4 = torch.nn.Sequential(
                conv_bn_relu(512, 128, kernel_size=3, stride=1, padding=1) if backbone in ['34','18'] else conv_bn_relu(2048, 128, kernel_size=3, stride=1, padding=1),
                conv_bn_relu(128,128,3,padding=1),
            )
            self.aux_combine = torch.nn.Sequential(
                conv_bn_relu(384, 256, 3,padding=2,dilation=2),
                conv_bn_relu(256, 128, 3,padding=2,dilation=2),
                conv_bn_relu(128, 128, 3,padding=2,dilation=2),
                conv_bn_relu(128, 128, 3,padding=4,dilation=4),
                torch.nn.Conv2d(128, cls_dim[-1] + 1,1)
                # output : n, num_of_lanes+1, h, w
            )
            initialize_weights(self.aux_header2,self.aux_header3,self.aux_header4,self.aux_combine)

        self.cls = torch.nn.Sequential(
            torch.nn.Linear(1800, 2048),
            torch.nn.ReLU(),
            torch.nn.Linear(2048, self.total_dim),
        )

        self.pool = torch.nn.Conv2d(512,8,1) if backbone in ['34','18'] else torch.nn.Conv2d(2048,8,1)
        # 1/32,2048 channel
        # 288,800 -> 9,40,2048
        # (w+1) * sample_rows * 4
        # 37 * 10 * 4
        initialize_weights(self.cls)

    def forward(self, x):
        # n c h w - > n 2048 sh sw
        # -> n 2048
        #这里的aux_seg是没有经过Linear的特征图
        #所以相当于普通的逐像素的分割
        x2,x3,fea = self.model(x)
        #其中use_aux=False
        if self.use_aux:
            x2 = self.aux_header2(x2)
            x3 = self.aux_header3(x3)
            x3 = torch.nn.functional.interpolate(x3,scale_factor = 2,mode='bilinear')
            x4 = self.aux_header4(fea)
            x4 = torch.nn.functional.interpolate(x4,scale_factor = 4,mode='bilinear')
            aux_seg = torch.cat([x2,x3,x4],dim=1)
            aux_seg = self.aux_combine(aux_seg)
        else:
            aux_seg = None
        #这里的pool是降维的1*1卷积
        fea = self.pool(fea).view(-1, 1800)
        #然后再预测到cls_dim的维度
        group_cls = self.cls(fea).view(-1, *self.cls_dim)

        if self.use_aux:
            return group_cls, aux_seg

        return group_cls

由上面的代码可以得出输出的每一个分类结果都是由全图的特征得到的
接下来看一下后处理的过程:

    for split, dataset in zip(splits, datasets):
        loader = torch.utils.data.DataLoader(dataset, batch_size=1, shuffle = False, num_workers=1)
        fourcc = cv2.VideoWriter_fourcc(*'MJPG')
        print(split[:-3]+'avi')
        vout = cv2.VideoWriter(split[:-3]+'avi', fourcc , 30.0, (img_w, img_h))
        for i, data in enumerate(tqdm.tqdm(loader)):
            imgs, names = data
            imgs = imgs.cuda()
            #imgs.size() torch.Size([1, 3, 288, 800])
            with torch.no_grad():
                out = net(imgs)
            #网格数量为100
            #这样就相当于以8为间隔,一共100个数[0 8 16 .....]
            col_sample = np.linspace(0, 800 - 1, cfg.griding_num)
            print("col_sample", col_sample)
            #这个间隔宽度就是8
            col_sample_w = col_sample[1] - col_sample[0]
            print("col_sample_w)", col_sample_w)
            #将网络输出的第一个单独拎出来,也就是group_cls
            out_j = out[0].data.cpu().numpy()
            print("out_j)", out_j)
            out_j = out_j[:, ::-1, :]
            print("out_j)", out_j)
            #经过softmax之后得到的是置信度
            prob = scipy.special.softmax(out_j[:-1, :, :], axis=0)
            print("prob)", prob)
            #建立0~100 100个索引
            idx = np.arange(cfg.griding_num) + 1
            print("idx)", idx)
            #reshape成纵向方便后面矩阵相乘
            idx = idx.reshape(-1, 1, 1)
            print("idx)", idx)
            #关于置信度和索引相乘获得位置的解释
            #作者的解释是这样的
            #假设我们有两个位置[39,40],并且的概率[39,40]为[0.2,0.8]。
            # 如果使用argmax,我们将得到的位置,40因为的概率40为0.8。
            # 但是我们还可以获得一个值:39*0.2 + 40*0.8 = 39.8,
            # 它比更加精确argmax。这是位置的数学期望。这就是为什么我们使用它来获取位置。
            #loc.shape=(100,4)
            loc = np.sum(prob * idx, axis=0)
            print("loc)", loc)
            out_j = np.argmax(out_j, axis=0)
            print("out_j)", out_j)
            loc[out_j == cfg.griding_num] = 0
            print("out_j)", out_j)
            out_j = loc
            print("out_j)", out_j)
            # import pdb; pdb.set_trace()
            vis = cv2.imread(os.path.join(cfg.data_root,names[0]))
            for i in range(out_j.shape[1]):
                if np.sum(out_j[:, i] != 0) > 2:
                    for k in range(out_j.shape[0]):
                        if out_j[k, i] > 0:
                            ppp = (int(out_j[k, i] * col_sample_w * img_w / 800) - 1, int(img_h * (row_anchor[cls_num_per_lane-1-k]/288)) - 1 )
                            cv2.circle(vis,ppp,5,(0,255,0),-1)
            vout.write(vis)

关于这个row_anchors是由数据集本身的label决定的
然后loss的话呢是由两部分组成:
在这里插入图片描述

# LOSS
sim_loss_w = 1.0
shp_loss_w = 0.0

Towards End-to-End Lane Detection: an Instance Segmentation Approach

大多数传统车道线检测是基于手工特征,再加上形状上的拟合(比如Ransac)。
另一条思路是将车道检测问题转为多类别分割问题,每条车道属于一类,这样能实现端到端训练出分类号的二进制图。但该方法受限于只能检测预先定义的,固定数量的车道线。
许多基于分类的网络实现方式需要实现固定好要检测车道线的数量,这也是一大缺点。
然而产生的二值化车道线分割图仍需要分离到不同的车道实例中。
论文思路:受语义分割和实例分割任务中dense预测的启发,将车道检测问题转为实例分割问题,每个车道线形成独立的实例,但都属于车道线这个类别。我们设计了一个带分支结构的多任务网络,如车道实例分割,由一个车道分割分支和一个车道embedding分支构成能够实现端到端训练。车道分割分支输出两类:背景或车道线;车道embedding分支进一步将分割后得到的车道线分离成不同车道实例。
当得到车道实例(即知道哪些像素属于哪条车道)后,就需要对每条线做参数描述。曲线拟合算法作为这个参数描述,流行的拟合模型有三次多项式,样条曲线,回旋曲线。为了提高拟合质量且保持计算效率,通常将图像转到鸟瞰图后做拟合。最后再逆变换到原图即可。但有个问题是:这个透视变换矩阵会受地面线影响(如上坡)。本文在做曲线拟合前先训练一个网络用于生成透视变换矩阵系数以解决道路平面变动的影响。论文模型的主框架如下图:
在这里插入图片描述
1.二值化分割
上图中的下面那个分支就是用于训练输出得到一个二值化的分割图,白色代表车道线,黑色代表背景。为了构建GT分割图,我们将每天车道线的对应像素连成线,这么做的好处是即使车道线被遮挡了,网络仍能预测车道位置。给分割网络用标准的交叉熵损失函数做训练的。由于目标类别是2类(车道/背景)高度不平衡(我的理解就是背景较车道线的像素多太多),我们应用 bounded inverse class weighting。
2.实例分割
当分割分支识别得到车道后,为了分离车道像素(就是为了知道哪些像素归这条,哪些归那条车道),我们训练训练了一个车道instance embedding分支网络(上图上面那个网络)。我们用基于one-shot的方法做距离度量学习(参考论文:[5] B. De Brabandere, D. Neven, L. Van Gool. CoRR abs/1708.02551, 2017.),该方法易于集成在标准的前馈神经网络中,可用于实时处理。利用聚类损失函数,instance embedding分支训练后输出一个车道线像素点距离,归属同一车道的像素点距离近,反之远,基于这个策略,可聚类得到各条车道线。
就是说有两股力在做较量,一股是方差项,主要是将每个embedding往某条车道线的均值方向拉,另一股是距离项,就是使两个类别的车道线越远越好(激活推这个动作的前提是两条车道线聚类中心的距离太近啦,近于阈值δd就push)。具体的损失函数如下:
在这里插入图片描述
3.用H-NET做车道线曲线拟合
lanenet网络输出的是每条车道线的像素集合。常规处理是将图像转为鸟瞰图,这么做的目的就是为了做曲线拟合时弯曲的车道能用2次或3次多项式拟合(拟合起来简单些)。关于这个变换矩阵的问题上面已经描述过了,意思就是通过H-Net网络学习得到的变换矩阵参数适用性更好(可看下图)
使用固定的单应性矩阵是不OK的,因为场景复杂多变(比如上坡这种),所以要去学习一个Homography变换的变量。
在这里插入图片描述
作者最后的结论就是用H-Net比用固定的转换矩阵要好,用3阶多项式拟合要比2阶好,用了透视变换比不用好~总之当前模型框架要出色。

Geometric Constrained Joint Lane Segmentation and Lane Boundary Detection

由人类感知而言,车道区域和车道边界是不可分离的。然而,现有的车道检测方法主要依靠单任务网络来独立训练车道分割和车道边界检测,完全忽略了两个任务之间固有的几何约束。本文却将两个任务相互制约了起来。
本文的主要创新点在于把车道边缘检测和车道区域检测放在一个任务里执行,即二者之间具有明确的几何联系,因此设计网络的时候如果能够把这种几何关系考虑进去,理论上就会有精度上的提升。本文的主要工作也是基于这种思想实现的。
大致实现的流程是,先通过一个共用的编码器提取特征,然后车道线检测和车道检测各对应一个解码器,分别标记车道线和车道对应的像素,在此之外,两个解码器的输出会各自再次通过一个编码器输入到对方的解码器中,形成一种交叉结构,这种交叉就是让上面提到的几何约束起作用的方法。
在这里插入图片描述
为了分别在单个任务学习框架中通过车道分割和车道边界检测的编码器来阐明激活区域,我们使用热图来可视化每个激活图。如图所示,通过车道分割强调具有相似纹理的车道,这引起了车道检测的模糊问题。此外,一些背景区域被激活。 相反,背景中的边缘引起车道边界检测的更严重的异常值问题。提出了一种共享编码器,以大大减少模糊问题和异常值,因为在共享编码器的训练期间已经强调了对两个任务的性能改进至关重要的特征,故可以获得更清晰的车道提取。
在这里插入图片描述

Real time Detection of Lane Markers in Urban Streets

这一篇论文相对久远,是2014年,用的方法可以算是传统方法。
车道线检测最早是用传统方法来实现的。
传统车道线检测有其固定的一套处理流程:(1)Distortion Correction(2)IPM Transform(3)Feature Extraction(4)Line or Curve Fitting(5)Tracking,除了(3)(4)之外,其他都不是必须的。
Distortion Correction
一般镜头都会带有畸变,主要包含径向畸变与切向畸变两种,做车道线检测之前最好能做一下畸变校正,一方面更有利于场景检测,另一方面如果涉及图像与相机坐标映射的计算也能提供更精确的转换。当然一般除了广角、鱼眼之类的镜头外,此类误差基本可以忽略。
IPM Transform
IPM Transform有两个优势,一个是相当于down sample能降低计算量(必然也会损失一些feature信息),另一方面车道线在bird eye view中基本都接近于平行直线,通过线的形态限制能更好地完成直线提取。
IPM Transform有两种转换方式,一种是利用四点法计算逆透视矩阵,另一种是该paper中作者使用的通过相机内参与外参直接获得坐标映射矩阵的方法,该坐标转换方法还可用于测距运算。
Line or Curve Fitting
直线拟合通常都采用文中hough变换,结合group lines和RANSAC的方法。当然你也可以去尝试用lsd等其他直线检测方法替换hough变换,不过意义不大。RANSAC中score计算方式文中采用sample points到直线的距离来衡量。
Tracking
传统lane detection的检测结果不是很稳定,虽然paper中没有提到tracking,但做tracking还是很有必要的,一般用Kalman Filter就够了,关键是跟踪哪几个参数、观测值怎么建模需要因人而异地去设计。

A Vision-Based Lane Detection System Combining Appearance Segmentation and Tracking of Salient Points

基于外观的分割和消失点的追踪,这一篇也是使用传统算法完成的:
在这里插入图片描述

VPGNet: Vanishing Point Guided Network for Lane and Road Marking Detection and Recognition

这一篇文章主要是针对下雨天,地面湿漉漉的,可能会反光,拍摄的图片不清晰,公开的数据集没有包括这一类图片,现有的算法也不能很好滴处理这个问题,作者只做了针对这种情况的数据集(包含下雨,不下雨,大雨,晚上四种情况),并且开发了多任务的VPGNet,并通过单个前向通道预测消失点。速度可达20fps。
在这里插入图片描述
网络有四个任务模块,每个任务执行互补合作:网格框回归,对象检测,多标签分类和消失点预测。 这种结构允许我们检测并分类车道和道路标记,并在单个前向通道中同时预测消失区
作者说明制作数据集的流程,在韩国的某处采集那四种情况的图片,车道线区域人工标注,用像素点连成多边形表示,因为远处的车道线像素太细,一般网络喂入图片都需要压缩,所以远处的细车道线可能就不可见,这时候用grid来标注vanish point,考虑到输入输出图片之比是8:1,grid的大小选取为8*8,由于消失点代表车道线延伸的交点,对于平行的车道线来说(比如路口),是没有交点的,也就不存在消失点。多任务在于,不仅标注了车道线,对于道路上的17中标志,也进行了标注。具体路标分类,如下图所示:
在这里插入图片描述
一般的box回归只是用于blob shape的物体,对于车道线来说并不适用,所以我们采用一个替代的方案,使用grid_level来进行回归。
有时候下雨天道路不清晰的时候,人们可以根据周围环境进行判断,设置消失点的检测也是为了符合人类的视觉特性。消失点指的是从图像视角三条三维空间的平行的车道线在相交于一个二维平面上。VP可以提供一个全局的场景几何信息,这对于定位车道线来说非常重要。

以下两篇论文是2020最新的论文

End-to-End Lane Marker Detection via Row-wise Classification(CVPR 2020)

这篇文章将车道线检测转换成检测车道线在每行上的位置,也就是将图像按行划分,

N是固定的要检测的车道线的数目,K是用多少点表示车道线,跟图片高度有关
这篇文章采用的实例分割的方式,先进行一个用一个encoder-decoder的网络进行二分类的分割,以下是网络结构:
在这里插入图片描述
整体步骤分为三个步骤:
1.第一步是编码解码的分割过程,获得图像的语义信息,输出的图片大小是输入图片大小的1/2;
2.在第二部当中,我们不改变纵向的维度,用HRMs网络仅仅压缩横向维度的表示。用这个压缩操作,我们可以更自然的观察到以行为单位的表示,每一行用一个vector表示,为了加速计算,HRMs中的某些层跟分割网络中的某些层是共享的;
3.最后一步,我们有两个分支,一个是为了以行为单位的车道线在竖直方向上的位置;一个是以列为单位的置信度。分类和置信度回归是放在HRMs后面,虽然只有竖直方向维度,但是水平方向的维度是放在通道上(channel)。
(这里注意:第一步的输出是第二步的输入)

Lane detection using lane boundary marker network with road geometry constraints(WACV2020)

首先,用一个网络检测车道线边缘上的关键点,估测一个反向透视映射结合车道线的几何限制(等距和并行)进行车道线拟合。
以下是用到的编码解码的骨干网络,

以下是算法的全流程:
在这里插入图片描述
本文用固定数量的关键点表示车道线边界,水平方向准确的估计(对于IPM来说很重要)有赖于消失点的定位

EL-GAN: Embedding Loss Driven Generative Adversarial Networks for Lane Detection(2018 CVPR)

有很多问题,并不是像素级分类的问题被用分割处理,而且需要复杂的后处理过程。EL-GAN可以去除复杂的后处理。
其方法就是加入了一个emmbedding loss,生成器用于生成跟label比较像的output,判别器用于判别fake和real.
由下图可以看出,EL-GAN的输出更符合数据集的label.
对于判别器,作者最先考虑的是由label生成的二进制map跟output生成的进行对比,但发现这样训练无法提供稳定和充足的梯度。
接下来重点看看loss函数的设计:
在这里插入图片描述
在这里插入图片描述
x,y分别是输入的图像和对应的label,gen代表generator网络的参数,disc代表discriminator网络的参数,G代表generators网络的输出,Lfit代表像素的分类交叉熵损失,可以用Lcce表示,关于Lcce看下面的公式:
在这里插入图片描述
其中c代表分类的数目,w,h分别代表图像的宽和长。

判别器损失Ladv代表了判别器认出real和fake的能力,这里的判别器损失就是BCEloss,具体看下图:
在这里插入图片描述

然而,生成器的目的是要最小化对抗loss,判别器的目的是最大化它,
在这里插入图片描述
在训练过程当中,判别器学习到了标签和预测分布之间的差别,生成器尝试优化生成的结果跟标签相似,训练对抗神经网络和一般的网络相比更加不稳定。
接下来说判别器的loss,也就是对抗网络的loss
在这里插入图片描述
总之,这里的整理思想就是生成器努力生成跟label相近,判别器要尽量能判别出fake和real.

CurveLane-NAS: Unifying Lane-Sensitive Architecture Search and Adaptive Point Blending

这篇文章目前是culane数据集公开赛的第一名。(因为作者只在Culane数据集上进行了网络搜索)
首先介绍以下什么是Nas?
深度学习可以自动学习出有用的特征,脱离了对特征工程的依赖,在图像、语音等任务上取得了超越其他算法的结果。这种成功很大程度上得益于新神经网络结构的出现,如ResNet、Inception、DenseNet等。但设计出高性能的神经网络需要大量的专业知识与反复试验,成本极高,限制了神经网络在很多问题上的应用。神经结构搜索(Neural Architecture Search,简称NAS)是一种自动设计神经网络的技术,可以通过算法根据样本集自动设计出高性能的网络结构,在某些任务上甚至可以媲美人类专家的水准,甚至发现某些人类之前未曾提出的网络结构,这可以有效的降低神经网络的使用和实现成本。
NAS的原理是给定一个称为搜索空间的候选神经网络结构集合,用某种策略从中搜索出最优网络结构。神经网络结构的优劣即性能用某些指标如精度、速度来度量,称为性能评估。这一过程如下图所示。
在这里插入图片描述

具体可以看链接:
https://zhuanlan.zhihu.com/p/60414004

我们解决了造成的弯道检测问题比传统的车道检测更现实的挑战,以更好地促进现代辅助/自动驾驶系统的发展。当前手工设计的车道检测方法不够强大,无法捕获弯道,尤其是偏远地区,因为缺乏建模远程上下文信息和详细的曲线轨迹。在这个
论文中,我们提出了一种新颖的车道敏感架构搜索框架名为CurveLane-NAS的软件可自动捕获远程相干和精确的短程曲线信息。它由三个部分组成搜索模块:a)特征融合搜索模块,以找到更好的融合多层次层次结构特征的本地和全局上下文; b)一个弹性骨干搜索模块以探索有效的特征提取器具有良好的语义和延迟; c)自适应点融合模块搜索多级后处理细化策略以结合多尺度头部预测。此外,我们还希望发布一个更具挑战性的基准,称为CurveLanes,以解决最困难的弯道。它由带有680K标签的15万张图像组成。3在新的CurveLanes上进行的实验表明,SOTA通道检测方法的性能大幅下降,而我们的模型仍然可以达到80%以上的F1得分。对传统的广泛实验CULane等车道基准也证明了我们的优势CurveLane-NAS,例如在CULane上获得了新的SOTA 74.8%F1得分。
在这里插入图片描述
当然,使用循环神经网络的也大有人在:

Lane Position Detection Based on Long Short-Term Memory (LSTM)2019CVPR

源码地址:https://github.com/qinnzou/Robust-Lane-Detection
这是CNN+RNN的例子,不过是多帧之间的RNN。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_39326879/article/details/108874914