今天要聊的論文是斯坦福大學Charles等人在CVPR2017上發表的論文,提出了一種直接處理點雲的深度學習網絡——PointNet。這篇論文具有里程碑意義,標誌着點雲處理進入一個新的階段。爲什麼會給這麼高的評價呢?
因爲在PointNet之前,點雲沒辦法直接處理,因爲點雲是三維的、無序的,別說深度神經網絡了,就是普通算法很多都不能奏效。於是人們想出來各種辦法,比如把點雲拍扁成圖片(MVCNN),比如把點雲劃分成體素(類似遊戲“我的世界”裏的場景),再比如把點雲劃分成節點然後按順序拉直(O-CNN)等等。總之,點雲先要被處理成“非點雲”。
這些想法怎麼樣呢?其實也挺不錯的,也能取得不錯的結果。比如MVCNN的有些指標就不輸PointNet。
這時候我們就會覺得,冥冥中一定會有一種網絡出現,擺脫上面這些操作。於是,PointNet出現了,從此點雲處理領域分成“前PointNet時代”和“後PointNet時代”。接着,各種直接處理點雲的網絡也紛紛出現,如PointCNN、SO-Net,效果也是越來越好。
下面進入正題,看看PointNet有哪些創新點。
1、針對點雲無序性——採用Maxpooling作爲對稱函數。最大池化操作就是對所有成員進行比較,把最大的留下來,其餘捨棄掉,所以,不管順序如何變化,最大值是不會改變的。
對稱函數是什麼意思?例如加法就是對稱函數,1+2+3+4=10,換個順序,2+4+3+1=10,不管順序如何變化,對結果不會產生影響。
2、針對剛體變化——對齊網絡T-net。T-net對性能的提升作用也還是有的,兩個T-net加上regularization 貢獻了2.1個百分點,但奇怪的是在PointNet++的代碼中,已經看不到T-net了(這一點論文沒有提及,github上也有人提問,但是作者沒有回覆)。
3、特徵提取階段採用MLP(多層感知機,說白了就是全連接層),這種結構用到的運算只有乘法和加法,都是對稱函數,所以不會受到排序影響。
實驗效果
1、分類
數據集是ModelNet40,包含40類物體的CAD,通過採樣獲得點雲。這裏作者沒有把MVCNN列出來,因爲精度沒有比過它,不過後來的改進版已經超過了。
2、物體分割:
3、參數量與MVCNN的對比:明顯佔優。
PointNet與PointCNN從文章到代碼都有很多相似之處,兩者對比看待,或許更有助於我們理解。
衆所周知,PointNet中使用了maxpooling和T-net,作者文章中起到關鍵作用的是maxpooling,而T-net對性能的提升作用也還是有的(兩個T-net加上regularization 貢獻了2.1個百分點),但奇怪的是在PointNet++的代碼中,已經看不到T-net了(這一點論文沒有提及,github上也有人提問,但是作者沒有回覆)。
但是,與之相似的PointCNN中有個X變換矩陣,但X變換對於PointCNN的作用可就非常重要了,因爲它連maxpooling都沒有用。下面我們就對兩者進行比較。
首先是PointNet中的T-net代碼:
- def feature_transform_net(inputs, is_training, bn_decay=None, K=64):
- """ Feature Transform Net, input is BxNx1xK
- Return:
- Transformation matrix of size KxK """
- batch_size = inputs.get_shape()[0].value
- num_point = inputs.get_shape()[1].value
- net = tf_util.conv2d(inputs, 64, [1,1],
- padding='VALID', stride=[1,1],
- bn=True, is_training=is_training,
- scope='tconv1', bn_decay=bn_decay)
- net = tf_util.conv2d(net, 128, [1,1],
- padding='VALID', stride=[1,1],
- bn=True, is_training=is_training,
- scope='tconv2', bn_decay=bn_decay)
- net = tf_util.conv2d(net, 1024, [1,1],
- padding='VALID', stride=[1,1],
- bn=True, is_training=is_training,
- scope='tconv3', bn_decay=bn_decay)
- net = tf_util.max_pool2d(net, [num_point,1],#池化窗口是[num_point,1]
- padding='VALID', scope='tmaxpool')
- net = tf.reshape(net, [batch_size, -1])#變成兩維
- net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training,
- scope='tfc1', bn_decay=bn_decay)
- net = tf_util.fully_connected(net, 256, bn=True, is_training=is_training,
- scope='tfc2', bn_decay=bn_decay)
- with tf.variable_scope('transform_feat') as sc:
- weights = tf.get_variable('weights', [256, K*K],
- initializer=tf.constant_initializer(0.0),
- dtype=tf.float32)
- biases = tf.get_variable('biases', [K*K],
- initializer=tf.constant_initializer(0.0),
- dtype=tf.float32)
- biases += tf.constant(np.eye(K).flatten(), dtype=tf.float32)
- transform = tf.matmul(net, weights)
- transform = tf.nn.bias_add(transform, biases)
- transform = tf.reshape(transform, [batch_size, K, K])
- return transform
接下來看PointCNN的X變換:
- ######################## X-transformation #########################
- X_0 = pf.conv2d(nn_pts_local_bn, K * K, tag + 'X_0', is_training, (1, K), with_bn=False)
- #kernal size(1, K, 3), kernal num=K*K, so the output size is (N, P, 1, K*K). so this operator is in the neighbor point dimentional.
- X_1 = pf.dense(X_0, K * K, tag + 'X_1', is_training, with_bn=False)#in the center point dimensional ,P decrease to 1.
- X_2 = pf.dense(X_1, K * K, tag + 'X_2', is_training, with_bn=False, activation=None)#(N, P, 1, K*K)
- X = tf.reshape(X_2, (N, P, K, K), name=tag + 'X')
- fts_X = tf.matmul(X, nn_fts_input, name=tag + 'fts_X')
- ###################################################################
從體量和複雜程度上來看,後者勝出。
從作用效果來看,不太好評價。因爲PointCNN是有局部特徵的,這點和pointnet++思想一致。所以即便PointCNN性能超過了PointNet,也不能直接證明X-transporm就一定優於T-net了。
代碼方面,其實T-net的前四層和X變換的第一層做的事情差不多,都是爲了把多個點的特徵融合到一組特徵,爲訓練變換矩陣提供素材。但接下來就不同了,T-net只有一組K*K的weights權值,而PointCNN後面跟了兩個dense層,維度都是K*K的,參數更多,因此猜測PointCNN訓練變換矩陣應該會更加充分。後期可以通過實驗驗證以下。
最後歪個樓,我在測試PointNet++的代碼時,ModelNet40的分類結果一直徘徊在90.1左右,達不到論文裏提的90.7,跟作者郵件聯繫也沒有得到很好的答案。所以我再想是不是作者本來的PointNet++代碼裏是有T-net的,但是放到github裏的版本沒加上。但這只是猜測,有待驗證。
附上相關代碼的鏈接:
PointNet2:https://github.com/charlesq34/pointnet2