参考代码:conditional-lane-detection
介绍:这篇文章针对车道线问题提出一种使用Conditional-Conv完成车道线检测的方案,其灵感源自于SOLO-V1&V2与CondInst,将车道线不同实例的检测问题转换为了在求解其对应条件卷积所包涵的动态卷积参数问题,正是用这些对应的动态卷积参数去表示不同的车道线实例,这种思路与CondInst的构建原理最为相似。在这篇文章中除了用上述提到针对不同车道线实例的条件卷积之外,还通过row-wise车道线(点 )定位与对应offset预测的方式实现车道线(点)的确定和对应refinement工作 。针对车道线中存在弯曲与交叉的情况,文章还针对性只用Conv-LSTM网络(也就是文章中提到的RIM模块)去预测对应动态卷积参数,从而引入时序感知网络去增加对于特征图全局视角的感知,从而提升对于弯曲与交叉情况的适应能力。综合上述的改进点文章的方法就在一定程度上避免了segmentation-based、anchor-based、polygon-based方法中存在的一些局限问题。
下图展示了文章的方法在一些case下的适应能力,可以看到文章的方法在弯曲交叉的情况也能表现出一定程度的适应性。
文章的整体方法总结如下图所示:
在文章中为了增加网络的视觉感知能力增加了Transformer-Block到编码器中,然后在高层次的特征图上使用Proposal head生成用于车道线定位和偏移的动态卷积参数,最后在Conditional shape head中使用动态卷积参数实现对应车道线的预测。在上图中描述的是带有RIM模块的实现版本(对应代码为CondLaneRNNHead
),其实文章代码中还有一种直接在特征图上预测动态参数的实现版本(对应代码为CondLaneHead
)。
在编码器的最后一个stage上文章为了增加特征的感知能力串联了几个transformer模块,其使用到的transformer模块的结构见下图所示:
在编码器中有无Transformer-Block模块对于整体性能的影响,见下表所示:
这部分的工作是实现动态卷积参数的预测,用于后序模块完成车道线的预测,以及实现车道线起点heatmap的预测。完成动态卷积参数的预测在文章中给出了两种类型的实现方式:直接预测与Conv-LSTM增强感知。这两种方式在对应的场景上有所区别,一种是对应常规车道线,另外一种是对应存在扭曲交叉的情况。
PS:在对照文章代码的时候需要知道一些GT参数的具体含义用以帮助对代码的理解,可以参考这里的实现:
# mmdet/datasets/pipelines/lane_formating.py
PS:这部分车道线预测是采用CondInst的策略完成的,在动态参数下估计车道线车道线在feature-map上的位置和该位置相对于实际位置(输入图像维度下)的偏移量。这部分的整体网络实现可以参考下面的这个类:
# mmdet/models/detectors/condlanenet.py#L199
class CondLaneNet(SingleStageDetector):
...
对应的网络预测头为:
# mmdet/models/dense_heads/condlanenet_head.py#L174
class CondLaneHead(nn.Module):
...
对于车道线heatmap的预测问题,文章分析了两种情况:使用车道线的中点(类似于CondInst)与使用车道线的起点,经过分析车道线在起点位置处的特征更加具有表征性,因而选择的是预测车道线的起点。之后再通过row-wise策略实现车道线的完成预测,只通过实例分割形式得到车道线在弯曲细长情况下会表现不好。
这里对起点heatmap的预测是通过focal loss实现的(heatmap包含的像素其label为1),其损失函数表达为:
L p o i n t = − 1 N p ∑ x y { ( 1 − P ^ x y ) α l o g ( P ^ x y ) P x y = 1 ( 1 − P x y ) β ( P ^ x y ) α l o g ( 1 − P ^ x y ) otherwise L_{point}=\frac{-1}{N_p}\sum_{xy} \begin{cases} (1-\hat{P}_{xy})^\alpha log(\hat{P}_{xy}) & \text{$P_{xy}=1$} \\ (1-P_{xy})^\beta (\hat{P}_{xy})^\alpha log(1-\hat{P}_{xy}) & \text{otherwise} \end{cases} Lpoint=Np−1xy∑{(1−P^xy)αlog(P^xy)(1−Pxy)β(P^xy)αlog(1−P^xy)Pxy=1otherwise
heatmap与动态卷积参数的生成是通过下面的这个模块实现的:
# mmdet/models/dense_heads/ctnet_head.py#L29
class CtnetHead(nn.Module):
...
上述提到的车道线起点heat-map是直接在FPN输出特征图上使用上面提到的CtnetHead
模块完成对应的预测任务,之后得到heatmap和后序预测用的条件动态卷积参数,这部分代码可以参考:
# mmdet/models/dense_heads/condlanenet_head.py#L350
z = self.ctnet_head(f_hm) # 预测车道线的起点heatmap与动态参数
hm, params = z['hm'], z['params']
params = params.view(m_batchsize, self.num_classes, -1, h_hm, w_hm)
params = params.permute(0, 1, 3, 4, 2).contiguous().view(-1, self.num_gen_params)
# 得到车道线在特征图尺度下的像素定位
mask_params = params[:, :self.num_mask_params].gather(0, mask_pos_tensor)
masks = self.mask_head(mask_branch, mask_params, num_ins)
# 得到车道线在特征图尺度下的像素偏移
if self.regression:
reg_params = params[:, self.num_mask_params:].gather(0, reg_pos_tensor)
regs = self.reg_head(reg_branch, reg_params, num_ins)
除了上述提到的heat-map、车道线(点)位置、车道线(点)位置偏移(在2.4节做了具体说明),这里网络还通过MLP预测了车道线的高度,也就是:
# # mmdet/models/dense_heads/condlanenet_head.py#L381
feat_range = masks.permute(0, 1, 3,
2).view(sum(num_ins), w_mask, h_mask)
feat_range = self.mlp(feat_range)
因而在直接预测的方法中损失是包含了4个部分的,其组成为:
L t o t a l = L p o i n t + α L r o w + β L r a n g e + γ L o f f s e t L_{total}=L_{point}+\alpha L_{row}+\beta L_{range}+\gamma L_{offset} Ltotal=Lpoint+αLrow+βLrange+γLoffset
PS:对于带Conv-LSTM的增强感知是在已经通过动态卷积产生动态卷积参数的情况下,使用Conv-LSTM去优化生成的动态卷积参数,其生成的动态卷积参数的数量是与车道线的数量一致。对于这部分的整体网络实现可以参考:
# mmdet/models/detectors/condlanenet_rnn.py
class CurvelanesRnn(SingleStageDetector):
...
说回到为啥会使用Conv-LSTM去完成动态卷积参数的优化,在车道线存在交叉的情况下由于之前的方法在感知能力和范围上存在不足导致在该情况下表现不是很好,对此文章引入Conv-LSTM(文中将其称之为RIM模块)增强对车道线的感知能力,也就是下图图中所示的模块:
该模块每个 h i h_i hi的基础上使用一个全连接模块生成状态 s i s_i si和动态卷积参数 k i k_i ki,该生成状态的数量与车道线的数量相对应(适用于车道线大于1条的情况,对应标签生成处也有体现),这部分对应的代码实现可以参考:
# mmdet/models/dense_heads/condlanenet_head.py#L711
for _ in range(r_times):
rnn_out, hidden_state = self.rnn_ceil( # Conv-LSTM
inputs=rnn_feat_input,
hidden_state=hidden_state,
seq_len=1)
rnn_out = rnn_out.reshape(1, -1, 1, 1)
k_param, state = self.final_fc(rnn_out) # 得到stage和动态卷积参数
k_param = k_param.squeeze(-1).squeeze(-1)
对于状态部分的损失函数使用的是二值交叉熵的形式,其定义为:
L s t a t e = 1 N s ∑ i − [ y i ⋅ l o g ( s i ) + ( 1 − y i ) ⋅ l o g ( 1 − s i ) ] L_{state}=\frac{1}{N_s}\sum_i-[y_i\cdot log(s_i)+(1-y_i)\cdot log(1-s_i)] Lstate=Ns1i∑−[yi⋅log(si)+(1−yi)⋅log(1−si)]
因而,这里相比上述的直接车道先预测添加了一个state的损失:
L t o t a l = L p o i n t + α L r o w + β L r a n g e + γ L o f f s e t + η L s t a t e L_{total}=L_{point}+\alpha L_{row}+\beta L_{range}+\gamma L_{offset}+\eta L_{state} Ltotal=Lpoint+αLrow+βLrange+γLoffset+ηLstate
在这部分中完成的便是对于准确车道线的预测,需要注意的是这里车道线的预测都是基于实例级的,这也正对应文章标题中的Conditional的概念。因而下面的内容都是针对于一条车道线而言的。这部分车道线的预测包含了3个部分:车道线在特征图尺度上像素定位、车道线的高度、车道线在特征图尺度上与真实位置的偏移量。
对于一个输入的特征图(stride=8)可以将其在横纵两个方向进行划分,得到对应的X、Y轴,也就是下图中表示的样子。
车道线位置定位:
对于车道线位置确定这里是将其转换为概率分布问题,首先定义第 i i i行上车道线存在概率分布描述为:
p i = s o f t m a x ( f l o c i ) p_i=softmax(f_{loc}^i) pi=softmax(floci)
那么当前行上所需要预测的车道线的像素位置被描述为离散积分的形式:
E ( x ^ i ) = ∑ j j ⋅ p i , j E(\hat{x}_i)=\sum_jj\cdot p_{i,j} E(x^i)=j∑j⋅pi,j
则对应的损失函数描述为:
L r o w = 1 N v ∑ i ∈ V ∣ E ( x ^ i ) − x i ∣ L_{row}=\frac{1}{N_v}\sum_{i\in V}|E(\hat{x}_i)-x_i| Lrow=Nv1i∈V∑∣E(x^i)−xi∣
车道线位置偏移:
在拿到特征图维度的车道线定位之后接下来便需要对车道线的具体位置进行确定,这里采用的是相对真实位置偏移的形式,其损失函数描述为:
L o f f s e t = 1 N Ω ∑ j , i ∈ Ω ∣ δ ^ i , j − δ i , j ∣ L_{offset}=\frac{1}{N_\Omega}\sum_{{j,i}\in\Omega}|\hat{\delta}_{i,j}-\delta_{i,j}| Loffset=NΩ1j,i∈Ω∑∣δ^i,j−δi,j∣
最后整体车道线的预测表达描述为:
{ y k i = H Y ⋅ i x k i = W X ⋅ ( l o c k i + δ ( l o c k i , i ) ) \begin{cases} y_k^i=\frac{H}{Y}\cdot i & \text{} \\[2ex] x_k^i=\frac{W}{X}\cdot (loc_k^i+\delta(loc_k^i,i))& \text{} \end{cases} ⎩⎨⎧yki=YH⋅ixki=XW⋅(locki+δ(locki,i))
其中, i ∈ [ v m i n , v m a x ] i\in[v_{min},v_{max}] i∈[vmin,vmax]代表可取的高度范围, l o c k i loc_k^i locki代表位置积分 E ( x ^ i ) E(\hat{x}_i) E(x^i)的下取整结果, δ ( ⋅ ) \delta(\cdot) δ(⋅)代表位置偏移。
车道线高度确定:
对于车道线的高度的预测文章中是通过在H维度做二值分类的形式实现的,也就是对应上面图4中的Vertical Range部分。对应到代码里面就是使用一个一维去实现:
# mmdet/models/dense_heads/condlanenet_head.py#L383
feat_range = self.mlp(feat_range) # 车道线范围预测 nn.Conv1d(n, k, 1)
之后便是使用二值交叉熵损失函数实现监督:
L r a n g e = ∑ i ( − y g t i l o g ( v i ) − ( 1 − y g t i ) l o g ( 1 − v i ) ) L_{range}=\sum_i(-y_{gt}^ilog(v_i)-(1-y_{gt}^i)log(1-v_i)) Lrange=i∑(−ygtilog(vi)−(1−ygti)log(1−vi))
对于带动态参数RNN优化的版本:
L t o t a l = L p o i n t + α L r o w + β L r a n g e + γ L o f f s e t + η L s t a t e L_{total}=L_{point}+\alpha L_{row}+\beta L_{range}+\gamma L_{offset}+\eta L_{state} Ltotal=Lpoint+αLrow+βLrange+γLoffset+ηLstate
对于使用直接预测的版本:
L t o t a l = L p o i n t + α L r o w + β L r a n g e + γ L o f f s e t L_{total}=L_{point}+\alpha L_{row}+\beta L_{range}+\gamma L_{offset} Ltotal=Lpoint+αLrow+βLrange+γLoffset