論文: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