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

代码:https://github.com/MaybeShewill-CV/lanenet-lane-detection

参考:车道线检测算法LaneNet + H-Net(论文解读)

数据集:Tusimple

Overview

本文提出一种端到端的车道线检测算法,包含 LanNet + H-Net 两个网络模型。其中 LanNet 是一种将语义分割和对像素进行向量表示结合起来的多任务模型,最后利用聚类完成对车道线的实例分割。H-Net 是有个小的网络结构,负责预测变换矩阵 H,使用转换矩阵 H 对同属一条车道线的所有像素点进行重新建模(使用 y 坐标来表示 x 坐标)。

LaneNet

论文中将实例分割任务拆解成语义分割(LanNet 一个分支)和聚类(LanNet一个分支提取 embedding express, Mean-Shift 聚类)两部分。如上图所示,LanNet 有两个分支任务,分别为 a lane segmentation branch and a lane embedding branch。Segmentation branch 负责对输入图像进行语义分割(对像素进行二分类,判断像素属于车道线还是背景);Embedding branch 对像素进行嵌入式表示,训练得到的 embedding 向量用于聚类。最后将两个分支的结果进行结合利用 Mean-Shift 算法进行聚类,得到实例分割的结果。

语义分割

在设计语义分割模型时,论文主要考虑了以下两个方面:

1.在构建 label 时,为了处理遮挡问题,论文对被车辆遮挡的车道线和虚线进行了还原(估计);

2. Loss 使用 softmax_cross_entropy,为了解决样本分布不均衡的问题(属于车道线的像素远少于属于背景的像素),参考论文ENet: A Deep Neural Network Architecture for Real-Time Semantic Segmentation ,使用了 bounded inverse class weight 对 loss 进行加权:

$W_{class} = \frac{1}{ln(c\ +\ p(class))}$

其中,p 为对应类别在总体样本中出现的概率,c 是超参数(ENet论文中是1.02)。

with tf.variable_scope(name_or_scope=name, reuse=reuse):
# calculate class weighted binary seg loss
with tf.variable_scope(name_or_scope='binary_seg'):
binary_label_onehot = tf.one_hot(
tf.reshape(
tf.cast(binary_label, tf.int32),
shape=[binary_label.get_shape().as_list()[0],
binary_label.get_shape().as_list()[1],
binary_label.get_shape().as_list()[2]]),
depth=CFG.TRAIN.CLASSES_NUMS,
axis=-1
) # 0/1 矩阵, 256x512x2 binary_label_plain = tf.reshape(
binary_label,
shape=[binary_label.get_shape().as_list()[0] *
binary_label.get_shape().as_list()[1] *
binary_label.get_shape().as_list()[2] *
binary_label.get_shape().as_list()[3]])
unique_labels, unique_id, counts = tf.unique_with_counts(binary_label_plain)
counts = tf.cast(counts, tf.float32) # 每个类别的像素数量
inverse_weights = tf.divide(
1.0,
tf.log(tf.add(tf.divide(counts, tf.reduce_sum(counts)), tf.constant(1.02)))
) # 1/log(counts/all_counts + 1.02) binary_segmentation_loss = self._compute_class_weighted_cross_entropy_loss(
onehot_labels=binary_label_onehot,
logits=binary_seg_logits,
classes_weights=inverse_weights
) def _compute_class_weighted_cross_entropy_loss(cls, onehot_labels, logits, classes_weights):
""" :param onehot_labels:
:param logits:
:param classes_weights:
:return:
"""
loss_weights = tf.reduce_sum(tf.multiply(onehot_labels, classes_weights), axis=3) loss = tf.losses.softmax_cross_entropy(
onehot_labels=onehot_labels,
logits=logits,
weights=loss_weights
) return loss

像素 embedding

为了区分车道线上的像素属于哪条车道,embedding_branch 为每个像素初始化一个 embedding 向量,并且在设计 loss 时,使属同一条车道线的像素向量距离尽可能小,属不同车道线的像素向量距离尽可能大。

这部分的 loss 函数是由三部分组成:方差 loss($L_{var}$) 和距离 loss($L_{dist}$):

$L_{var} = \frac{1}{C} \sum_{c=1}^{C} \frac{1}{N_c} [\Arrowvert \mu_c-x_i \Arrowvert -\delta_v]_{+}^{2}$

$L_{dist} =  \frac{1}{C(C-1)} \sum_{c_{A=1}}^{C} \sum_{c_{B=1}, c_A \ne C_B}^{C} [\delta_d - \Arrowvert \mu_{c_A} -  \mu_{c_B} \Arrowvert ]_{+}^{2}$

$L_{reg} =  \frac{1}{C} \sum_{c=1}^{C}  \Arrowvert \mu_c \Arrowvert $

其中,C 是车道线数量,$N_c$ 是属同一条车道线的像素点数量,$\mu_c$ 是车道线的均值向量,$x_i$ 是像素向量(pixel embedding),$[x]_+ = max(0, x)$。注意这里先执行 $_+$ 操作,再执行 $^2$ 操作。

该 loss 函数源自于论文 《Semantic Instance Segmentation with a Discriminative loss function》

同一车道线的像素向量,距离车道线均值向量 $\mu_c$ 超过 $\delta_v$ 时, pull force($L_{var}$) 才有意义,使得 $x_i$ 靠近 $\delta_d$;

不同车道线的均值向量 $\mu_{c_A}$ 和 $\mu_{c_B}$ 之间距离小于 $\delta_d$ 时,push force($L_{dist}$) 才有意义,使得 $\mu_{c_A}$ 和 $\mu_{c_B}$ 彼此远离。

def discriminative_loss_single(
prediction,
correct_label,
feature_dim,
label_shape,
delta_v,
delta_d,
param_var,
param_dist,
param_reg):
"""
论文equ(1)提到的实例分割损失函数
:param prediction: inference of network
:param correct_label: instance label
:param feature_dim: feature dimension of prediction
:param label_shape: shape of label
:param delta_v: cut off variance distance
:param delta_d: cut off cluster distance
:param param_var: weight for intra cluster variance
:param param_dist: weight for inter cluster distances
:param param_reg: weight regularization
"""
# 像素对齐为一行
correct_label = tf.reshape(
correct_label, [label_shape[1] * label_shape[0]]
)
reshaped_pred = tf.reshape(
prediction, [label_shape[1] * label_shape[0], feature_dim]
)
# 统计实例个数
# unique_labels统计出correct_label中一共有几种数值,unique_id为correct_label中的每个数值是属于unique_labels中第几类
# counts统计unique_labels中每个数值在correct_label中出现了几次
unique_labels, unique_id, counts = tf.unique_with_counts(correct_label)
counts = tf.cast(counts, tf.float32) # 每个实例占用像素点量
num_instances = tf.size(unique_labels) # 实例数量 # 计算pixel embedding均值向量
# segmented_sum是把reshaped_pred中对于GT里每个部分位置上的像素点相加
# 比如unique_id[0, 0, 1, 1, 0],reshaped_pred[1, 2, 3, 4, 5],最后等于[1+2+5,3+4],channel层不相加
segmented_sum = tf.unsorted_segment_sum(
reshaped_pred, unique_id, num_instances) # [num_instances, feature_dim]
# 除以每个类别的像素在gt中出现的次数,是每个类别像素的均值 (?, feature_dim)
mu = tf.div(segmented_sum, tf.reshape(counts, (-1, 1))) # [num_instances, feature_dim]
# 然后再还原为原图的形式,现在mu_expand中的数据是与correct_label的分布一致,但是数值不一样
mu_expand = tf.gather(mu, unique_id) # 特征均值矩阵 # 计算公式的loss(var)
# 对channel维度求范数-[131072,]
distance = tf.norm(tf.subtract(mu_expand, reshaped_pred), axis=1) # gb& ||mu_c - x_i||
distance = tf.subtract(distance, delta_v) # bg& ||mu_c - x_i|| - delta_v
# 小于0的设置为0,大于distance的设置为distance
distance = tf.clip_by_value(distance, 0., distance) # bg& (||mu_c - x_i|| - delta_v)_+
distance = tf.square(distance) # bg& (||mu_c - x_i|| - delta_v)_+^2 l_var = tf.unsorted_segment_sum(distance, unique_id, num_instances) # (||mu_c - x_i|| - delta_v)_+
l_var = tf.div(l_var, counts) # (||mu_c - x_i|| - delta_v)_+^2 / N_c
l_var = tf.reduce_sum(l_var) # sumC{sum[(||mu_c - x_i|| - delta_v)_+^2 / N_c]}
# sumC{sum[(||mu_c - x_i|| - delta_v)_+^2 / N_c]} / [C * (C - 1)]
l_var = tf.divide(l_var, tf.cast(num_instances * (num_instances - 1), tf.float32)) # 计算公式的loss(dist)
mu_interleaved_rep = tf.tile(mu, [num_instances, 1]) # 0 轴方向上复制 num_instances 次
mu_band_rep = tf.tile(mu, [1, num_instances]) # 1 轴方向上复制 num_instances 次
mu_band_rep = tf.reshape(mu_band_rep, (num_instances * num_instances, feature_dim)) mu_diff = tf.subtract(mu_band_rep, mu_interleaved_rep) # mu_ca - mu_cb # 去除掩模上的零点 ca != cb
intermediate_tensor = tf.reduce_sum(tf.abs(mu_diff), axis=1)
zero_vector = tf.zeros(1, dtype=tf.float32)
bool_mask = tf.not_equal(intermediate_tensor, zero_vector)
mu_diff_bool = tf.boolean_mask(mu_diff, bool_mask) mu_norm = tf.norm(mu_diff_bool, axis=1)
mu_norm = tf.subtract(2. * delta_d, mu_norm) # (2*delta_d - ||mu_ca - mu_cb||)
mu_norm = tf.clip_by_value(mu_norm, 0., mu_norm) # (2*delta_d - ||mu_ca - mu_cb||)_+
mu_norm = tf.square(mu_norm) # (2*delta_d - ||mu_ca - mu_cb||)_+^2 l_dist = tf.reduce_mean(mu_norm) # sum[2*delta_d - ||mu_ca - mu_cb||)_+^2] / C # 计算原始Discriminative Loss论文中提到的正则项损失
l_reg = tf.reduce_mean(tf.norm(mu, axis=1)) # mean[sqrt(sum(mu_c^2))] # 合并损失按照原始Discriminative Loss论文中提到的参数合并
l_var = param_var * l_var
l_dist = param_dist * l_dist
l_reg = param_reg * l_reg loss = l_var + l_dist + l_reg # loss = l_var + l_dist + 0.001*mean[sqrt(sum(mu_c^2))] return loss, l_var, l_dist, l_reg

聚类

注意,聚类可以看做是个后处理,上一步里 embedding_branch 已经为聚类提供好的特征向量了,利用这些特征向量我们可以利用任意聚类算法来完成实例分割的目标。

为了方便聚类,论文中设定 $\delta_d > 6\delta_v$。

在进行聚类时,首先使用 mean shift聚类,使得簇中心沿着密度上升的方向移动,防止将离群点选入相同的簇中;之后对像素向量进行划分:以簇中心为圆心,以 $2\delta_v$ 为半径,选取圆中所有的像素归为同一车道线。重复该步骤,直到将所有的车道线像素分配给对应的车道。

网络结构

LaneNet是基于ENet 的encoder-decoder模型,如图5所示,ENet由5个stage组成,其中stage2和stage3基本相同,stage1,2,3属于encoder,stage4,5属于decoder。

如上图所示,在LaneNet 中,语义分割和实例分割两个任务共享 stage1 和 stage2,并将 stage3 和后面的 decoder 层作为各自的分支(branch)进行训练;其中,语义分割分支(branch)的输出 shape 为W*H*2,实例分割分支(branch)的输出 shape 为W*H*N,W,H分别为原图宽和高,N 为 embedding vector 的维度;两个分支的loss权重相同。

ENet 实现中有一些 block 可以借鉴:

H-Net

LaneNet的输出是每条车道线的像素集合,还需要根据这些像素点回归出一条车道线。传统的做法是将图片投影到 bird’s-eye view 中,然后使用 2 阶或者 3 阶多项式进行拟合。在这种方法中,变换矩阵 H 只被计算一次,所有的图片使用的是相同的变换矩阵,这会导致地平面(山地,丘陵)变化下的误差。

为了解决这个问题,论文训练了一个可以预测变换矩阵 H 的神经网络 H-Net,网络的输入是图片,输出是变换矩阵 H:

通过置 0 对转置矩阵进行约束,即水平线在变换下保持水平。(即坐标 y 的变换不受坐标 x 的影响)

由上式可以看出,转置矩阵 H 只有6个参数,因此H-Net的输出是一个 6 维的向量。H-Net 由 6 层普通卷积网络和一层全连接网络构成,其网络结构如图所示:

Curve Fitting

Curve fitting的过程就是通过坐标 y 去重新预测坐标 x 的过程:

  • 对于包含 N 个像素点的车道线,每个像素点 $p_i = [x_i, y_i, 1]^T \in P$, 首先使用 H-Net 的预测输出 H 对其进行坐标变换:

$P^{'} = HP$

  • 随后使用 最小二乘法对 3d 多项式的参数进行拟合:

$w = (Y^TY)^{-1}Y^Tx^{'}$

  • 根据拟合出的参数 $w = [\alpha, \beta, \gamma]^T$ 预测出 $x_i^{'*}$

$x_i^{'*} = \alpha y^{'2} + \beta y^{'} + \gamma$

  • 最后将 $x_i^{'*}$ 投影回去:

$p_i^{*} = H^{-1}p_i^{'*}$

Loss function

$Loss = \frac{1}{N} \sum_{i=1}^{N}(x_i^{*} - x_i)^2 $

实验参数

LanNet

Dataset : Tusimple

Embedding dimension = 4

δ_v=0.5

δ_d=3

Image size = 512*256

Adam optimizer

Learning rate = 5e-4

Batch size = 8

H-Net

Dataset : Tusimple

3rd-orderpolynomial

Image size =128*64

Adam optimizer

Learning rate = 5e-5

Batch size = 10

评价标准

语义分割部分

第三方

$accuracy = \frac{2}{1/recall  + 1/precision}$

$recall = \frac{|P_1 \cap G_1|}{|G_1|}$ # 统计GT中车道线分对的概率

$precision = \frac{|P_0 \cap G_0|}{|G_0|}$ # 统计GT中背景分对的概率

设定 $G_1$ 代表 GT二值图里像素值为 1 部分的集合,$P_1$ 表示检测结果为 1 的集合。

def compute_out(net, input_tensor, binary_label_tensor, instance_label_tensor):
# calculate the loss
compute_ret = net.compute_loss(input_tensor=input_tensor, binary_label=binary_label_tensor,
instance_label=instance_label_tensor, name='lanenet_model')
total_loss = compute_ret['total_loss']
binary_seg_loss = compute_ret['binary_seg_loss']
disc_loss = compute_ret['discriminative_loss']
pix_embedding = compute_ret['instance_seg_logits'] # calculate the accuracy
out_logits = compute_ret['binary_seg_logits']
out_logits = tf.nn.softmax(logits=out_logits)
out_logits_out = tf.argmax(out_logits, axis=-1)
out = tf.argmax(out_logits, axis=-1)
out = tf.expand_dims(out, axis=-1) idx = tf.where(tf.equal(binary_label_tensor, 1))
pix_cls_ret = tf.gather_nd(out, idx)
recall = tf.count_nonzero(pix_cls_ret)
recall = tf.divide(recall, tf.cast(tf.shape(pix_cls_ret)[0], tf.int64)) idx = tf.where(tf.equal(binary_label_tensor, 0))
pix_cls_ret = tf.gather_nd(out, idx)
precision = tf.subtract(tf.cast(tf.shape(pix_cls_ret)[0], tf.int64), tf.count_nonzero(pix_cls_ret))
precision = tf.divide(precision, tf.cast(tf.shape(pix_cls_ret)[0], tf.int64)) accuracy = tf.divide(2.0, tf.divide(1.0, recall) + tf.divide(1.0, precision)) return total_loss, binary_seg_loss, disc_loss, pix_embedding, out_logits_out, accuracy

简单示例:

import numpy as np
import tensorflow as tf out_logits = np.array([
[[0.8, 0.8, 0.8, 0.8, 0.1],
[0.8, 0.1, 0.1, 0.8, 0.8],
[0.8, 0.1, 0.1, 0.1, 0.8],
[0.8, 0.1, 0.1, 0.1, 0.1],
[0.8, 0.8, 0.8, 0.8, 0.8]],
[[0.1, 0.1, 0.1, 0.1, 0.8],
[0.1, 0.8, 0.8, 0.2, 0.1],
[0.1, 0.8, 0.8, 0.8, 0.1],
[0.1, 0.8, 0.8, 0.8, 0.2],
[0.1, 0.1, 0.1, 0.1, 0.1]]
]) # 预测结果
binary_label = np.array([
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 1],
[0, 0, 0, 0, 0]
]) # GT
logits = np.transpose(out_logits, (1, 2, 0))
out_logits = tf.constant(logits, dtype=tf.float32)
binary_label_tensor = tf.constant(binary_label, dtype=tf.int32)
binary_label_tensor = tf.expand_dims(binary_label_tensor, axis=-1)
# =================== pix_cls_ret: 对于 GT 中为 1 的部分,统计 Pre 中是否分对,1对0错
out_logits = tf.nn.softmax(logits=out_logits)
out = tf.argmax(out_logits, axis=-1) # 最后那个维度上的 max_idx
"""
[[0 0 0 0 1]
[0 1 1 0 0]
[0 1 1 1 0]
[0 1 1 1 1]
[0 0 0 0 0]]
"""
out = tf.expand_dims(out, axis=-1) # 增加一维 5x5 -> 5x5x1
# =================== pix_cls_ret: 对于 GT 中为 1 的部分,统计 Pre 中是否分对,1对0错
idx1 = tf.where(tf.equal(binary_label_tensor, 1))
pix_cls_ret1 = tf.gather_nd(out, idx1)
# =================== recall: 统计GT车道线像素点分对的概率,统计 recall = (P_1 \cap G1) / G1
recall = tf.count_nonzero(pix_cls_ret1) # P_1 \cap G1
recall = tf.divide(recall, tf.cast(tf.shape(pix_cls_ret1)[0], tf.int64))
# =================== pix_cls_ret: 对于 GT 中为 0 的部分,统计 Pre 中是否分对,0对1错
idx2 = tf.where(tf.equal(binary_label_tensor, 0))
pix_cls_ret2 = tf.gather_nd(out, idx2)
# =================== precision: 统计GT背景像素点分对的的概率,统计 precision = (P_0 \cap G0) / G0
precision = tf.subtract(tf.cast(tf.shape(pix_cls_ret2)[0], tf.int64), tf.count_nonzero(pix_cls_ret2)) # TP_0
precision = tf.divide(precision, tf.cast(tf.shape(pix_cls_ret2)[0], tf.int64)) accuracy = tf.divide(2.0, tf.divide(1.0, recall) + tf.divide(1.0, precision))
with tf.Session() as sess:
out = out.eval()
idx1 = idx1.eval()
pix_cls_ret1 = pix_cls_ret1.eval()
idx2 = idx2.eval()
pix_cls_ret2 = pix_cls_ret2.eval()
recall = recall.eval() # 9/10
precision = precision.eval() # 14/15
accuracy = accuracy.eval() # 2 / (9/10 + 14/15)

官方

$accuracy = \frac{|P_1 \cap G_1|}{|G_1|}$ # 统计GT中车道线分对的概率

def calculate_model_precision(input_tensor, label_tensor):
"""
calculate accuracy acc = correct_nums / ground_truth_nums
:param input_tensor: binary segmentation logits
:param label_tensor: binary segmentation label
:return:
""" logits = tf.nn.softmax(logits=input_tensor)
final_output = tf.expand_dims(tf.argmax(logits, axis=-1), axis=-1) idx = tf.where(tf.equal(final_output, 1))
pix_cls_ret = tf.gather_nd(label_tensor, idx) # P1 \cap G1
accuracy = tf.count_nonzero(pix_cls_ret)
accuracy = tf.divide(
accuracy,
tf.cast(tf.shape(tf.gather_nd(label_tensor, tf.where(tf.equal(label_tensor, 1))))[0], tf.int64)) return accuracy

$fp = \frac{|P_1| - |P_1 \cap G_1|}{|P_1|}$ # 统计Pre中的车道线误检率

def calculate_model_fp(input_tensor, label_tensor):
"""
calculate fp figure
:param input_tensor:
:param label_tensor:
:return:
"""
logits = tf.nn.softmax(logits=input_tensor)
final_output = tf.expand_dims(tf.argmax(logits, axis=-1), axis=-1) idx = tf.where(tf.equal(final_output, 1))
pix_cls_ret = tf.gather_nd(final_output, idx) # P_1
false_pred = tf.cast(tf.shape(pix_cls_ret)[0], tf.int64) - tf.count_nonzero(
tf.gather_nd(label_tensor, idx)
) return tf.divide(false_pred, tf.cast(tf.shape(pix_cls_ret)[0], tf.int64))

$fn = \frac{|G_1| - |P_1 \cap G_1|}{|G_1|}$ # 统计GT车道线中漏检率

def calculate_model_fn(input_tensor, label_tensor):
"""
calculate fn figure
:param input_tensor:
:param label_tensor:
:return:
"""
logits = tf.nn.softmax(logits=input_tensor)
final_output = tf.expand_dims(tf.argmax(logits, axis=-1), axis=-1) idx = tf.where(tf.equal(label_tensor, 1))
pix_cls_ret = tf.gather_nd(final_output, idx) # P1 \cap G1
label_cls_ret = tf.gather_nd(label_tensor, tf.where(tf.equal(label_tensor, 1))) # G_1
mis_pred = tf.cast(tf.shape(label_cls_ret)[0], tf.int64) - tf.count_nonzero(pix_cls_ret) return tf.divide(mis_pred, tf.cast(tf.shape(label_cls_ret)[0], tf.int64))

简单示例:

import numpy as np
import tensorflow as tf
import tools.evaluate_model_utils as eval out_logits = np.array([
[[0.8, 0.8, 0.8, 0.8, 0.1],
[0.8, 0.1, 0.1, 0.8, 0.8],
[0.8, 0.1, 0.1, 0.1, 0.8],
[0.8, 0.1, 0.1, 0.1, 0.1],
[0.8, 0.8, 0.8, 0.8, 0.8]],
[[0.1, 0.1, 0.1, 0.1, 0.8],
[0.1, 0.8, 0.8, 0.2, 0.1],
[0.1, 0.8, 0.8, 0.8, 0.1],
[0.1, 0.8, 0.8, 0.8, 0.2],
[0.1, 0.1, 0.1, 0.1, 0.1]]
]) # 预测结果
binary_label = np.array([
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 1],
[0, 0, 0, 0, 0]
]) # GT
logits = np.transpose(out_logits, (1, 2, 0))
out_logits = tf.constant(logits, dtype=tf.float32)
binary_label_tensor = tf.constant(binary_label, dtype=tf.int32)
binary_label_tensor = tf.expand_dims(binary_label_tensor, axis=-1)
""" out
[[0 0 0 0 1]
[0 1 1 0 0]
[0 1 1 1 0]
[0 1 1 1 1]
[0 0 0 0 0]]
"""
# 统计GT中车道线分对的概率
accuracy = eval.calculate_model_precision(out_logits, binary_label_tensor)
# 统计检测中的车道线误检率
fp = eval.calculate_model_fp(out_logits, binary_label_tensor)
# 统计GT车道线中漏检率
fn = eval.calculate_model_fn(out_logits, binary_label_tensor) with tf.Session() as sess:
accuracy = accuracy.eval() # 9/10
fp = fp.eval() # 1/10
fn = fn.eval() # 1/10

相关实验

1. 替换 backbone 为 mobilenet_v2

2. 调整 embedding dim

3. 预处理方式调整

4. 上采样方式替换

5. 学习率衰减方式

6. 反卷积卷积核尺寸调整

代码结构

lanenet-lane-detection
├── config //配置文件
├── data //一些样例图片和曲线拟合参数文件
├── data_provider // 用于加载数据以及制作 tfrecords
├── lanenet_model
│ ├── lanenet.py //网络布局 inference/compute_loss/compute_acc
│ ├── lanenet_front_end.py // backbone 布局
│ ├── lanenet_back_end.py // 网络任务和Loss计算 inference/compute_loss
│ ├── lanenet_discriminative_loss.py //discriminative_loss实现
│ ├── lanenet_postprocess.py // 后处理操作,包括聚类和曲线拟合
├── model //保存模型的目录semantic_segmentation_zoo
├── semantic_segmentation_zoo // backbone 网络定义
│ ├── __init__.py
│ ├── vgg16_based_fcn.py //VGG backbone
│ └─+ mobilenet_v2_based_fcn.py //mobilenet_v2 backbone
│ └── cnn_basenet.py // 基础 block
├── tools //训练、测试主函数
│ ├── train_lanenet.py //训练
│ ├── test_lanenet.py //测试
│ └──+ evaluate_dataset.py // 数据集评测 accuracy
│ └── evaluate_lanenet_on_tusimple.py // 数据集检测结果保存
│ └── evaluate_model_utils.py // 评测相关函数 calculate_model_precision/calculate_model_fp/calculate_model_fn
│ └── generate_tusimple_dataset.py // 原始数据转换格式
├─+ showname.py //模型变量名查看
├─+ change_name.py //模型变量名修改
├─+ freeze_graph.py//生成pb文件
├─+ convert_weights.py//对权重进行转换,为了模型的预训练
└─+ convert_pb.py //生成pb文

数据准备

1. 首先按照链接下载 Tusimple 数据集:train_set.zip  test_set.zip test_label.json

2. 调用 tools/generate_tusimple_dataset.py 将原始数据转换格式

这里会生成 train.txt 和 val.txt,调整格式如下:

testing/gt_image/0000.png testing/gt_binary_image/0000.png testing/gt_instance_image/0000.png
testing/gt_image/0001.png testing/gt_binary_image/0001.png testing/gt_instance_image/0001.png
testing/gt_image/0002.png testing/gt_binary_image/0002.png testing/gt_instance_image/0002.png

3. 调用 data_provider/lanenet_data_feed_pipline.py 转换标注成 TFRecord 格式

相关文献

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

ENet: A Deep Neural Network Architecture for Real-Time Semantic Segmentation

Discriminative Loss: Semantic Instance Segmentation with a Discriminative loss function

深度学习笔记(十二)车道线检测 LaneNet的更多相关文章

  1. python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL

    python3.4学习笔记(十二) python正则表达式的使用,使用pyspider匹配输出带.html结尾的URL实战例子:使用pyspider匹配输出带.html结尾的URL:@config(a ...

  2. Go语言学习笔记十二: 范围(Range)

    Go语言学习笔记十二: 范围(Range) rang这个关键字主要用来遍历数组,切片,通道或Map.在数组和切片中返回索引值,在Map中返回key. 这个特别像python的方式.不过写法上比较怪异使 ...

  3. UFLDL深度学习笔记 (二)SoftMax 回归(矩阵化推导)

    UFLDL深度学习笔记 (二)Softmax 回归 本文为学习"UFLDL Softmax回归"的笔记与代码实现,文中略过了对代价函数求偏导的过程,本篇笔记主要补充求偏导步骤的详细 ...

  4. Tensorflow深度学习之十二:基础图像处理之二

    Tensorflow深度学习之十二:基础图像处理之二 from:https://blog.csdn.net/davincil/article/details/76598474   首先放出原始图像: ...

  5. 语义分割之车道线检测Lanenet(tensorflow版)

    Lanenet 一个端到端的网络,包含Lanenet+HNet两个网络模型,其中,Lanenet完成对车道线的实例分割,HNet是一个小网络结构,负责预测变换矩阵H,使用转换矩阵H对同属一条车道线的所 ...

  6. 车道线检测LaneNet

    LaneNet LanNet Segmentation branch 完成语义分割,即判断出像素属于车道or背景 Embedding branch 完成像素的向量表示,用于后续聚类,以完成实例分割 H ...

  7. java jvm学习笔记十二(访问控制器的栈校验机制)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 本节源码:http://download.csdn.net/detail/yfqnihao/4863854 这一节,我们 ...

  8. (C/C++学习笔记) 十二. 指针

    十二. 指针 ● 基本概念 位系统下为4字节(8位十六进制数),在64位系统下为8字节(16位十六进制数) 进制表示的, 内存地址不占用内存空间 指针本身是一种数据类型, 它可以指向int, char ...

  9. Python学习笔记(十二)—Python3中pip包管理工具的安装【转】

    本文转载自:https://blog.csdn.net/sinat_14849739/article/details/79101529 版权声明:本文为博主原创文章,未经博主允许不得转载. https ...

随机推荐

  1. 查找担保圈-step4-提取s1中担保圈路径中的成员

    USE [test] GO /****** Object: StoredProcedure [dbo].[p02_get_group_member] Script Date: 2019/7/8 14: ...

  2. APP_测试流程(测试点)

    1 APP测试基本流程 1.1流程图 1.2测试周期 测试周期可按项目的开发周期来确定测试时间,一般测试时间为两三周(即15个工作日),根据项目情况以及版本质量可适当缩短或延长测试时间. 1.3测试资 ...

  3. windows 安装 python 踩坑记录

    官方不建议使用 64 bit python,容易出各种问题 Unable to find vcvarsall.bat 凡是安装与操作系统底层相关的 python 扩展都会遇到这个问题,如 PIL,Pi ...

  4. window10提交代码到码云

    1.创建项目文件夹,例如创建一个"爬虫项目码云仓库" 2.进入项目文件夹,在地址栏输入cmd然后回车,这样就在该文件夹打开了终端 3.终端输入git init初始化项目仓库,此时会 ...

  5. Python基础教程之dict和set

    1. dict Python中的dict等于js中的 map ,使用键-值(key-value)存储,具有极快的查找速度. 如果 我们要根据同学的姓名去查找他的成绩在不用dict的情况下.就需要两个l ...

  6. 超详细思路讲解SQL语句的查询实现,及数据的创建。

    最近一直在看数据库方面的问题,总结了一下SQL语句,这是部分详细的SQL问题,思路讲解: 第一步:创建数据库表,及插入数据信息 --Student(S#,Sname,Sage,Ssex) 学生表 CR ...

  7. FastDFS整合Nginx的模块:fastdfs-nginx-module报错:fdfs_define.h:15:27: 致命错误:common_define.h:没有那个文件或目录

    错误提示: /usr/include/fastdfs/fdfs_define.h:15:27: 致命错误:common_define.h:没有那个文件或目录 [解决办法] 1.编辑fastdfs-ng ...

  8. oracle 12C的新特性-CDB和PDB

    1.前言 CDB与PDB是Oracle 12C引入的新特性,在ORACLE 12C数据库引入的多租用户环境(Multitenant Environment)中,允许一个数据库容器(CDB)承载多个可插 ...

  9. linux创建定时任务发送钉钉通知

    一.现在钉钉里面添加机器人 添加成功后,复制出Webhook链接. 注意,自定义关键字时你的发送信息中一定要完整包含关键字 二.找到自己的服务器 1. sudo su 切换到root用户 2.cron ...

  10. Linux grep命令 -- 三剑客老三

    常用选项 -E :开启扩展(Extend)的正则表达式. -i :忽略大小写(ignore case). -v :反过来(invert),只打印没有匹配的,而匹配的反而不打印. -n :显示行号 -w ...