VoxelNet论文解读和代码解析

本文详细介绍了VoxelNet,一种用于3D点云目标检测的深度学习框架。VoxelNet利用体素化和堆叠的VFE层提取特征,结合RPN网络进行目标检测。网络结构包括特征学习网络、中间卷积层和RPN层,损失函数基于分类和回归任务。此外,还讨论了数据增强策略和高效实现的堆叠VFE方法。代码实现部分展示了网络结构和损失函数的具体细节。

目录

一、论文动机

二、论文方法

三、网络结构

1.Feature Learning Network

2.中间卷积层(Convolution middle layers)

3.RPN层

四、损失函数及其余创新点

4.1损失函数

4.2点云的数据增强

4.3高效实现堆叠VFE

五、代码阅读

六、Reference


论文地址:https://arxiv.org/abs/1711.06396

代码地址:GitHub - RPFey/voxelnet_pytorch: modification of voxelnet

一、论文动机

1.传统的点云特征手工提取具有很大的局限性,不能适应点云检测场景的多变性。

2.pointnet和++都是处理的小规模数量点云,一般都是几千个点左右。而lidar点云一般都是几万级别的。

二、论文方法

1.提出了一种端到端的点云检测框架,直接在稀疏点云上运行,避免了手动特征工程的信息瓶颈

2.提出了VFE体素特征编码网络,可以有效的提取体素特征

3.引入RPN区域提议网络,用于高效的目标检测

具体来说,VoxelNet 将点云划分为等间距的 3D 体素,通过堆叠 VFE 层对每个体素进行编码,然后 3D 卷积进一步聚合这些局部体素的特征,将三维特征的点云转换为高维特征表示。最后, 使用RPN产生检测结果。这种高效的算法受益于稀疏点结构和体素网格上的高效并行处理。

三、网络结构

1.Feature Learning Network

 1.1Voxel划分

将点云在空间上划分为许多体素,设定体素的长宽高,并对点云裁剪,太边缘的不要,因为很稀疏没啥用。论文中划分后为[352,400,10]个voxel

1.2Grouping(将每个点分配给对应的Voxel)和Sampling(voxel中点云的采样)

体素化后,有好多网格里面没有点,有些点又过多,这时我们对非空的voxel进行降采样,每个里面随机采样T个点,不足的用0补充。因此在grouping之后,得到的数据为[N,T,C],N为非空体素的个数,T为每个体素里面采样的点,C为特征。论文里T为35,C为7, [x, y, z, ri, xi_offset,yi_offset,zi_offset],先求出每个体素里面35个点的均值,每个点xyz都减去均值,得到xyz偏移。

1.3VFE堆叠

将每个voxel中的点通过全连接层转换为高维空间(每个全连接层包括FC,RELU,BN),维度也从(N,35,7)变成了(N,35,C1),然后maxpooling得到voxel的聚合特征,将聚合特征拼接到每个高维点云特征中,得到(N,35,2*C1),我们将这个过程称之为VFE模块。原论文中有两个VFE模块,得到(N,35,32),(N,35,128),得到这个特征之后需要再进行一个FC来融合点特征和聚合特征,这个FC保持输入输出维度不变,还是(N,35,128),再maxpooling一下,得到(N,128)

1.4稀疏特征表示

前面的操作都是对非空体素的操作,这些voxel仅仅对应3D空间中很小一部分,现在将那些非空的体素还原到3D空间中,得到一个稀疏的4D张量(128,10,400,352)

2.中间卷积层(Convolution middle layers)

得到稀疏张量之后,我们使用三维卷积来聚合voxel之间的局部关系,扩大感受野获取更丰富的形状信息,方便RPN预测。论文里使用了三个三维卷积(cin,cout,K,S,P)

Conv3D(128, 64, 3, (2,1,1), (1,1,1)),

Conv3D(64, 64, 3, (1,1,1), (0,1,1)),

Conv3D(64, 64, 3, (2,1,1), (1,1,1))

最终得到的张量为 (64,2,400,352),将数据整理成RPN网络需要的特征体,reshape成(64*2,400,352),这样维度就变成了C,Y,X,不考虑Z的原因是kitti的3D空间在高度上没有堆叠,这样也减少了网络后期RPN的设计难度和anchor数量。

3.RPN层

这里每一层卷积都是二维的卷积操作,每个卷积后面都接一个BN和RELU层。最终的预测结果只针对一类,两个角度的预测,所以是(B,2,200,176)和(B,14,200,176),anchor沿着xy间隔放置。

四、损失函数及其余创新点

4.1损失函数

正负样本匹配:在BEV视角进行2D IOU匹配,大于0.6为正样本,小于0.45为负样本,中间不计。分类损失正负样本都记,使用交叉熵损失函数,回归损失只记正样本的,使用smoothL1函数。

4.2点云的数据增强

1.由于标注的时候一个GTbox已经标注出来了有哪些点,所以可以同时移动或者旋转这些点来创造大量的变化数据,移动后还要进行碰撞检测,删掉碰撞的那些GT。

2.对所有的GTbox进行放大或者缩小,放大缩小尺度在[0.95,1.05]之间,引入缩放可以使得网络在检测不同大小物体上有更好的泛化性能。

3.对所有的GT box进行随机的旋转操作,角度在[-45,45]均匀分布中抽取,旋转物体的偏航角可以模拟其转了一个弯。

4.3高效实现堆叠VFE

由于每个voxel中包含的点个数不一样,所以作者将所有点云数据转换成了一种密集的数据结构,使得后面堆叠VFE可以在所有点和voxel的特征上平行处理。

1、首先创建一个K*T*7的张量(voxel input feature buffer)用来存储每个点或者中间的voxel特征数据,K是最大的非空voxel数量,T是每个voxel中最大的点数,7是每个点的编码特征。所有的点都是被随机处理的。

2、遍历整个点云数据,如果一个点对应的voxel在voxel coordinate buffer中,并且与之对应的voxel input feature buffer中点的数量少于T个,直接将这个点插入其中,否则直接抛弃。如果一个点对应的voxel不在voxel coordinate buffer中,需要在voxel coordinate buffer中直接使用这个voxel的坐标初始化这个voxel,并储存这个点到voxel input feature buffer中。这整个操作都是用哈希表完成,因此时间复杂度都是O(1)。整个Voxel Input Feature Buffer和voxel coordinate buffer的创建只需要遍历一次点云数据就可以,时间复杂度只有O(N),同时为了进一步提高内存和计算资源,对voxel中点的数量少于m数量的voxel直接忽略该voxel的创建。

3、在创建完Voxel Input Feature Buffer和voxel coordinate buffer后Stacked Voxel Feature Encoding就可以直接在点的基础上或者voxel的基础上进行平行计算。再经过VFE模块的concat操作后,就将之前为空的点的特征置0,保证了voxel的特征和点的特征的一致性。最后,使用存储在voxel coordinate buffer的内容恢复出稀疏的4D张量数据,完成后续的中间特征提取和RPN层。

五、代码阅读

数据预处理:先使用crop.py程序把图像坐标之外的点云剪裁掉便于后期可视化验证,在运行crop.py之前,需要先在training和testing文件夹下新建crop文件夹,运行完的数据存在里面。

根据标签数据构建3D框,在KITTIDatset里面,加载标签数据得到gt_box3d_corner(N,8,3), gt_box3d(N,7)

加载点云数据同时进行data_augment,然后utils.get_filtered_lidar筛选出指定空间范围内的点云和真实检测框顶点的坐标。self.preprocess(lidar)点云体素化返回体素特征voxel_features(Nx35x7)和体素坐标voxel_coords(Nx3),这两个送入网络得到预测输出,然后真实框和锚框送入dataset.cal_target计算真实偏移。然后求这两者的损失

5.1网络结构

import torch.nn as nn
import torch.nn.functional as F
import torch
from torch.autograd import Variable
from config import config as cfg

# conv2d + bn + relu
class Conv2d(nn.Module):
    def __init__(self,in_channels,out_channels,k,s,p, activation=True, batch_norm=True):
        super(Conv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels,out_channels,kernel_size=k,stride=s,padding=p)
        if batch_norm:
            self.bn = nn.BatchNorm2d(out_channels)
        else:
            self.bn = None
        self.activation = activation
    def forward(self,x):
        x = self.conv(x)
        if self.bn is not None:
            x=self.bn(x)
        if self.activation:
            return F.relu(x,inplace=True)
        else:
            return x

# conv3d + bn + relu
class Conv3d(nn.Module):
    def __init__(self, in_channels, out_channels, k, s, p, batch_norm=True):
        super(Conv3d, self).__init__()
        self.conv = nn.Conv3d(in_channels, out_channels, kernel_size=k, stride=s, padding=p)
        if batch_norm:
            self.bn = nn.BatchNorm3d(out_channels)
        else:
            self.bn = None

    def forward(self, x):
        x = self.conv(x)
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CVplayer111

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值