無人駕駛汽車系統入門:基於VoxelNet的激光雷達點雲車輛檢測及ROS實現
作者 | 申澤邦(Adam Shan)
蘭州大學在讀碩士研究生,主要研究方向無人駕駛,深度學習;蘭大未來計算研究院無人車團隊負責人,自動駕駛全棧工程師。
之前我們提到使用SqueezeSeg進行了三維點雲的分割,由於採用的是SqueezeNet作為特徵提取網路,該方法的處理速度相當迅速(在單GPU加速的情況下可達到100FPS以上的效率),然而,該方法存在如下的問題:
第一,雖然採用了CRF改進邊界模糊的問題,但是從實踐結果來看,其分割的精度仍然偏低;
第二,該模型需要大量的訓練集,而語義分割數據集標註困難,很難獲得大規模的數據集。當然,作者在其後的文章:SqueezeSegV2: Improved Model Structure and Unsupervised Domain Adaptation for Road-Object Segmentation from a LiDAR Point Cloud 中給出了改進的方案,我將在後面的文章中繼續解讀。需要注意的是,在無人車環境感知問題中,很多情況下並不需要對目標進行精確的語義分割,只需將目標以一個三維的Bounding Box準確框出即可(即Detection)。
本文介紹一種基於點雲的Voxel(三維體素)特徵的深度學習方法,實現對點雲中目標的準確檢測,並提供一個簡單的ROS實現,供大家參考。
VoxelNet結構
VoxelNet是一個端到端的點雲目標檢測網路,和圖像視覺中的深度學習方法一樣,其不需要人為設計的目標特徵,通過大量的訓練數據集,即可學習到對應的目標的特徵,從而檢測出點雲中的目標,如下:
VoxelNet的網路結構主要包含三個功能模塊:
(1)特徵學習層;
(2)卷積中間層;
(3) 區域提出網路( Region Proposal Network,RPN)。
特徵學習網路
特徵學習網路的結構如下圖所示,包括體素分塊(Voxel Partition),點雲分組(Grouping),隨機採樣(Random Sampling),多層的體素特徵編碼(Stacked Voxel Feature Encoding),稀疏張量表示(Sparse Tensor Representation)等步驟,具體來說:
體素分塊
這是點雲操作里最常見的處理,對於輸入點雲,使用相同尺寸的立方體對其進行劃分,我們使用一個深度、高度和寬度分別為(D,H,W)的大立方體表示輸入點雲,每個體素的深高寬為(vD,vH,vW),則整個數據的三維體素化的結果在各個坐標上生成的體素格(voxel grid)的個數為:
點雲分組
將點雲按照上一步分出來的體素格進行分組,如上圖所示。
隨機採樣
很顯然,按照這種方法分組出來的單元會存在有些體素格點很多,有些格子點很少的情況,64線的激光雷達一次掃描包含差不多10萬個點,全部處理需要的計算力和內存都很高,而且高密度的點勢必會給神經網路的計算結果帶來偏差。所以,該方法在這裡插入了一層隨機採樣,對於每一個體素格,隨機採樣固定數目的點,T 。
多個體素特徵編碼(Voxel Feature Encoding,VFE)層
之後是多個體素特徵編碼層,簡稱為VFE層,這是特徵學習的主要網路結構,以第一個VFE層為例說明:
對於輸入:
是一個體素格內隨機採樣的點集,分別點的XYZ坐標以及激光束的反射強度(即intensity),我們首先計算體素內所有點的平均值 (vx,vy,vz)作為體素格的形心(類似於Voxel Grid Filter),那麼我們就可以將體素格內所有點的特徵數量擴充為如下形式:
接著,每一個都會通過一個全連接網路(Fully Connected,FC,論文中用的是FCN來簡稱,實際上FCN更多的被用於表示全卷積網路,所以原文此處用FCN簡稱實際上不妥)被映射到一個特徵空間,輸入的特徵維度為7,輸出的特徵維數變成m mm,全連接層包含了一個線性映射層,一個批標準化(Batch Normalization),以及一個非線性運算(ReLU),得到逐點的(point-wise)的特徵表示。
接著我們採用最大池化(MaxPooling)對上一步得到的特徵表示進行逐元素的聚合,這一池化操作是對元素和元素之間進行的,得到局部聚合特徵(Locally Aggregated Feature),即,最後,將逐點特徵和逐元素特徵進行連接(concatenate),得到輸出的特徵集合:
對於所有的非空的體素格我們都進行上述操作,並且它們都共享全連接層(FC)的參數。我們使用符號來描述經過VFE以後特徵的維數變化,那麼顯然全連接層的參數矩陣大小為:
由於VFE層中包含了逐點特徵和逐元素特徵的連接,經過多層VFE以後,我們希望網路可以自動學習到每個體素內的特徵表示(比如說體素格內的形狀信),那麼如何學習體素內的特徵表示呢?原論文的方法下圖所示:
通過對體素格內所有點進行最大池化,得到一個體素格內特徵表示 C 。
稀疏張量表示
通過上述流程處理非空體素格,我們可以得到一系列的體素特徵(Voxel Feature)。這一系列的體素特徵可以使用一個4維的稀疏張量來表示:
雖然一次lidar掃描包含接近10萬個點,但是超過90%的體素格都是空的,使用稀疏張量來描述非空體素格在於能夠降低反向傳播時的內存和計算消耗。
對於具體的車輛檢測問題,我們取沿著Lidar坐標系的(Z,Y,X) (Z,Y,X)(Z,Y,X)方向取[?3,1]×[?40,40]×[0,70.4] [?3, 1] × [?40, 40] × [0, 70.4][?3,1]×[?40,40]×[0,70.4]立方體(單位為米)作為輸入點雲,取體素格的大小為:
那麼有
我們設置隨機採樣的T=35 T = 35T=35,並且採用兩個VFE層:VFE-1(7, 32) 和 VFE-2(32, 128),最後的全連接層將VFE-2的輸出映射到。最後,特徵學習網路的輸出即為一個尺寸為 (128×10×400×352) 的稀疏張量。整個特徵網路的TensorFlow實現代碼如下:
classVFELayer(object):
def__init__(self, out_channels, name):
super(VFELayer,self).__init__()
self.units = int(out_channels /2)
with tf.variable_scope(name, reuse=tf.AUTO_REUSE) asscope:
self.dense = tf.layers.Dense(
self.units, tf.nn.relu, name="dense", _reuse=tf.AUTO_REUSE, _scope=scope)
self.batch_norm = tf.layers.BatchNormalization(
name="batch_norm", fused=True, _reuse=tf.AUTO_REUSE, _scope=scope)
defapply(self, inputs, mask, training):
# [K, T, 7] tensordot [7, units] = [K, T, units]
pointwise =self.batch_norm.apply(self.dense.apply(inputs), training)
#n [K, 1, units]
aggregated = tf.reduce_max(pointwise, axis=1, keep_dims=True)
# [K, T, units]
repeated = tf.tile(aggregated, [1, cfg.VOXEL_POINT_COUNT,1])
# [K, T, 2 * units]
concatenated = tf.concat([pointwise, repeated], axis=2)
mask = tf.tile(mask, [1,1,2*self.units])
concatenated = tf.multiply(concatenated, tf.cast(mask, tf.float32))
returnconcatenated
classFeatureNet(object):
def__init__(self, training, batch_size, name=""):
super(FeatureNet,self).__init__()
self.training = training
# scalar
self.batch_size = batch_size
# [ΣK, 35/45, 7]
self.feature = tf.placeholder(
tf.float32, [None, cfg.VOXEL_POINT_COUNT,7], name="feature")
# [ΣK]
self.number = tf.placeholder(tf.int64, [None], name="number")
# [ΣK, 4], each row stores (batch, d, h, w)
self.coordinate = tf.placeholder(
tf.int64, [None,4], name="coordinate")
with tf.variable_scope(name, reuse=tf.AUTO_REUSE) asscope:
self.vfe1 = VFELayer(32,"VFE-1")
self.vfe2 = VFELayer(128,"VFE-2")
# boolean mask [K, T, 2 * units]
mask = tf.not_equal(tf.reduce_max(
self.feature, axis=2, keep_dims=True),)
x =self.vfe1.apply(self.feature, mask,self.training)
x =self.vfe2.apply(x, mask,self.training)
# [ΣK, 128]
voxelwise = tf.reduce_max(x, axis=1)
# car: [N * 10 * 400 * 352 * 128]
# pedestrian/cyclist: [N * 10 * 200 * 240 * 128]
self.outputs = tf.scatter_nd(
self.coordinate, voxelwise, [self.batch_size,10, cfg.INPUT_HEIGHT, cfg.INPUT_WIDTH,128])
卷積中間層
每一個卷積中間層包含一個3維卷積,一個BN層(批標準化),一個非線性層(ReLU),我們用:
來描述一個卷積中間層,Conv3D表示是三維卷積,cin,cout分別表示輸入和輸出的通道數,k是卷積核的大小,它是一個向量,對於三維卷積而言,卷積核的大小為(k,k,k);s即stride,卷積操作的步長;p即padding,填充的尺寸。
對於車輛檢測而言,設計的卷積中間層如下:
Conv3D(128, 64, 3,(2,1,1), (1,1,1))
Conv3D(64, 64, 3, (1,1,1), (0,1,1))
Conv3D(64, 64, 3, (2,1,1), (1,1,1))
卷積中間層的TensorFlow代碼如下:
def ConvMD(M, Cin, Cout, k, s, p, input, training=True, activation=True, bn=True, name="conv"):
temp_p = np.array(p)
temp_p = np.lib.pad(temp_p, (1,1),"constant", constant_values=(,))
with tf.variable_scope(name)asscope:
if(M ==2):
paddings = (np.array(temp_p)).repeat(2).reshape(4,2)
pad = tf.pad(input, paddings,"CONSTANT")
temp_conv = tf.layers.conv2d(
pad, Cout, k, strides=s, padding="valid", reuse=tf.AUTO_REUSE, name=scope)
if(M ==3):
paddings = (np.array(temp_p)).repeat(2).reshape(5,2)
pad = tf.pad(input, paddings,"CONSTANT")
temp_conv = tf.layers.conv3d(
pad, Cout, k, strides=s, padding="valid", reuse=tf.AUTO_REUSE, name=scope)
ifbn:
temp_conv = tf.layers.batch_normalization(
temp_conv, axis=-1, fused=True, training=training, reuse=tf.AUTO_REUSE, name=scope)
ifactivation:
returntf.nn.relu(temp_conv)
else:
returntemp_conv
# convolutinal middle layers
temp_conv = ConvMD(3,128,64,3, (2,1,1),
(1,1,1),self.input, name="conv1")
temp_conv = ConvMD(3,64,64,3, (1,1,1),
(,1,1), temp_conv, name="conv2")
temp_conv = ConvMD(3,64,64,3, (2,1,1),
(1,1,1), temp_conv, name="conv3")
temp_conv = tf.transpose(temp_conv, perm=[,2,3,4,1])
temp_conv = tf.reshape(temp_conv, [-1, cfg.INPUT_HEIGHT, cfg.INPUT_WIDTH,128])
區域提出網路(RPN)
RPN實際上是目標檢測網路中常用的一種網路,下圖是VoxelNet中使用的RPN:
如圖所示,該網路包含三個全卷積層塊(Block),每個塊的第一層通過步長為2的卷積將特徵圖採樣為一半,之後是三個步長為1的卷積層,每個卷積層都包含BN層和ReLU操作。將每一個塊的輸出都上採樣到一個固定的尺寸並串聯構造高解析度的特徵圖。最後,該特徵圖通過兩種二維卷積被輸出到期望的學習目標:
概率評分圖(Probability Score Map )
回歸圖(Regression Map)
使用TensorFlow實現該RPN如下(非完整代碼,完整代碼請見文末鏈接)):
def Deconv2D(Cin, Cout, k, s, p, input, training=True, bn=True, name="deconv"):
temp_p = np.array(p)
temp_p = np.lib.pad(temp_p, (1,1),"constant", constant_values=(,))
paddings = (np.array(temp_p)).repeat(2).reshape(4,2)
pad = tf.pad(input, paddings,"CONSTANT")
with tf.variable_scope(name)asscope:
temp_conv = tf.layers.conv2d_transpose(
pad, Cout, k, strides=s, padding="SAME", reuse=tf.AUTO_REUSE, name=scope)
ifbn:
temp_conv = tf.layers.batch_normalization(
temp_conv, axis=-1, fused=True, training=training, reuse=tf.AUTO_REUSE, name=scope)
returntf.nn.relu(temp_conv)
# rpn
# block1:
temp_conv = ConvMD(2,128,128,3, (2,2), (1,1),
temp_conv, training=self.training, name="conv4")
temp_conv = ConvMD(2,128,128,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv5")
temp_conv = ConvMD(2,128,128,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv6")
temp_conv = ConvMD(2,128,128,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv7")
deconv1 = Deconv2D(128,256,3, (1,1), (,),
temp_conv, training=self.training, name="deconv1")
# block2:
temp_conv = ConvMD(2,128,128,3, (2,2), (1,1),
temp_conv, training=self.training, name="conv8")
temp_conv = ConvMD(2,128,128,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv9")
temp_conv = ConvMD(2,128,128,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv10")
temp_conv = ConvMD(2,128,128,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv11")
temp_conv = ConvMD(2,128,128,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv12")
temp_conv = ConvMD(2,128,128,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv13")
deconv2 = Deconv2D(128,256,2, (2,2), (,),
temp_conv, training=self.training, name="deconv2")
# block3:
temp_conv = ConvMD(2,128,256,3, (2,2), (1,1),
temp_conv, training=self.training, name="conv14")
temp_conv = ConvMD(2,256,256,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv15")
temp_conv = ConvMD(2,256,256,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv16")
temp_conv = ConvMD(2,256,256,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv17")
temp_conv = ConvMD(2,256,256,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv18")
temp_conv = ConvMD(2,256,256,3, (1,1), (1,1),
temp_conv, training=self.training, name="conv19")
deconv3 = Deconv2D(256,256,4, (4,4), (,),
temp_conv, training=self.training, name="deconv3")
# final:
temp_conv = tf.concat([deconv3, deconv2, deconv1],-1)
# Probability score map, scale = [None, 200/100, 176/120, 2]
p_map = ConvMD(2,768,2,1, (1,1), (,), temp_conv,
training=self.training, activation=False, bn=False, name="conv20")
# Regression(residual) map, scale = [None, 200/100, 176/120, 14]
r_map = ConvMD(2,768,14,1, (1,1), (,),
temp_conv, training=self.training, activation=False, bn=False, name="conv21")
損失函數
我們首先定義為正樣本集合,為負樣本集合,使用來表示一個真實的3D標註框,其中
表示標註框中心的坐標,表示標註框的長寬高,表示偏航角(Yaw)。相應的,表示正樣本框。那麼回歸的目標為一下七個量:
其中
是正樣本框的對角線。我們定義損失函數為:
其中分別表示正樣本和負樣本的Softmax輸出,分別表示神經網路的正樣本輸出的標註框和真實標註框。損失函數的前兩項表示對於正樣本輸出和負樣本輸出的分類損失(已經進行了正規化),其中表示交叉熵,是兩個常數,它們作為權重來平衡正負樣本損失對於最後的損失函數的影響。表示回歸損失,這裡採用的是Smooth L1函數。
ROS實踐
我們仍然使用第二十六篇博客的數據(截取自KITTI),下載地址:https://pan.baidu.com/s/1kxZxrjGHDmTt-9QRMd_kOA
我們直接採用qianguih提供的訓練好的模型(參考:https://github.com/qianguih/voxelnet ,大家也可以基於該項目自己訓練模型)。
安裝項目依賴環境:
python3.5+
TensorFlow (tested on 1.4)
opencv
shapely
numba
easydict
ROS
jsk package
準備數據
下載上面的數據集,解壓到項目(源碼地址見文末)的data文件夾下,目錄結構為:
data
----lidar_2d
--------0000...1.npy
--------0000...2.npy
--------.......
運行
catkin_make
roscd voxelnet/script/
python3 voxelnet_ros.py & python3 pub_kitti_point_cloud.py
注意不能使用rosrun,因為VoxelNet代碼為Python 3.x
rqt節點圖
使用Rviz可視化
存在的問題
實例的模型的性能不佳,由於論文作者沒有開源其代碼,許多參數仍然有待調整
調整速度慢,沒有實現作者提出的高效策略
感興趣的同學可以試著調整參數提高模型性能,歡迎大家留言、私信交流。
源碼地址:
https://github.com/AbangLZU/VoxelNetRos
數據地址:
https://pan.baidu.com/s/1kxZxrjGHDmTt-9QRMd_kOA
原文地址:
https://blog.csdn.net/AdamShan/article/details/84837211
2018 中國大數據技術大會
BDTC 2018
2018中國大數據技術大會今天在北京新雲南正式拉開序幕,產學研大伽年度精彩分享,現場前沿觀點頻現,思維碰撞精彩迭起~~大會第一天精彩觀點總結,分享給沒有到會場的小夥伴們~(另外,目前大會官網已更新部分PPT,掃描海報二維碼即可下載)
※小米發力AI場景下的「快應用」,投百億資源扶持開發者
※AlphaGo「兄弟」AlphaFold出世,DeepMind再創記錄
TAG:AI科技大本營 |