生活随笔
收集整理的這篇文章主要介紹了
Pointnet网络结构与代码解读
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
前言
Pointnet開創性地將深度學習直接用于三維點云任務。由于點云數據的無序性,無法直接對原始點云使用卷積等操作。Pointnet提出對稱函數來解決點的無序性問題,設計了能夠進行分類和分割任務的網絡結構,本文結合源碼與個人的理解對于T-net網絡和對稱函數進行分析。
點的無序性
針對點的無序性問題實際上是文章提出了三個方案:
對于無序點集進行排序(Pointcnn)。 把點集當做序列進行處理,但是這種方法需要對輸入點集做所有的排列變換進行數據增強。 使用對稱函數,Pointnet使用的就是這種方法。
Pointnet網絡主要使用對稱函數解決點的無序性問題,對稱函數 就是指對輸入順序不敏感的函數。如加法、點乘、max pooling等操作。假設輸入特征為NxD,NN N 表示點數,DD D 表示維度數,在max pooling作用下,取出每個維度上最大值的1xD的向量,每一維特征都與其順序無關,這樣便保證了對于點云輸入順序的魯棒性。
點云的旋轉不變性
Pointnet的解決方法是學習一個變換矩陣TT T ,即T?NetT-Net T ? N e t 結構。由于loss的約束,使得TT T 矩陣訓練會學習到最有利于最終分類的變換,如把點云旋轉到正面。論文的架構中,分別在輸入數據后和第一層特征中使用了TT T 矩陣,大小為3x3和64x64。其中第二個T矩陣由于參數過多,考慮添加正則項,使其接近于正交矩陣,減少點云的信息丟失。
1. T-Net網絡結構
將輸入的點云數據作為nx3x1單通道圖像,接三次卷積和一次池化后,再reshape為1024個節點,然后接兩層全連接,網絡除最后一層外都使用了ReLU激活函數和批標準化(batch normalization)。
論文中的T-net網絡的實際結構并不復雜,我根據個人理解畫出T-net的結構。 實際訓練過程中,T 矩陣的參數初始化使用單位矩陣(np.eye(K)), 參數會隨著整個網絡的訓練進行更新,并不是提前單獨訓練的 。很多文章提到T-Net對特征進行對齊,保證了模型的對特定空間轉換的不變性,我其實不太理解這種說法。
實際上通過網絡結構看出T-net結構是一個mini的Pointnet做特征提取,是個弱監督學習設計,我理解為需要訓練一個矩陣對輸入點(或者深層特征)進行坐標變換 ,個人認為這樣的設計實際上是可以保留原始點云的部分特征,為后面的concat操作提供更多特征。源碼中在點云分類部分使用到了T?netT-net T ? n e t ,點云分割部分可以不用,對結果并沒有太大的提升,原因在于pointnet結構自身不能學到點云點的局部聯系,因此即使加入類似結構的T-net也是一樣。
models/transform_nets.py中的網絡實現
def input_transform_net ( point_cloud
, is_training
, bn_decay
= None , K
= 3 ) : """ Input: BxNx3 B=batch size;N=number of pointcloudOutput: 3x3 matrix""" batch_size
= point_cloud
. get_shape
( ) [ 0 ] . valuenum_point
= point_cloud
. get_shape
( ) [ 1 ] . valueinput_image
= tf
. expand_dims
( point_cloud
, - 1 ) net
= tf_util
. conv2d
( input_image
, 64 , [ 1 , 3 ] , 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 ] , 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_XYZ' ) as sc
: assert ( K
== 3 ) weights
= tf
. get_variable
( 'weights' , [ 256 , 3 * K
] , initializer
= tf
. constant_initializer
( 0.0 ) , dtype
= tf
. float32
) biases
= tf
. get_variable
( 'biases' , [ 3 * K
] , initializer
= tf
. constant_initializer
( 0.0 ) , dtype
= tf
. float32
) biases
+= tf
. constant
( [ 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 ] , dtype
= tf
. float32
) transform
= tf
. matmul
( net
, weights
) transform
= tf
. nn
. bias_add
( transform
, biases
) transform
= tf
. reshape
( transform
, [ batch_size
, 3 , K
] ) return transform
針對64x64的網絡設計與3x3的一樣,只是改變了K值,對于文章提到讓特征轉化矩陣接近正交化 ,這樣特征損失更小,這部分的實現是在分類任務對損失函數加入正則項 ,及添加權重reg_weight=0.001,下文分析中會標出。
2. 點云分類部分
這部分主要分析訓練代碼和點云分類模型的設計
parser
= argparse
. ArgumentParser
( )
parser
. add_argument
( '--gpu' , type = int , default
= 0 , help = 'GPU to use [default: GPU 0]' )
parser
. add_argument
( '--model' , default
= 'pointnet_cls' , help = 'Model name: pointnet_cls or pointnet_cls_basic [default: pointnet_cls]' )
parser
. add_argument
( '--log_dir' , default
= 'log' , help = 'Log dir [default: log]' )
parser
. add_argument
( '--num_point' , type = int , default
= 1024 , help = 'Point Number [256/512/1024/2048] [default: 1024]' )
parser
. add_argument
( '--max_epoch' , type = int , default
= 250 , help = 'Epoch to run [default: 250]' )
parser
. add_argument
( '--batch_size' , type = int , default
= 32 , help = 'Batch Size during training [default: 32]' )
parser
. add_argument
( '--learning_rate' , type = float , default
= 0.001 , help = 'Initial learning rate [default: 0.001]' )
parser
. add_argument
( '--momentum' , type = float , default
= 0.9 , help = 'Initial learning rate [default: 0.9]' )
parser
. add_argument
( '--optimizer' , default
= 'adam' , help = 'adam or momentum [default: adam]' )
parser
. add_argument
( '--decay_step' , type = int , default
= 200000 , help = 'Decay step for lr decay [default: 200000]' )
parser
. add_argument
( '--decay_rate' , type = float , default
= 0.7 , help = 'Decay rate for lr decay [default: 0.8]' )
FLAGS
= parser
. parse_args
( )
BATCH_SIZE
= FLAGS
. batch_size
NUM_POINT
= FLAGS
. num_point
MAX_EPOCH
= FLAGS
. max_epoch
BASE_LEARNING_RATE
= FLAGS
. learning_rate
GPU_INDEX
= FLAGS
. gpu
MOMENTUM
= FLAGS
. momentum
OPTIMIZER
= FLAGS
. optimizer
DECAY_STEP
= FLAGS
. decay_step
DECAY_RATE
= FLAGS
. decay_rate
pred
, end_points
= MODEL
. get_model
( pointclouds_pl
, is_training_pl
, bn_decay
= bn_decay
)
原始點云nx3與T-Net訓練后得到的3x3旋轉矩陣相乘后,可以理解為變換為一組新的坐標下的點云數據。
models/pointnet_cls.py 中的代碼
with tf
. variable_scope
( 'transform_net1' ) as sc
: transform
= input_transform_net
( point_cloud
, is_training
, bn_decay
, K
= 3 )
point_cloud_transformed
= tf
. matmul
( point_cloud
, transform
)
input_image
= tf
. expand_dims
( point_cloud_transformed
, - 1 )
net
= tf_util
. conv2d
( input_image
, 64 , [ 1 , 3 ] , padding
= 'VALID' , stride
= [ 1 , 1 ] , bn
= True , is_training
= is_training
, scope
= 'conv1' , bn_decay
= bn_decay
)
net
= tf_util
. conv2d
( net
, 64 , [ 1 , 1 ] , padding
= 'VALID' , stride
= [ 1 , 1 ] , bn
= True , is_training
= is_training
, scope
= 'conv2' , bn_decay
= bn_decay
)
with tf
. variable_scope
( 'transform_net2' ) as sc
: transform
= feature_transform_net
( net
, is_training
, bn_decay
, K
= 64 )
end_points
[ 'transform' ] = transform
net_transformed
= tf
. matmul
( tf
. squeeze
( net
, axis
= [ 2 ] ) , transform
)
pointnet_cls
. py
net_transformed
= tf
. expand_dims
( net_transformed
, [ 2 ] )
net
= tf_util
. conv2d
( net_transformed
, 64 , [ 1 , 1 ] , padding
= 'VALID' , stride
= [ 1 , 1 ] , bn
= True , is_training
= is_training
, scope
= 'conv3' , bn_decay
= bn_decay
)
net
= tf_util
. conv2d
( net
, 128 , [ 1 , 1 ] , padding
= 'VALID' , stride
= [ 1 , 1 ] , bn
= True , is_training
= is_training
, scope
= 'conv4' , bn_decay
= bn_decay
)
net
= tf_util
. conv2d
( net
, 1024 , [ 1 , 1 ] , padding
= 'VALID' , stride
= [ 1 , 1 ] , bn
= True , is_training
= is_training
, scope
= 'conv5' , bn_decay
= bn_decay
)
net
= tf_util
. max_pool2d
( net
, [ num_point
, 1 ] , padding
= 'VALID' , scope
= 'maxpool' )
net
= tf
. reshape
( net
, [ batch_size
, - 1 ] )
net
= tf_util
. fully_connected
( net
, 512 , bn
= True , is_training
= is_training
, scope
= 'fc1' , bn_decay
= bn_decay
)
net
= tf_util
. dropout
( net
, keep_prob
= 0.7 , is_training
= is_training
, scope
= 'dp1' )
net
= tf_util
. fully_connected
( net
, 256 , bn
= True , is_training
= is_training
, scope
= 'fc2' , bn_decay
= bn_decay
)
net
= tf_util
. dropout
( net
, keep_prob
= 0.7 , is_training
= is_training
, scope
= 'dp2' )
net
= tf_util
. fully_connected
( net
, 40 , activation_fn
= None , scope
= 'fc3' )
return net
, end_points
def get_loss ( pred
, label
, end_points
, reg_weight
= 0.001 ) : """ 預測值pred: B*NUM_CLASSES,標簽值label: B, """ loss
= tf
. nn
. sparse_softmax_cross_entropy_with_logits
( logits
= pred
, labels
= label
) classify_loss
= tf
. reduce_mean
( loss
) tf
. summary
. scalar
( 'classify loss' , classify_loss
) transform
= end_points
[ 'transform' ] K
= transform
. get_shape
( ) [ 1 ] . valuemat_diff
= tf
. matmul
( transform
, tf
. transpose
( transform
, perm
= [ 0 , 2 , 1 ] ) ) mat_diff
-= tf
. constant
( np
. eye
( K
) , dtype
= tf
. float32
) mat_diff_loss
= tf
. nn
. l2_loss
( mat_diff
) tf
. summary
. scalar
( 'mat loss' , mat_diff_loss
) return classify_loss
+ mat_diff_loss
* reg_weight
對于pointnet_cls_basic.py 沒有使用T-net的點云分類,網絡結構更容易理解,比加入T-net的結構性能略低。
3. 點云分割部分
針對與分割物體上的問題與分類任務不同,分類任務中特征經過max pooling得到一維特征向量,它包含了全局信息,再經過全連接網絡,得到1*K的k個類別預測得分即為分類結果。而分割任務中,需要對每一個點輸出所屬類別,使用類似二維圖像分割的上采樣過程(跳步連接skip-links)。Pointnet針對分割任務也使用了類似圖像分割任務的,高層全局信息與底層局部特征結合的思想。
針對Pointnet論文作者提供的版本(Tensorflow)的源碼如下:https://github.com/charlesq34/pointnet
對于pointnet源碼其余部分的介紹不詳細展開,根據個人理解將源碼的結構與功能設計展示如下: 分割部分的代碼實現主要在part_seg/(部件分割)和sem_seg/(場景分割)下。其中part_seg中底層局部特征與高層全局特征的連接**(concat)使用到了各層特征**。 sem_seg/model.py場景分割中需要注意論文使用的S3DIS數據維度不再是3維而是更高的9維度(XYZ+RGB+相對于房間的標準化后的位置信息),針對特征連接部分使用高層全局特征(B*1024)接全連接降維到128,然后與高維特征自身做concat ,不是采用論文中提到的方式。
而論文中提到的分割結構實際是在models/pointnet_seg.py中實現,即max pooling后的1D特征向量,使用tf.tile()復制n份(n個特征點),與之前網絡得到的 n * 64特征矩陣分別concat。得到一個n(64+D)的特征矩陣,再經一系列的特征變換操作,得到每個點的分類結果。
結語
本文主要結合代碼層面總結了pointnet網絡的分類和分割任務的實現。主要是理解pointnet是如何做到直接從原始點云數據中提取高維特征,并且解決好點云的特性。實際上基于pointnet結構可以進行很多任務,比如點云配準,物體檢測,3D重建,法向量估計等,只需要根據具體任務合理修改網絡后幾層的結構,利用好網絡提取的高維特征。
針對pointnet存在的點與點之間相關性的缺失,在pointnet++中使用局部采樣+分組+pointnet的結構進行解決,并考慮到了點云的稀疏性解決方案,之后很多深度學習的研究在此基礎上展開,習慣上稱為pointnet家族(point-wise MLP),比如Frustum,flowNet 3D,LSAnet,PAT等等。個人認為更高的準確度需要點云等3D數據與圖像結合進行深度學習訓練,將圖像的高分辨率優勢借鑒進來會有更好的效果。
源碼地址: 1.原論文實現代碼 https://github.com/charlesq34/pointnet 2.基于pytorch實現: https://github.com/fxia22/pointnet.pytorch https://github.com/yanx27/Pointnet_Pointnet2_pytorch
放上自己在谷歌的Colab上的gpu實現:在Colab上實現分類和Part_seg,選擇GPU版本的Notebook, 掛載好自己的谷歌云盤(方便保存和加載訓練數據),batch_size設置為32,數據集使用別人共享的Shapenet的數據集。 對于語義分割部分colab上的免費gpu滿足不了,需要購買更高版本的配置。
總結
以上是生活随笔 為你收集整理的Pointnet网络结构与代码解读 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。