3D计算机视觉简介

随着AR / VR,自动驾驶汽车的发展,3D视觉问题变得越来越重要,因为它提供了比2D更丰富的信息。我将介绍两种基本的3D场景分析深度学习模型,VoxNet和PointNet。

3D格式简介

3D图像测量更多维度,即深度维度。有两种最广泛使用的3D格式,即RGB-D和点云(point cloud)。对于正常的基于像素的图像,我们可以通过(x,y)坐标定位任何像素,然后我们可以分别获得三个属性(R,G,B)。而在RGB-D图像中,每个(x,y)坐标将对应于四个属性(depth,R,G,B)。RGB-D和点云之间唯一的区别在于,在点云中,(x,y)坐标反映了真实世界中的实际值,而不是简单的整数值。

3D计算机视觉简介

Sample point cloud

此外,如果您有一个RGB-D扫描,并且还了解您的扫描相机的内部特性,您将能够通过使用camera intrinsic简单地计算真实世界(x, y),从RGB-D图像创建点云。这叫做相机标定(camera intrinsics)。因此,到目前为止,您应该知道RGB-D图像是网格对齐的图像,而点云的结构更为稀疏。

3D Vision

就像二维问题一样,我们想要检测和识别3D扫描中的所有对象。与2D图像不同,输入数据以使用CNNs的最佳方式是什么?

Voxel Grid

Voxel Grid是最直观的方法,它将3D对象嵌入到网格中,使其看起来像像素图像,而我们在这里称之为Voxel(体素) 。在这种情况下,3D图像用(x, y, z)坐标来描述,就像乐高一样。

3D计算机视觉简介

VoxNet架构

VoxNet是一种基于深度学习的架构,用于使用占用网格对三维点云进行分类,这在分类问题上非常有效。

例如,如果我们将点云拟合到32x32x32 Voxel Grid,我们可以构建一个填充零的32x32x32数组。然后缩放点云以计算每个小Voxel内有多少个点。

获得体素网格后,我们将接下来执行3D卷积,这有效地将立方体滑过基于体素的图像。

我们正在将我们的扫描扩展到相同的基础上。我们可以想象,如果我们有一个大规模的扫描,每个对象将只是一个会导致一些问题的体素。而且,由于这些上采样操作,要确定每个体素的RGB颜色并不容易。

对于简单的数据集(具有相似的点数,类似的扫描规模),VoxNet可能是一种简单而好的方法。但是如果遇到一个复杂的数据集,它可能不是一个好的选择。

Points

虽然基于体素的方法在分类问题上可以很好地工作,但它为上采样行为牺牲了大量信息。因此,我们可能想要明智地训练我们的网络。

第一个问题是点的顺序,我们知道点云对点的顺序是不变的。处理这个问题有三种策略(PointNet):

  • 对点进行排序。
  • 作为RNN序列输入,通过用各种排列来扩充序列。
  • 使用一个对称函数来聚合每个点的信息。说到对称函数,我们指的是像+或*这样的对称二元函数。

在PointNet的论文中,他们说第一种方法是位计算强度,第二种方法不够健壮。因此,使用了一个对称函数max pooling。最大池是这里的主要操作。

整个体系结构如下所示,

3D计算机视觉简介

PointNet的体系结构 mlp代表多层感知器

通常,它是灵活使用卷积,全连接和最大池化层。我发现首先可能很难理解,所以让我们通过代码来看看它。

首先,我将给出一个示例点云,代表每行的(x,y,z,r,g,b)。每一行代表这里的一点。假设我们在这个例子中有n点。

...

-38. 17. 54. 149 148 147

-38. 89. 54. 152 153 152

-79. 99. 32. 151 151 148

...

注意:PointNet的实际输入使用标准化的RGB颜色,并且x,y值的基点在空间中心对齐。我没有这样做,只是为了不弄乱浮点数。

PointNet中的分类

第一个操作是进行2d卷积,内核大小(1,6)将一个点的相关信息(x,y,z,r,g,b;共6个)聚合在一起。这里的输出应该是(n,1,64)。

net = tf_util.conv2d(input_image, 64, [1,6], padding='VALID', stride=[1,1], scope='conv1')

注意:每层都有批量标准化,我将其删除以简化演示。另外,原始的pointnet使用9个特征作为输入(x,y,z,r,g,b,normalized_x,normalized_y,normalized_z)。

然后会有几个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')

接下来最重要的步骤是,最大池选择所有点中最特别的特性。这就是为什么函数是不变量的原因。由于我们在以前的层中有1024个过滤器,所以这个层将输出1024个特性。

pc_feat1 = tf_util.max_pool2d(points_feat1, [n,1], padding='VALID', scope='maxpool1')

然后所有功能都将通过全连接层完全连接。

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')

注意:在我们的例子中,批大小是1。而PointNet的输入是对场景的扫描,将被分成小批(每批4096个点)。PointNet将执行特性检测批处理

到目前为止,您可以回到上面的图中,如果您添加一个全连接层来输出类标签的数量,这就是PointNet在点云上进行分类的方式。简单地说,

  • 要聚合每个点信息,
  • 为了找到每个点最特别的特征,
  • 然后fully-connect分类。

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')

相关推荐