3D視覺處理導論
隨着AR/VR,自動駕駛的快速發展,由於3D視覺比2D覺有更加豐富的信息,3D視覺變得越來越重要。處理3D視覺的神經網絡模型包括基於點的模型(Point-Based Models),基於像素的模型(Pixel-Based Models),基於體素的模型(Voxel-Based Models)。
3D 格式簡介
3D圖像測量更多的一個維度,深度信息。通常我們使用最多的3D格式是RGB-D和點雲。
RGB-D
通常的像素圖片,像素可以通過座標(x,y)找到,然後得到(R,G,B)的顏色信息。然而對於RGB-D的圖像,可以找到(Depth,R,G,B)的四個屬性的信息。RGB-D和點雲的不同之處在於,點雲的座標(x,y)反應的是真實世界的實際值,而不是簡單的整數值。
Point Cloud點雲
點雲可以從RGB-D圖像中構建出來。如果你有了RGB-D的圖像和相機的內參,那麼就可以創建從RGB-D圖像中創建點雲,通過簡單的計算真實世界位置通過相機的內參。這被稱之爲相機校準。因此,到現在,可以發現,RGB-D圖像是網格對齊的圖像,而點雲則是具有稀疏結構的點。
3D 深度學習模型
這裏主要列舉幾種網絡模型,以備網絡設計時進行參考。
Point-Based Neural Network Models
在預處理時需要進行旋轉,尺度變化,歸一化採樣,點交換等等。
Multi-Layer Perceptron (MLP)
Multi Rotational MLPs
Single Orientation CNN
Multi Rotational CNNs
Multi Rotational Resample & Max Pool Layers
ResNet-like
Pixel-Based Neural Network Models
MLP
Depth-Only Orthogonal MVCNN
Voxel-Based Neural Network Models
需要預先採樣,體素化等。
MLP
CNN
ResNet-like
3D Vision 實例
正如,2D圖像處理問題,我們可以檢測,識別所有的物體,在3D圖像中。
這裏引入兩個重要的3D場景分析的深度學習模型,VoxNet和PointNet。
VoxNet - Voxel Grid
Voxel grid 是最符合直覺的方法,能夠將3D模型放入到網格中,讓其更像是像素圖像,這裏稱之爲Voxel(體素)。那麼3D圖像就由座標(x,y,z)描述,像樂高玩具一樣。
Voxnet是一個深度學習架構,使用佔用網格對3D點雲進行分類,在分類問題上取得了非常好的結果。
舉例,如果我們將點雲放入到32x32x32的體素網格中,我們就可以建立32x32x32的數組,然後將點雲在每一個體素中進行計算。
在得到體素後,就可以使用3D卷積網絡,高效的在立方體上滑動進行基於體素圖像的處理。
我們需要將掃描的圖像放入到相同的基中,這樣即使我們的圖像不同,依然會變成處理同一個問題。然而,對提升採樣的操作,則很難確定Voxel的RGB圖像。
對於簡單的數據集,具有相似的點數,相似的掃描尺度,Voxnet確實是一個很好的,很簡單的方法。但是,對於複雜的數據集,卻不是一個很好的選擇。此外,由於Voxel需要形成3D的數據,使得點雲的稀疏結構變得稠密了,對於需要低功耗,實時性高場合,變得不是很有優勢。
PointNet - Points
基於Voxel的方法在分類問題上表現出很好的性能,但是同時採樣行爲也犧牲了很多信息。因此,我們需要對每一個點在網絡中進行訓練。
第一個問題是點的序列問題,我們知道,點雲是與點的序列無關的。
主要有三個策略處理序列問題:
- 點排序
- 以RNN序列輸入,採用各種各樣的交換增加序列
- 使用對稱函數聚合每個點的信息。使用對稱函數,比如+或者×這類二元函數。
在PointNet文章中,他們認爲第一種方法計算強度過大,第二種方法不夠魯棒。因爲,使用對稱函數max pooling。max pooling是一個主要的操作。
總的來說,是convolution,max pooling, dense層的靈活運用。第一次看很難明白,看代碼會容易一些,詳細代碼見Github PointNet, PointNet++。
首先,點雲的每一行可以用表示。三點的例子如下:
-38. 17. 54. 149 148 147
-38. 89. 54. 152 153 152
-79. 99. 32. 151 151 148
PointNet分類
第一個操作是2d convolution, 濾波器大小(1,6)來聚合每一個點的相關信息,輸出的維度應該是。
net = tf_util.conv2d(input_image, 64, [1,6], padding='VALID', stride=[1,1], scope='conv1')
注意,此處的padding是選用VALID。
接着,幾個1x1的卷積操作檢測每個點的小特徵。因此,我們的輸出尺寸是$ (n, 1, 1024) $.
net = tf_util.conv2d(net, 64, [1,1], padding='VALID', stride=[1,1], scope='conv2')
net = tf_util.conv2d(net, 64, [1,1], padding='VALID', stride=[1,1], scope='conv3')
net = tf_util.conv2d(net, 128, [1,1], padding='VALID', stride=[1,1], scope='conv4')
points_feat1 = tf_util.conv2d(net, 1024, [1,1], padding='VALID', stride=[1,1], scope='conv5')
最重要的步驟是,max pooling選擇所有點最突出的特徵。這就是爲什麼點順序不變性的原因。由於之前的網絡層具有1024個濾波器,將得到1024個特徵。
pc_feat1 = tf_util.max_pool2d(points_feat1, [n,1], padding='VALID', scope='maxpool1')
接着所有的特徵輸入到dense層中
pc_feat1 = tf.reshape(pc_feat1, [batch_size, -1])
pc_feat1 = tf_util.fully_connected(pc_feat1, 256, bn=True, scope='fc1')
pc_feat1 = tf_util.fully_connected(pc_feat1, 128, bn=True, scope='fc2')
需要注意的是,如果輸入的batch_size是預先不知道的,那麼tf.reshape可能會報錯,畢竟無法直接將None指定維度,建議使用tf.squeeze並指定去除維度,我使用目前有效。
再添加一層dense層,指定輸入的分類數,這就是PointNet對點雲的分類方式了。簡單總結如下:
- 聚合每一個點的信息 - conv2d, kernal size(1,6)
- 找到每個點最顯著地特徵 - max pooling
- 全連接分類 - dense
PointNet語義分割
語義分割是分類模型的後續工作。我們依然需要網絡能夠忽略點排序。所有我們將串聯每一個點的特徵,每一個點與上下文相關。
pc_feat1_reshape = tf.reshape(pc_feat1, [batch_size, 1, 1, -1])
pc_feat1_expand = tf.tile(pc_feat1_reshape, [1, num_point, 1, 1])
points_feat1_concat = tf.concat(axis=3, values=[points_feat1, pc_feat1_expand])
接着使用1x1的卷積核提取新的逐點特徵。
net = tf_util.conv2d(points_feat1_concat, 512, [1,1], padding='VALID', stride=[1,1], scope='conv6')
net = tf_util.conv2d(net, 256, [1,1], padding='VALID', stride=[1,1], scope='conv7')
最後,我們能夠做一個逐點預測,比如,每一個點13個分類
net = tf_util.conv2d(net, 13, [1,1], padding='VALID', stride=[1,1], activation_fn=None, scope='conv8')