推广 热搜: page  音视频  使用  个数  搜索引擎  选择  企业  可以  父亲  百度 

3D点云语义分割篇——PointNet

   日期:2025-01-01     作者:3w6dw    caijiyuan   评论:0    移动:http://ww.kub2b.com/mobile/news/18119.html
核心提示:PointNet: Deep Learning on Point Sets for 3D Classifification and SegmentationCharles R. Qi*         Hao Su*   

PointNet: Deep Learning on Point Sets for 3D Classifification and Segmentation

Charles R. Qi*         Hao Su*          Kaichun Mo        Leonidas J. Guibas
Stanford University

       随着深度学习在二维图像处理及应用的逐渐成熟,对于三维点云也希望能利用强大的深度学习去解决诸如:分类、识别、分割、补全、配准等问题,这篇2017年发表的文章可以算是将深度学习直接应用于散乱、无序的三维点云的开山鼻祖。网络结构简单而有效(当然有效只是相对的,作者在ModelNet40上验证了分类任务,在ShapeNet上验证了部件分割,在S3DIS上验证了语义分割,本文主要针对语义分割做介绍原理+代码)。

直接看网络结构

 网络的核心思想就两点

1.直接使用多层感知机(MLP)。比如第一个mlp(64,64)就是指有两层网络,神经元的个数分别为64,64。为什么不用卷积,作者是这样解释的:传统的卷积框架为了共享权重和优化,需要输入的数据有很规整的形式,但是点云是无序的,一般需要变成体素网格或者经过一系列规整化的处理,但是这样无疑引入了太多的人为干预,可能对数据造成破坏。

2.最大池化(MaxPooling)。充分考虑到点云的无序性,对称性和空间变换不变性,所以提出了最大池化。具体来说文中对于像点云这种无序输入提出了三种应对策略:1)直接排序;2)把点云当成序列使用RNN来做,同时做数据增强(改变序列中点的排列;3)用对称函数聚合每个点的信息,比如最大池化(对称就是无论你的输入顺序是怎么样的,最终结果都是不变的)。当然作者通过对比实验验证了最大池化的方法是最好的。

网络流程

输入整个网络的输入是固定数目n个点,每个点含有xyz三维坐标信息。

input transform 就是网络结构图中左下角的那个小模块,原文里面叫做Joint Alignment Network因为我们对点云打的标签是不会随着空间变换而改变的,所以希望网络学到的东西也是空间变换不变的,所以这个模块的目的就是估计一个仿射变换矩阵(3x3,在特征提取之前用估计得到的这个矩阵乘以输入点云(就是对输入点云做一个仿射变换,模块中T-Net也是由MLP构成的,具体结构在代码分析部分。

mlp(64,64两层感知机,每一层的神经元个数分别为64,64;shared的意思就是网络结构是固定的,所有的点都要输入这个结构然后得到对应的输出(铁打的营盘流水的兵)。

feature transform和input transform一样的思路,不过是在更高维的特征空间,估计的仿射变换也是更高维的(64x64)。由于特征空间维度更高,优化难度大,所以在计算损失函数的时候加了一项正则项,让求解出来的仿射变换矩阵接近于正交矩阵(原文说因为正交变换不会丢失输入的信息)。A就是T-Net要估计的放射变换矩阵。

mlp(64,128,1024三层感知机,每层的神经元个数分别为64,128,1024。

maxpooling至此,输入的n个点中的每一个点都有1024维特征,然后我们在n个点(点数这个维度上)选取最大值得到一个全局特征;如果是分类任务,就直接将这个特征输入到下一个mlp(512,256,k),其中k为类别数,得到对应类别的分数。对于分割任务来说,将这个全局特征复制n次,和前面第二层mlp得到的特征拼接在一起,作为Segmentation Network的输入。

Segmentation Network输入相当于是把每个点的局部特征和全局特征进行了拼接,然后经过两个MLP,最终得到维度为nxm的输出,即对于每个点进行m分类,输出它对应每一类的预测分数。

语义分割实验      
本文主要针对语义分割实验进行介绍,代码来源为GitHub(fork别人的,可以在这里访问。代码为pytorch实现,里面也包括了其他的任务,以及PointNet++的代码,直接将整个项目拉取下来即可。数据集为S3DIS,可以访问这里下载(需要科学上网,然后需要填写一些联系信息就可以进入谷歌云下载数据了)。

下载的数据文件名为Stanford3dDataset_v1.2_Aligned_Version.下载之后将其解压到data/s3dis/Stanford3dDataset_v1.2_Aligned_Version/

然后这里简单做一个数据集介绍数据集包含由Matterport scanners进行扫描的6个室内区域,总共包含271个房间,扫描的每个点都进行了语义标注,共计13个类别ceiling、floor、wall、beam、column、window、door、table、chair、sofa、bookcase、board、clutter。其实官方的描述里只有前12个类别,但是有的房间里面出现了stairs或者其他的一些东西,将其归为clutter。

解压下载的压缩文件后房间的数据为txt文件,每行代表一个点,含六个维度(XYZRGB),Anotation文件夹下包含这个房间里面含有的物体的txt文件,每个物体单独为一个txt文件,同样包含XYZRGB数据。可以用notebook打开txt文件,用列编辑模式在每行开头加上“v空格",然后把后缀名改为obj(也可以自己写一小段代码完成,代码放在文章最后,然后用meshlab直接打开obj文件

在查看了原始数据之后,由于拉取下来的文件中已经有训练好的模型文件(保存在log sem_seg pointnet_sem_segcheckpointsbest_model.pth,我们可以直接进行测试,测试之前需要对原始数据集做一些处理,把原来的数据和标签放在一个文件,每行数据为XYZRGBL(L代表label:0~12),具体操作为进入data_utils文件夹,运行collect_indoor3d_data.py文件即可,会在data下面新建一个文件夹stanford_indoor3d,在原始数据的文件夹下面生成一堆.npy文件(就是numpy格式的文件,把这些.npy文件剪切到stanford_indoor3d下面(总之就是要把生成的.npy文件放在这个文件夹下面,就可以进行测试代码的运行了。

运行测试文件

 

测试区域为5号区域,生成的obj文件会保存log/sem_seg/pointnet2_sem_seg/visual/下(包括ground truth和predict),直接用meshlab打开即可,下面是测试的结果

avg class IoU: 0.436354

avg class acc: 0.526488

whole scene point accuracy: 0.787821

用meshlab打开其中一个标签和预测结果

代码详解

按照模型文件、数据处理文件、训练文件、测试文件的顺序调试一遍代码。

模型文件modelspointnet_utils.py,modelspointnet_sem_seg.py

pointnet_utils,首先定义了STN3d、STNkd模块,也就是结构图中的这两个小网络

以STN3d为例,其实和主干网络的结构是类似的,都是由一系列mlp(用1*1的一维卷积实现,卷积核就相当于神经元之间的权重连接,数据本身为神经元节点)和全连接层组成。模块的输入是xyz三维坐标,输出是3*3或k*k的仿射变换矩阵。

 

STNkd结构基本一致,只是最后估计的仿射变换矩阵大小是k*k(default:k=64

然后定义了PointNetEncoder,结构图中的这个部分,输入是n个D维的点,每个点除了xyz外可以包含其他维度的信息;内部调用了STN和STNkd模块并设置了一个开关绝对是否使用STNkd,有三个输出,其中output1分为两种情况:用作分类时输出1*1024的global feature,用作分割时需要把global复制n次和第二个mlp得到的特征进行拼接然后再输出,作为分割网络的输入;output2为STN网络估计的3*3空间变换矩阵,output3为STNkd估计的k*k特征变换矩阵(当开关关闭时为None).

 

pointnet_utils.py文件的最后还定义了一个函数:feature_transform_reguliarzer,实现前面在网络流程部分提到的对特征空间进行对齐时,需要添加正则约束,让求解出来的矩阵接近于正交矩阵,也就是实现这个式子,A就是估计的k*k矩阵(函数输入)。模块返回此正则项,后面在计算loss的时候会用到。

 

接下来看看pointnet_sem_seg文件,描述了分割任务用到的整个网络结构。导入在上一个文件中定义好的PointNetEncoder, feature_transform_reguliarzer两个模块,定义了get_model和get_loss两个类。

 

这个文件中是写了__main__函数的,所以可以直接运行查看模型概要

 

 输出

get_model(
  (feat): PointNetEncoder(
    (stn): STN3d(
      (conv1): Conv1d(9, 64, kernel_size=(1,), stride=(1,))
      (conv2): Conv1d(64, 128, kernel_size=(1,), stride=(1,))
      (conv3): Conv1d(128, 1024, kernel_size=(1,), stride=(1,))
      (fc1): Linear(in_features=1024, out_features=512, bias=True)
      (fc2): Linear(in_features=512, out_features=256, bias=True)
      (fc3): Linear(in_features=256, out_features=9, bias=True)
      (relu): ReLU()
      (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (bn2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (bn3): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (bn4): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (bn5): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
    (conv1): Conv1d(9, 64, kernel_size=(1,), stride=(1,))
    (conv2): Conv1d(64, 128, kernel_size=(1,), stride=(1,))
    (conv3): Conv1d(128, 1024, kernel_size=(1,), stride=(1,))
    (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (bn2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (bn3): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (fstn): STNkd(
      (conv1): Conv1d(64, 64, kernel_size=(1,), stride=(1,))
      (conv2): Conv1d(64, 128, kernel_size=(1,), stride=(1,))
      (conv3): Conv1d(128, 1024, kernel_size=(1,), stride=(1,))
      (fc1): Linear(in_features=1024, out_features=512, bias=True)
      (fc2): Linear(in_features=512, out_features=256, bias=True)
      (fc3): Linear(in_features=256, out_features=4096, bias=True)
      (relu): ReLU()
      (bn1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (bn2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (bn3): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (bn4): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (bn5): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (conv1): Conv1d(1088, 512, kernel_size=(1,), stride=(1,))
  (conv2): Conv1d(512, 256, kernel_size=(1,), stride=(1,))
  (conv3): Conv1d(256, 128, kernel_size=(1,), stride=(1,))
  (conv4): Conv1d(128, 13, kernel_size=(1,), stride=(1,))
  (bn1): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (bn2): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (bn3): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)

 数据处理文件data_utilsS3DISDataLoader.py,data_utilsindoor3d_util.py,data_utils collect_indoor3d_data.py

indoor3d_util.py,collect_indoor3d_data.py这两个文件是用来对下载的原始数据进行处理,把标签和数据整合成.npy文件,文件中的每一行为XYZRGB以及label索引。indoord_util.py中定义了很多工具,有点像个工具包,同时定义了一些文件路径,collect_indoor3d_data.py使用了其中的collect_point_label这个工具。运行collect_indoor3d_data.py文件会在data/stanford_indoor3d中生成实验所需要的.npy(numpy)文件。

重点看S3DISDataLoader.py这个文件,首先定义了加载器S3DISDataset,继承torch.utils.data.Dataset,pytorch要实现自定义DataLoader至少需要实现__getitem__和__getlen__两个类方法,init方法中传入的参数还包括了测试区域(默认为5,以及block的大小(训练的时候是在1m*1m的block里面随机采集4096个点作为网络输入)。

 

这个文件里面还定义了ScannetDatasetWholeScene(),讲解测试文件的时候再看。

训练文件train_semseg.py,用到了provider.py中的rotate_point_cloud_z)做了数据增强(随机沿z轴旋转)。代码里面都是很常规的操作:设置用户参数,建立文件夹,设置超参数和参数自动调整方案,加载dataset生成dataloader,加载模型,然后train,eval,模型保存(保存最近一次模型model.pth和IoU最高的模型best_model.pth),打印信息。

测试文件test_semseg.py

由于测试的时候并不是像训练那样随机采样block,而是需要把整个场景全部输入网络,所以用到了S3DISDataLoader.py中定义的ScannetDatasetWholeScene)来制作数据。具体来说是将一个房间按给定步长网格化,然后有重叠的移动block进行点的采样,和训练的时候一样,block中的点如果不足4096,就重复采样一些点。这样在每个block内部一般都会有数个小的batch,将每个batch输入网络进行预测得到相应的预测分数进行保存,最后计算IOU,并将每个点类别信息和语义标签的颜色信息进行关联,然后一同写入文件。

S3DISDataLoader.py

 

test_semseg.py

 

模型继续训练

利用当前训练好的模型继续训练,需要注意的是给出的best_model的epoch已经达到110,所以在train_sem_seg.py文件中需要设置epoch的值(要大于110,这里设置了140(再训30轮)。

 

由于是在win10下面进行实验,所以dataloader里面的num_worker参数需要改为0,要不然会报错

 

在IDE中设置运行参数

 
 

eval point avg class IoU: 0.437896

eval whole scene point avg class acc: 0.531335

eval whole scene point accuracy: 0.784970

补充:从txt构造obj的python代码 

本文地址:http://ww.kub2b.com/news/18119.html     企库往 http://ww.kub2b.com/ ,  查看更多

特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。

 
 
更多>同类最新文章
0相关评论

文章列表
相关文章
最新动态
推荐图文
最新文章
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号