论文: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. 什么是PWA

    什么是PWA:https://www.jianshu.com/p/299c9c720e56 2019前端必会黑科技之PWA:https://www.jianshu.com/p/098af61bbe04 ...

  2. 安装gitlab ce

    切换到root用户,安装相关依赖 yum install curl policycoreutils openssh-server openssh-clients service sshd restar ...

  3. Codeforces 1201C. Maximum Median

    传送门 看到中位数考虑先把数排序一下 然后有个显然的贪心,一个数增加后一定不能比下一个数大,不然我们直接增加下一个数显然更优 所以初始时的中位数操作后也是中位数 那么我们只要考虑中间再往后怎么加使得答 ...

  4. 使用.NET Core创建Windows服务(一) - 使用官方推荐方式

    原文:使用.NET Core创建Windows服务(一) - 使用官方推荐方式 原文:Creating Windows Services In .NET Core – Part 1 – The &qu ...

  5. centos查看实时网络带宽占用情况方法【转】

    Linux中查看网卡流量工具有iptraf.iftop以及nethogs等,iftop可以用来监控网卡的实时流量(可以指定网段).反向解析IP.显示端口信息等. centos安装iftop的命令如下: ...

  6. performance面板使用,以及解决动画卡顿

    https://googlechrome.github.io/devtools-samples/jank//    官方案例 https://juejin.im/post/5b65105f518825 ...

  7. vue跳转新页面后回到顶部

    全部页面的话就在mian.js中设置或者 单独的某个页面就在页面加载之前设置 单独设置需变为this.$router router.afterEach((to,from,next)=>{ win ...

  8. selenium入门学习

    在写爬虫的学习过程中,经常会有一些动态加载,有些是可以动过接口直接获取到,但是实在没办法,所以学习下selenium. 首先百度一下: Selenium [1]  是一个用于Web应用程序测试的工具. ...

  9. IT技术网站博客推荐

    CSDN 全球最大中文IT社区,为IT专业技术人员提供最全面的信息传播和服务平台. 51CTO 技术成就梦想 - 中国领先的IT技术网站 itEye Java编程 Spring框架 Ajax技术 ag ...

  10. C++中写文件ofstream 的<< 操作符 与C风格的fwrite 的笔记

    在某次工作中,调用了某SDK接口,该接口通过一个回调函数返回需要的内容.我们需要的内容是H.264码流,该码流通过一个unsigned char* 变量带回,另外还有一个长度 int length.为 ...