目录
一、论文动机
现有的问题:
作者的思路及面临的问题:
二、论文方法
如何解决点云无序性问题?作者提出了三种想法。
针对点云的刚体运动不变性
三、网络结构
四、代码阅读
五、Reference(两篇都是对原论文的翻译)
一、论文动机
现有的问题:
因为卷积神经网络一般都要求高度规则的输入数据格式,如图像和3D体素,所以现有的方法一般是把点云转换成体素格式或者投影到二维图中。但是由于算力的限制,体素的分辨率最多只能达到32*32*32,而且体素是立方体,对于点云表面的特征都没有表达出来。
作者的思路及面临的问题:
我们能否设计一个神经网络直接对点云的特征进行学习,而不进行转换?
面临两个问题:1.如何解决点云的无序性问题
? ? ? ? ? ? ? ? ? ? ? ? ? 2.如何保证点云的刚体运动不变性
二、论文方法
如何解决点云无序性问题?作者提出了三种想法。
1.对点云排序之后送入——高维空间不存在稳定的排序,因为有点的扰动。
2.将点云作为一个序列来用RNN处理——RNN对于长度较小的序列具有很好的鲁棒性,但点云序列太长了
3.使用对称函数来解决无序性问题,如均值池化,最大池化。实验证明最大池化效果最好。
针对点云的刚体运动不变性
引入两个对齐输入点和点特征的联合对齐网络,在点云进行特征提取之前将所有的输入对齐到规范空间。通过T-Net网络预测一个仿射变换矩阵,与点云进行相乘。
为什么联合对齐网络可以引入刚体运动不变性?
三、网络结构
?原理就是对输入的点云先进行对齐,引入刚体运动不变性,然后通过MLP对特征升维,再对特征进行对齐,然后再MLP升维特征,这时进行最大池化,解决无序性问题,得到1024维的全局特征,如果是分类,则直接对全局特征进行MLP操作,输出k维结果就是分类结果。如果是分割的话,则把全局特征与每个点的特征进行拼接,然后两次MLP得到每个点的分割结果。
四、代码阅读
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.utils.data
from torch.autograd import Variable
import numpy as np
import torch.nn.functional as F
#nn.relu模块调用一般用在init里,而F.relu是函数调用一般用在forward里
#Conv1d和Linear的区别,当你必须保留语义分割中的空间信息时,使用卷积 Conv1d() 。当你不需要做任何与空间信息相关的事情时,比如在基本分类(mnist、猫狗分类器)中,使用线性层 Linear()
class STN3d(nn.Module): #第一个T-Net
def __init__(self, channel):
super(STN3d, self).__init__()
self.conv1 = torch.nn.Conv1d(channel, 64, 1)
self.conv2 = torch.nn.Conv1d(64, 128, 1)
self.conv3 = torch.nn.Conv1d(128, 1024, 1)
self.fc1 = nn.Linear(1024, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, 9)
self.relu = nn.ReLU()
self.bn1 = nn.BatchNorm1d(64)
self.bn2 = nn.BatchNorm1d(128)
self.bn3 = nn.BatchNorm1d(1024)
self.bn4 = nn.BatchNorm1d(512)
self.bn5 = nn.BatchNorm1d(256)
def forward(self, x):
batchsize = x.size()[0] #x-(B,C,N)N表示n个点,C表示特征数
x = F.relu(self.bn1(self.conv1(x)))
x = F.relu(self.bn2(self.conv2(x)))
x = F.relu(self.bn3(self.conv3(x))) #(B,1024,N)
x = torch.max(x, 2, keepdim=True)[0] #只返回最大值的每个数(B,1024,1)
x = x.view(-1, 1024)
x = F.relu(self.bn4(self.fc1(x)))
x = F.relu(self.bn5(self.fc2(x)))
x = self.fc3(x) #(B,9)
iden = Variable(torch.from_numpy(np.array([1, 0, 0, 0, 1, 0, 0, 0, 1]).astype(np.float32))).view(1, 9).repeat(
batchsize, 1) #tensor不能反向传播,variable可以反向传播。同时在单位矩阵的基础上进行调整
if x.is_cuda:
iden = iden.cuda()
x = x + iden
x = x.view(-1, 3, 3)
return x
class STNkd(nn.Module): #特征T-Net
def __init__(self, k=64):
super(STNkd, self).__init__()
self.conv1 = torch.nn.Conv1d(k, 64, 1)
self.conv2 = torch.nn.Conv1d(64, 128, 1)
self.conv3 = torch.nn.Conv1d(128, 1024, 1)
self.fc1 = nn.Linear(1024, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, k * k)
self.relu = nn.ReLU()
self.bn1 = nn.BatchNorm1d(64)
self.bn2 = nn.BatchNorm1d(128)
self.bn3 = nn.BatchNorm1d(1024)
self.bn4 = nn.BatchNorm1d(512)
self.bn5 = nn.BatchNorm1d(256)
self.k = k
def forward(self, x):
batchsize = x.size()[0]
x = F.relu(self.bn1(self.conv1(x)))
x = F.relu(self.bn2(self.conv2(x)))
x = F.relu(self.bn3(self.conv3(x)))
x = torch.max(x, 2, keepdim=True)[0]
x = x.view(-1, 1024)
x = F.relu(self.bn4(self.fc1(x)))
x = F.relu(self.bn5(self.fc2(x)))
x = self.fc3(x)
iden = Variable(torch.from_numpy(np.eye(self.k).flatten().astype(np.float32))).view(1, self.k * self.k).repeat(
batchsize, 1)
if x.is_cuda:
iden = iden.cuda()
x = x + iden
x = x.view(-1, self.k, self.k)
return x
class PointNetEncoder(nn.Module):
def __init__(self, global_feat=True, feature_transform=False, channel=3):
super(PointNetEncoder, self).__init__()
self.stn = STN3d(channel)
self.conv1 = torch.nn.Conv1d(channel, 64, 1)
self.conv2 = torch.nn.Conv1d(64, 128, 1)
self.conv3 = torch.nn.Conv1d(128, 1024, 1)
self.bn1 = nn.BatchNorm1d(64)
self.bn2 = nn.BatchNorm1d(128)
self.bn3 = nn.BatchNorm1d(1024)
self.global_feat = global_feat
self.feature_transform = feature_transform
if self.feature_transform:
self.fstn = STNkd(k=64)
def forward(self, x):
B, D, N = x.size()
trans = self.stn(x)
x = x.transpose(2, 1) #交换矩阵的两个维度,也可以torch.transpose
if D > 3:
feature = x[:, :, 3:]
x = x[:, :, :3]
x = torch.bmm(x, trans) #计算两个tensor的矩阵乘法,torch.bmm(a,b),tensor a 的size为(b,h,w),tensor b的size为(b,w,m) 也就是说两个tensor的第一维是相等的,然后第一个数组的第三维和第二个数组的第二维度要求一样,对于剩下的则不做要求,输出维度 (b,h,m)
if D > 3:
x = torch.cat([x, feature], dim=2)
x = x.transpose(2, 1)
x = F.relu(self.bn1(self.conv1(x))) #(B,64,N)
if self.feature_transform:
trans_feat = self.fstn(x)
x = x.transpose(2, 1)
x = torch.bmm(x, trans_feat)
x = x.transpose(2, 1)
else:
trans_feat = None
pointfeat = x #这里记录一下点的特征,后面分割用
x = F.relu(self.bn2(self.conv2(x))) #(B,128,N)
x = self.bn3(self.conv3(x)) #(B,1024,N)
x = torch.max(x, 2, keepdim=True)[0]
x = x.view(-1, 1024) #(B,1024)
if self.global_feat:
return x, trans, trans_feat
else:
x = x.view(-1, 1024, 1).repeat(1, 1, N) #(B,1024,N)
return torch.cat([x, pointfeat], 1), trans, trans_feat #如果有分割,则返回合并后的特征
##对齐特征的时候,由于特征空间维度更高,优化难度大,所以加了一项正则项,让求解出来的仿射变换矩阵接近于正交,这里返回的在后面损失函数会用到
#扩充维度可以先view扩充一维,再repeat
def feature_transform_reguliarzer(trans):
d = trans.size()[1]
I = torch.eye(d)[None, :, :]
if trans.is_cuda:
I = I.cuda()
loss = torch.mean(torch.norm(torch.bmm(trans, trans.transpose(2, 1)) - I, dim=(1, 2))) #torch.norm默认求所有元素的平方和,dim指定维度,是在一二维求,得到(B,loss)再求平均
return loss
#损失函数nn.CrossEntropyLoss()=F.log_softmax() + F.nll_loss()
五、Reference(两篇都是对原论文的翻译)
(3条消息) 【论文翻译】从零开始PointNet论文分析与代码复现_花花大魔王的博客-CSDN博客
PointNet 文献阅读及拓展阅读_万俟淋曦的博客-CSDN博客
|