pointnet分割自己的点云数据_细嚼慢咽读论文:PointNet论文及代码详细解析
論文標題:PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation
標簽:有監督 | 特征學習、點云分類、語義分割
首先回答3個問題作為引子:
Q1:什么是點云?
簡單來說就是一堆三維點的集合,必須包括各個點的三維坐標信息,其他信息比如各個點的法向量、顏色等均是可選。
點云的文件格式可以有很多種,包括xyz,npy,ply,obj,off等(有些是mesh不過問題不大,因為mesh可以通過泊松采樣等方式轉化成點云)。對于單個點云,如果你使用np.loadtxt得到的實際上就是一個維度為
的張量,num_channels一般為3,表示點云的三維坐標。這里以horse.xyz文件為例,實際就是文本文件,打開后數據長這樣(局部,總共有2048個點):
實際就是一堆點的信息,這里只有三維坐標,將其可視化出來長這樣:
Q2:為什么點云處理任務是重要的?
三維圖形具有多種表現形式,包括了mesh、體素、點云等,甚至還有些方法使用多視圖來對三維圖形表征。而點云在以上各種形式的數據中算是日常生活中最能夠大規模獲取和使用的數據結構了,包括自動駕駛、增強現實等在內的應用需要直接或間接從點云中提取信息,點云處理也逐漸成為計算機視覺非常重要的一部分。
Q3:為什么PointNet是重要的?
這個后面會說,直接對點云使用深度學習、解決了點云帶來的一系列挑戰,PointNet應該是開創性的。但我覺得,真正讓PointNet具備很大影響力的,還是它的簡潔、高效和強大。
首先要說清楚,PointNet所作的事情就是對點云做特征學習,并將學習到的特征去做不同的應用:分類(shape-wise feature)、分割(point-wise feature)等。
PointNet之所以影響力巨大,就是因為它為點云處理提供了一個簡單、高效、強大的特征提取器(encoder),幾乎可以應用到點云處理的各個應用中,其地位類似于圖像領域的AlexNet。
1 motivation
related work
why we want to do this?
點云或者mesh,大多數研究人員都是將其轉化成3D體素或者多視圖來做特征學習的,這其中的工作包括了VoxelNet, MVCNN等。這些工作都或多或少存在了一些問題(上面提到了)。
直接對點云做特征學習也不是不可以,但有幾個問題需要考慮:特征學習需要對點云中各個點的排列保持不變性、特征學習需要對rigid transformation保持不變性等。雖然有挑戰,但是深度學習強大的表征能力以及其在圖像領域取得的巨大成功,因此是很有必要直接在點云上進行嘗試的。
2 contribution
3 solution
challenges
點云的幾個特點:
forward propagation
PointNet網絡結構圖網絡分成了分類網絡和分割網絡2個部分,大體思路類似,都是設計表征的過程
分類網絡設計global feature,分割網絡設計point-wise feature
兩者都是為了讓表征盡可能discriminative,也就是同類的能分到一類,不同類的距離能拉開
PointNet設計思路主要有以下3點:
1 Symmetry Function for Unordered Input:
要做到對點云點排列不變性有幾種思路:
為什么不這樣做?(摘自原文)in high dimensional space there in fact does not exist an ordering that is stable w.r.t. point perturbations in the general sense.簡單來說就是很難找到一種穩定的排序方法
2. 作為序列去訓練一個RNN,即使這個序列是隨機排布的,RNN也有能力學習到排布不變性。
為什么不這樣做?(摘自原文)While RNN has relatively good robustness to input ordering for sequences with small length (dozens), it’s hard to scale to thousands of input elements, which is the common size for point sets. RNN很難處理好成千上萬長度的這種輸入元素(比如點云)。
3. 使用一個簡單的對稱函數去聚集每個點的信息
我們的目標:左邊
是我們的目標,右邊 是我們期望設計的對稱函數。由上公式可以看出,基本思路就是對各個元素(即點云中的各個點)使用 分別處理,在送入對稱函數 中處理,以實現排列不變性。在實現中
就是MLP, 就是max pooling對于以上三種不同的做法,作者均作了實驗來驗證,得出第三種方法效果最好:
2 Local and Global Information Aggregation:
對于分割任務,我們需要point-wise feature
因此分割網絡和分類網絡設計局部略有不同,分割網絡添加了每個點的local和global特征的拼接過程,以此得到同時對局部信息和全局信息感知的point-wise特征,提升表征效果。
3 alignment network:
用于實現網絡對于仿射變換、剛體變換等變換的無關性
直接的思路:將所有的輸入點集對齊到一個統一的點集空間
pn的做法:直接預測一個變換矩陣(3*3)來處理輸入點的坐標。因為會有數據增強的操作存在,這樣做可以在一定程度上保證網絡可以學習到變換無關性。
特征空間的對齊也可以這么做,但是需要注意:
transformation matrix in the feature space has much higher dimension than the spatial transform matrix, which greatly increases the difficulty of optimization. 對于特征空間的alignment network,由于特征空間維度比較高,因此直接生成的alignment matrix會維度特別大,不好優化,因此這里需要加個loss約束一下。
add a regularization term to our softmax training loss:
使得特征空間的變換矩陣A盡可能接近正交矩陣
消融實驗驗證alignment network的效果:
loss
看代碼:分類中常用的交叉熵+alignment network中用于約束生成的alignment matrix的loss
4 dataset and experiments
evaluate metric
分類:分類準確率acc
分割:mIoU
dataset
分類:ModelNet40
分割:ShapeNet Part dataset和Stanford 3D semantic parsing dataset
experiments
分類:
局部分割:
5 code
看代碼分析PointNet結構:
觀察上圖,有4個值得關注的點:
1. 如何對點云使用MLP?
2. alignment network怎么做的?
3. 對稱函數如何實現來提取global feature的?
4. loss?
針對問題1:
以分類網絡為例,整體代碼:
def get_model(point_cloud, is_training, bn_decay=None):""" Classification PointNet, input is BxNx3, output Bx40 """batch_size = point_cloud.get_shape()[0].valuenum_point = point_cloud.get_shape()[1].valueend_points = {}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'] = transformnet_transformed = tf.matmul(tf.squeeze(net, axis=[2]), transform)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)# Symmetric function: max poolingnet = 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_pointsMLP的核心做法:
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)這里input_image維度是
,因此將點云看成是W和H分為N和3的2D圖像,維度是然后直接基于這個“2D圖像”做卷積,第一個卷積核size是
,正好對應的就是“2D圖像”的一行,也就是一個點(三維坐標),輸出通道數是64,因此輸出張量維度應該是第二個卷積核size是
, 卷積只改變通道數,輸出張量維度是conv2d就是將卷積封裝了一下,核心部分也就是調用tf.nn.conv2d,實現如下:
def conv2d(inputs,num_output_channels,kernel_size,scope,stride=[1, 1],padding='SAME',use_xavier=True,stddev=1e-3,weight_decay=0.0,activation_fn=tf.nn.relu,bn=False,bn_decay=None,is_training=None):""" 2D convolution with non-linear operation.Args:inputs: 4-D tensor variable BxHxWxCnum_output_channels: intkernel_size: a list of 2 intsscope: stringstride: a list of 2 intspadding: 'SAME' or 'VALID'use_xavier: bool, use xavier_initializer if truestddev: float, stddev for truncated_normal initweight_decay: floatactivation_fn: functionbn: bool, whether to use batch normbn_decay: float or float tensor variable in [0,1]is_training: bool Tensor variableReturns:Variable tensor"""with tf.variable_scope(scope) as sc:kernel_h, kernel_w = kernel_sizenum_in_channels = inputs.get_shape()[-1].valuekernel_shape = [kernel_h, kernel_w,num_in_channels, num_output_channels]kernel = _variable_with_weight_decay('weights',shape=kernel_shape,use_xavier=use_xavier,stddev=stddev,wd=weight_decay)stride_h, stride_w = strideoutputs = tf.nn.conv2d(inputs, kernel,[1, stride_h, stride_w, 1],padding=padding)biases = _variable_on_cpu('biases', [num_output_channels],tf.constant_initializer(0.0))outputs = tf.nn.bias_add(outputs, biases)if bn:outputs = batch_norm_for_conv2d(outputs, is_training,bn_decay=bn_decay, scope='bn')if activation_fn is not None:outputs = activation_fn(outputs)return outputs針對問題2:
這里以input_transform_net為例:
def input_transform_net(point_cloud, is_training, bn_decay=None, K=3):""" Input (XYZ) Transform Net, input is BxNx3 gray imageReturn:Transformation matrix of size 3xK """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實際上,前半部分就是通過卷積和max_pooling對batch內各個點云提取global feature,再將global feature降到
維度,并reshape成 ,得到transform matrix通過數據增強豐富訓練數據集,網絡確實應該學習到有效的transform matrix,用來實現transformation invariance
針對問題3:
max_pooling,這個在論文的圖中還是代碼中都有體現,代碼甚至直接用注釋注明了
針對問題4:
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)# Enforce the transformation as orthogonal matrixtransform = end_points['transform'] # BxKxKK = 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無論是分類還是分割,本質上都還是分類任務,只是粒度不同罷了。
因此loss一定有有監督分類任務中常用的交叉熵loss
另外loss還有之前alignment network中提到的約束loss,也就是上面的mat_diff_loss
5 conclusion
PointNet之所以影響力巨大,并不僅僅是因為它是第一篇,更重要的是它的網絡很簡潔(簡潔中蘊含了大量的工作來探尋出簡潔這條路)卻非常的work,這也就使得它能夠成為一個工具,一個為點云表征的encoder工具,應用到更廣闊的點云處理任務中。
MLP+max pooling竟然就擊敗了眾多SOTA,令人驚訝。另外PointNet在眾多細節設計也都進行了理論分析和消融實驗驗證,保證了嚴謹性,這也為PointNet后面能夠大規模被應用提供了支持。
總結
以上是生活随笔為你收集整理的pointnet分割自己的点云数据_细嚼慢咽读论文:PointNet论文及代码详细解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mobi格式电子书_进阶能力 | 了解常
- 下一篇: oracle data guard闪回,