Python-PointCloud系列(二)——PointNet论文复现

更新时间:2023-05-27 14:16:49 阅读: 评论:0

Python-PointCloud 系列(⼆)——PointNet 论⽂复现
⽂章⽬录
前⾔:
参考内容:
参考内容:
Point Net 介绍
⽹络结构旋转变换矩阵
⽂章中采⽤了⼀个3    3 矩阵对单个点云数据进⾏旋转变换。矩阵的参数是在⽹络训练的过程中⾃动调整的,也就是说,在训练过程中,⽹络会⾃动旋转待分类的点云对象,以达到⼀个合适的效果。其实也可以看成是⼀个特征转换层,只是从物理层⾯上是⼀个⼏何旋转变化。其实就是将原始数据流分为两股,⼀股去训练⼀个⽹络,这个⽹络的输出是3 3的矩阵,另外⼀股则
直接与这个矩阵相乘,起到旋转的结果。
论⽂主体中没有过多的解释,⽽是放到了补充材料中
另外,作者从低维(3dim)得到启发,对转换后的⾼维特征也做了旋转标定,如64维的特征乘⼀个64 64 的矩阵即可达到⾼维空间旋转的效果。 为了使变换矩阵靠近正交矩阵,作者对矩阵参数添加了正则项。
这部分还有部分不理解
1、为什么在数据输⼊的时候已经过旋转标定,在转换后的⾼维特征空间中还需要旋转标定(实验试错⽽得?)
2、看到很多⽂章说是转到正⾯,但是我还是不理解这个正⾯是从哪⾥看出来的(可能跟正交矩阵有关系?这部分可能还需要线性相关的知识。
3、为什么后⾯的64维的变换矩阵需要添加正则项以达到正交矩阵?
参考其他博客,了解到旋转矩阵是正交矩阵的⼀种。
4、为什么对3维旋转变换矩阵不添加正则项以保证其正交性?和⾼维⼀样?
欧式空间V中的正交变换只包含:
(1)旋转
(2)反射
(3)旋转+反射的组合(即瑕旋转)
源码解析(详细)
×××
源码下载
把下载后的zip⽂件解压缩。
源码⽬录结构
解压后的⽂件⽬录如下图。其中PointNet主体的代码在“models”这个⽂件夹中。
“models”⽂件夹⾥⾯包括了原始pointnet的代码以及针对分类、分割、检测的代码,其中pointnet.py⾥⾯包含了最基础的模型代码。如下图
PointNet.py
import torch
as nn
parallel
import torch.utils.data
from torch.autograd import Variable
import numpy as np
functional as F
"""
STN: Spatial Transformer Networks  空间转换⽹络
"""
# 这边实现的是三维空间转换⽹络。
class STN3d(nn.Module):
def__init__(lf, channel):
super(STN3d, lf).__init__()
lf.fc1 = nn.Linear(1024,512)
lf.fc2 = nn.Linear(512,256)
lf.fc3 = nn.Linear(256,9)
lf.bn1 = nn.BatchNorm1d(64)
lf.bn2 = nn.BatchNorm1d(128)
lf.bn3 = nn.BatchNorm1d(1024)
lf.bn4 = nn.BatchNorm1d(512)
lf.bn5 = nn.BatchNorm1d(256)
def forward(lf, x):
batchsize = x.size()[0]# 第⼀个维度是batch的数量
x = F.relu(lf.v1(x)))
酒囊饭袋
x = F.relu(lf.v2(x)))
x = F.relu(lf.v3(x)))
x = torch.max(x,2, keepdim=True)[0]
屏下指纹原理
x = x.view(-1,1024)# 转换为列为1024但⾏不定的数据
x = F.relu(lf.bn4(lf.fc1(x)))
x = F.relu(lf.bn5(lf.fc2(x)))
x = lf.fc3(x)
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)# ⽣成3x3的单位矩阵,但是是⼀⾏的形式⽅便计算
if x.is_cuda:
iden = iden.cuda()
x = x + iden  # 这边加起来是什么意思?为什么要加单位矩阵,这边是对应论⽂说初始化为对⾓单位阵
x = x.view(-1,3,3)# 转换为3x3的矩阵
return x
class STNkd(nn.Module):
def__init__(lf, k=64):
super(STNkd, lf).__init__()
lf.fc1 = nn.Linear(1024,512)
lf.fc2 = nn.Linear(512,256)
lf.fc3 = nn.Linear(256, k * k)
lf.bn1 = nn.BatchNorm1d(64)
lf.bn2 = nn.BatchNorm1d(128)
lf.bn3 = nn.BatchNorm1d(1024)
lf.bn4 = nn.BatchNorm1d(512)
lf.bn5 = nn.BatchNorm1d(256)
lf.k = k
def forward(lf, x):
batchsize = x.size()[0]
x = F.relu(lf.v1(x)))
x = F.relu(lf.v2(x)))
x = F.relu(lf.v3(x)))
x = torch.max(x,2, keepdim=True)[0]落马洲口岸
x = x.view(-1,1024)
x = F.relu(lf.bn4(lf.fc1(x)))
x = F.relu(lf.bn5(lf.fc2(x)))
x = lf.fc3(x)
iden = Variable(torch.from_(lf.k).flatten().astype(np.float32))).view(1, lf.k * lf.k).repeat(            batchsize,1)
if x.is_cuda:
iden = iden.cuda()
x = x + iden
x = x.view(-1, lf.k, lf.k)
return x
"""⾼维映射⽹络,即将单个点云点映射到多维空间的⽹络,以避免后续的最⼤池化过度地损失信息"""
class PointNetEncoder(nn.Module):
def__init__(lf, global_feat=True, feature_transform=Fal, channel=3):
super(PointNetEncoder, lf).__init__()
lf.stn = STN3d(channel)# 3维空间转换矩阵
什么而不舍
lf.bn1 = nn.BatchNorm1d(64)
lf.bn2 = nn.BatchNorm1d(128)
lf.bn3 = nn.BatchNorm1d(1024)
lf.global_feat = global_feat  # 全局特侦标志
lf.feature_transform = feature_transform  # 是否对⾼维特征进⾏旋转变换标定
if lf.feature_transform:
lf.fstn = STNkd(k=64)# ⾼维空间变换矩阵
def forward(lf, x):
# B:样本的⼀个批次⼤⼩,batch;D:点的维度 3 (x,y,z) dim ; N:点的数量 (1024) number
# 即这边⼀次输⼊24个样本,⼀个样本含有1024个点云点,⼀个点云点为3维(x,y,z)
B, D, N = x.size()# [24, 3, 1024]
trans = lf.stn(x)# 得到3维旋转转换矩阵
x = x.transpo(2,1)# 将2轴和1轴对调,相当于[24,1024,3]
if D >3:# 这边是是特征点的话,不只有3维(x,y,z),可能为多维
x, feature = x.split(3, dim=2)# 从维度2上按照3块分开。就是将⾼维特征按照3份分开
x = torch.bmm(x, trans)# 将3维点云数据进⾏旋转变换
if D >3:
x = torch.cat([x, feature], dim=2)
x = x.transpo(2,1)# 将2轴和1轴再对调,??
x = F.relu(lf.v1(x)))# 进⾏第⼀次卷积、标准化、激活、得到64维的数据
"""————————————————(2020/1/18)——————————————————"""
山水背景图片
# 下⾯是第⼆层卷积层处理
if lf.feature_transform:# 如果需要对中间的特征进⾏旋转标定的话
trans_feat = lf.fstn(x)# 得到特征空间的旋转矩阵
x = x.transpo(2,1)# 将1轴和2轴对调
x = torch.bmm(x, trans_feat)# 将特征数据进⾏旋转转换
x = x.transpo(2,1)# 将2轴再次和1轴对调
el:
trans_feat =None
pointfeat = x  # 旋转矫正过后的特征
x = F.relu(lf.v2(x)))# 第⼆次卷积输出维128
x = lf.v3(x))# 第三次卷积输出维1024
x = torch.max(x,2, keepdim=True)[0]# 进⾏最⼤池化处理,只返回最⼤的数,不返回索引([0]是数值,[1]是索引)
x = x.view(-1,1024)# 把x reshape为 1024列的⾏数不定矩阵,这边的-1指的就是⾏数不定。
if lf.global_feat:# 是否为全局特征
return x, trans, trans_feat  # 返回特征数据x,3维旋转矩阵,多维旋转矩阵
el:
x = x.view(-1,1024,1).repeat(1,1, N)# 多扩展了⼀个维度是为了和局部特征统⼀维度,⽅便后⾯的连接,然后复制成与局部特征⼀样的数量return torch.cat([x, pointfeat],1), trans, trans_feat # 这边对应点云分割算法中,将全局特征与局部特征连接。
"""这边是⾼维特征空间转换举证的正则项,⼤致的意思的把这个转换矩阵乘上其转置阵再减去单位阵,取剩下差值的均值为损失函数"""
def feature_transform_reguliarzer(trans):
d = trans.size()[1]# 矩阵维度
I = (d)[None,:,:]# ⽣成同维度的对⾓单位阵
if trans.is_cuda:# 是否采⽤Cuda加速
prefer名词
I = I.cuda()
# 损失函数,将变换矩阵乘⾃⾝转置然后减单位阵,取结果的元素均值为损失函数,因为正交阵乘其转置为单位阵。这边不需要取绝对值或者L2吗?# A*(A'-I) = A*A'- A*I = I - A*I |  A’: 矩阵A的转置
loss = (torch.bmm(trans, anspo(2,1)- I), dim=(1,2)))
return loss
ModelNetDataLoader.py
import numpy as np
import warnings
import os
from torch.utils.data import Datat
ASTYPE = np.array([cls]).astype(np.int32)
INDEX_ = lf.class[lf.datapath[index][0]]
warnings.filterwarnings('ignore')
def pc_normalize(pc):# 简单的标准化
centroid = np.mean(pc, axis=0)# 形⼼,设微元体积为单位体积。
pc = pc - centroid  # 去偏移,将坐标系原点转换到形⼼位置,坐标系只平移不旋转
m = np.max(np.sqrt(np.sum(pc **2, axis=1)))# 将同⼀⾏的元素取平⽅相加,再开⽅,取最⼤。  sqrt(x^2+y^2+z^2)
pc = pc / m  # 归⼀化,归⼀化操作似乎会丢失物品的尺⼨⼤⼩信息?因为每个样本的m不⼀样。
return pc
def farthest_point_sample(point, npoint):# 最远点的提取
"""
Input:
xyz: pointcloud data, [N, D]
npoint: number of samples
Return:
centroids: sampled pointcloud index, [npoint, D]
"""
N, D = point.shape
xyz = point[:,:3]
centroids = np.zeros((npoint,))# 重⼼
distance = np.ones((N,))*1e10
farthest = np.random.randint(0, N)
for i in range(npoint):
撒的组词centroids[i]= farthest
centroid = xyz[farthest,:]
dist = np.sum((xyz - centroid)**2,-1)
mask = dist < distance
distance[mask]= dist[mask]
farthest = np.argmax(distance,-1)
point = point[centroids.astype(np.int32)]
return point
#  制作这个类的重点在于⽣成⼀个列表,这个列表的元素为(path_sample x,lable x)的形式,重要的是⽣成路径与标签的列表
#  也不⼀定需要制作路径的列表,可能制作路径的列表会⽐较不占内存,每次只把需要的数据加载进来⽽已。
#  可以直接制作数据与标签的列表,可以按索引进⾏连接。
花儿与少年第三季
class ModelNetDataLoader(Datat):# ⾃⼰的数据集类⼦类,需要集成⽗类  Datat
# 要求override __len__和__getitem__,前⾯提供数据集⼤⼩、后者⽀持整数索引(?)
def__init__(lf, root, npoint=1024, split='train', uniform=Fal, normal_channel=True, cache_size=1
5000):
< = root  # 根⽬录
lf.npoints = npoint  # 每个实例的点云数
lf.uniform = uniform  # 统⼀
lf.catfile = os.path.,'modelnet40_')# cat  数据库类别的路径(40类)
lf.cat =[line.rstrip()for line in open(lf.catfile)]# 打开txt⽂件,读取每⼀⾏,并⽤rstrip()删除每⾏末尾的空格
lf.class =dict(zip(lf.cat,range(len(lf.cat))))# ⽣成类别字典{类别1:0,类别2:1,...类别40:39}
shape_ids ={}
shape_ids['train']=[line.rstrip()for line in open(os.path.,''))]# 加载训练
集为⼀个列表,列表放在⼀个字典⾥⾯        shape_ids['test']=[line.rstrip()for line in open(os.path.,''))]# 加载测试集....
asrt(split =='train'or split =='test')
shape_names =['_'.join(x.split('_')[0:-1])for x in shape_ids[split]]# a = 'va_0513', a.split('_') =  ['va', '0513']  为什么要⽤'_'/join 呢?

本文发布于:2023-05-27 14:16:49,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/937586.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:矩阵   旋转   特征   转换   需要   列表   空间
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图