一、点云数据
点云数据是由大量的离散点组成的三维空间中的数据形式,例如激光雷达扫描的地形、建筑物或者是摄像机捕捉的物体等三维场景都可以转换为点云数据。点云数据的稀疏性、不规则性、本质上是无序的,这样的特点造成传统的图像处理和计算机视觉算法难以直接运用于点云数据的处理分析。
二、PointNet网络结构
为了克服点云数据的这些缺陷,作者设计了一种新型的网络结构——PointNet。PointNet网络结构可以接收任意数量的点云数据,输出对象的分类或者对点云进行分割等处理。PointNet主要分为三个模块,分别为输入层、特征提取层以及输出层。
输入层:PointNet网络的输入层直接接收点云数据,不需要将点云数据转换成体素或者多视角图像这样的预处理形式。输入层将点云数据映射到高维空间,并且为每个点生成唯一的坐标,同时把每个点看成了输入向量。
特征提取层:输入层映射到高维空间后,PointNet网络首先通过一系列变换网络,对每个点进行空间上的局部特征提取,然后通过最大池化的方式将局部特征压缩成全局特征。通过这样的处理方式,PointNet可以识别不同姿态、物体大小的点云数据,并能够对旋转、平移、缩放不变。
输出层:输出层采用多层感知器(Multi-Layer Perceptron, MLP)来对全局特征进行分类或分割。也就是说,PointNet不仅可以进行对象的分类,还可以实现点云的分割,可以识别不同部位相互作用的目标。
三、代码示例
def input_transform_net(point_cloud, is_training, reuse=None): with tf.variable_scope('input_transform_net', reuse=reuse): input_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='conv1', bn_decay=bn_decay) net = tf_util.conv2d(net, 128, [1, 1], padding='VALID', stride=[1, 1], bn=True, is_training=is_training, scope='conv2', bn_decay=bn_decay) net = tf_util.conv2d(net, 1024, [1, 1], padding='VALID', stride=[1, 1], bn=True, is_training=is_training, scope='conv3', 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.fully_connected(net, 256, bn=True, is_training=is_training, scope='fc2', bn_decay=bn_decay) with tf.variable_scope('transform_XYZ'): assert xyz_shape[1:] == [num_points, 3] input_transform = tf_util.fully_connected(net, 3 * 3, weights_regularizer=tf.compat.v1.layers.l2_regularizer(0.0), activation_fn=None, scope='fc3') # 上述第2维应该是3*num_points而不是3,参见这个Tensorflow issue:https://github.com/tensorflow/tensorflow/issues/17947 input_transform = tf.reshape(input_transform, [batch_size, 3, 3]) # 把三个维度的下标混淆为[1, 0, 2],作用参见paper Figure 2右下方。 with tf.variable_scope('transform_XYZ'): spatial_transformed_input = tf.matmul(xyz, tf.transpose(input_transform, [0, 2, 1])) # 下面输出的两个变量可以作为中间结果查看。 # return spatial_transformed_input, input_transform return spatial_transformed_input def feature_transform_net(inputs, is_training, reuse=None): with tf.variable_scope('feature_transform_net', reuse=reuse): # 上述point_cloud_shape的形状为[batch_size, num_points, 3] # 计算出PointNet的local feature的channels数目 # 看这个issue:https://github.com/charlesq34/pointnet/issues/19 batch_size, num_points, num_dims = inputs.get_shape().as_list() net = tf.expand_dims(inputs, 2) net = tf_util.conv2d(net, 64, [1, 1], padding='VALID', stride=[1, 1], bn=True, is_training=is_training, scope='conv1', bn_decay=bn_decay) net = tf_util.conv2d(net, 128, [1, 1], padding='VALID', stride=[1, 1], bn=True, is_training=is_training, scope='conv2', bn_decay=bn_decay) net = tf_util.conv2d(net, 1024, [1, 1], padding='VALID', stride=[1, 1], bn=True, is_training=is_training, scope='conv3', 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.fully_connected(net, 256, bn=True, is_training=is_training, scope='fc2', bn_decay=bn_decay) with tf.variable_scope('transform_feat'): # debug时,把这个命名空间改为'transform_XYZ',主要是为了便于trace。 net = tf_util.fully_connected(net, num_dims * num_dims, weights_regularizer=tf.compat.v1.layers.l2_regularizer(0.0), activation_fn=None, scope='fc3') # 把一维的向量转换为num_dims*num_dims矩阵,(想象一个特征是num_dims维,现在16个特征,那么就是一个16x16的矩阵) # 维度是(batch_size, num_dims*num_dims) net = tf.reshape(net, [batch_size, num_dims, num_dims]) return net
四、PointNet的应用领域
PointNet网络不仅在目标检测、语义分割以及网格分割等传统计算领域有着重要的应用,还在自动驾驶、无人飞行器和虚拟现实等领域有着广泛的应用前景。例如在自动驾驶中,PointNet网络可以通过点云数据对车道标线、行人、车辆等进行检测,并进行追踪。同样,在无人飞行器的管理与控制中,使用PointNet网络可以对三维环境以及物体进行实时的检测和跟踪。