看centerpoint
的预处理pcdet/datasets/processor/data_processor.py
直接看代码,有详细的注释
class VoxelGeneratorWrapper():
def __init__(self, vsize_xyz, coors_range_xyz, num_point_features, max_num_points_per_voxel, max_num_voxels):
try:
from spconv.utils import VoxelGeneratorV2 as VoxelGenerator
self.spconv_ver = 1
except:
try:
from spconv.utils import VoxelGenerator
self.spconv_ver = 1
except:
from spconv.utils import Point2VoxelCPU3d as VoxelGenerator
self.spconv_ver = 2
if self.spconv_ver == 1:
self._voxel_generator = VoxelGenerator(
voxel_size=vsize_xyz,
point_cloud_range=coors_range_xyz,
max_num_points=max_num_points_per_voxel,
max_voxels=max_num_voxels
)
else:
self._voxel_generator = VoxelGenerator(
vsize_xyz=vsize_xyz,
coors_range_xyz=coors_range_xyz,
num_point_features=num_point_features,
max_num_points_per_voxel=max_num_points_per_voxel,
max_num_voxels=max_num_voxels
)
def generate(self, points):
if self.spconv_ver == 1:
voxel_output = self._voxel_generator.generate(points)
if isinstance(voxel_output, dict):
voxels, coordinates, num_points = \
voxel_output['voxels'], voxel_output['coordinates'], voxel_output['num_points_per_voxel']
else:
voxels, coordinates, num_points = voxel_output
else:
assert tv is not None, f"Unexpected error, library: 'cumm' wasn't imported properly."
voxel_output = self._voxel_generator.point_to_voxel(tv.from_numpy(points))
tv_voxels, tv_coordinates, tv_num_points = voxel_output
# make copy with numpy(), since numpy_view() will disappear as soon as the generator is deleted
voxels = tv_voxels.numpy()
coordinates = tv_coordinates.numpy()
num_points = tv_num_points.numpy()
return voxels, coordinates, num_points
重点看spconv
的类VoxelGeneratorV2
位于:spconv/utils/__init__.py
class VoxelGeneratorV2:
def __init__(self,
voxel_size,
point_cloud_range,
max_num_points,
max_voxels=20000,
full_mean=False,
block_filtering=False,
block_factor=8,
block_size=3,
height_threshold=0.1,
height_high_threshold=2.0):
assert full_mean is False, "don't use this."
point_cloud_range = np.array(point_cloud_range, dtype=np.float32)
# [0, -40, -3, 70.4, 40, 1]
voxel_size = np.array(voxel_size, dtype=np.float32)
grid_size = (point_cloud_range[3:] - point_cloud_range[:3]) / voxel_size
grid_size = np.round(grid_size).astype(np.int64)
if block_filtering: # 默认False
assert block_size > 0
assert grid_size[0] % block_factor == 0
assert grid_size[1] % block_factor == 0
voxelmap_shape = tuple(np.round(grid_size).astype(np.int32).tolist()) # 数组转列表转元组(1440,1440,40)
voxelmap_shape = voxelmap_shape[::-1] # (40,1440,1440)
self._coor_to_voxelidx = np.full(voxelmap_shape, -1, dtype=np.int32) # [40,1440,1440] -1填充
self._voxel_size = voxel_size # [0.075, 0.075, 0.2]
self._point_cloud_range = point_cloud_range # [-54.0, -54.0, -5.0, 54.0, 54.0, 3.0]
self._max_num_points = max_num_points # 10
self._max_voxels = max_voxels # 60000
self._grid_size = grid_size # [1440,1440,40]
self._full_mean = full_mean # False
self._block_filtering = block_filtering # False
self._block_factor = block_factor # 8
self._height_threshold = height_threshold # 0.1
self._block_size = block_size # 3
self._height_high_threshold = height_high_threshold # 2.0
def generate(self, points, max_voxels=None):
res = points_to_voxel(points, # [N,5]
self._voxel_size, # [0.075, 0.075, 0.2]
self._point_cloud_range, # [-54.0, -54.0, -5.0, 54.0, 54.0, 3.0]
self._coor_to_voxelidx, # [40,1440,1440] -1填充
self._max_num_points, # 10
max_voxels or self._max_voxels, # 60000
self._full_mean, # False
self._block_filtering, # False
self._block_factor, # 8
self._block_size, # 3
self._height_threshold, # 0.1
self._height_high_threshold # 2.0
)
)
for k, v in res.items():
if k != "voxel_num":
res[k] = v[:res["voxel_num"]]
return res
接着看points_to_voxel
函数,位于spconv/utils/__init__.py
def points_to_voxel(points, # [N,5]
voxel_size, # [0.075, 0.075, 0.2]
coors_range, # [-54.0, -54.0, -5.0, 54.0, 54.0, 3.0]
coor_to_voxelidx, # np.numpy[40,1440,1440] -1填充
max_points=35, # 10
max_voxels=20000, # 60000
full_mean=False, # False
block_filtering=True, # False
block_factor=1, # 8
block_size=8, # 3
height_threshold=0.2, # 0.1
height_high_threshold=3.0, # 2.0
pad_output=False):
"""convert 3d points(N, >=3) to voxels. This version calculate
everything in one loop. now it takes only 0.8ms(~6k voxels)
with c++ and 3.2ghz cpu.
Args:
points: [N, ndim] float tensor. points[:, :3] contain xyz points and
points[:, 3:] contain other information such as reflectivity.
voxel_size: [3] list/tuple or array, float. xyz, indicate voxel size
coors_range: [6] list/tuple or array, float. indicate voxel range.
format: xyzxyz, minmax
coor_to_voxelidx: int array. used as a dense map.
max_points: int. indicate maximum points contained in a voxel.
max_voxels: int. indicate maximum voxels this function create.
for voxelnet, 20000 is a good choice. you should shuffle points
before call this function because max_voxels may drop some points.
full_mean: bool. if true, all empty points in voxel will be filled with mean
of exist points.
block_filtering: filter voxels by height. used for lidar point cloud.
use some visualization tool to see filtered result.
Returns:
voxels: [M, max_points, ndim] float tensor. only contain points.
coordinates: [M, 3] int32 tensor. zyx format.
num_points_per_voxel: [M] int32 tensor.
"""
if full_mean: # False
assert block_filtering is False
if not isinstance(voxel_size, np.ndarray):
voxel_size = np.array(voxel_size, dtype=points.dtype)
if not isinstance(coors_range, np.ndarray):
coors_range = np.array(coors_range, dtype=points.dtype)
voxelmap_shape = (coors_range[3:] - coors_range[:3]) / voxel_size # [1140,1440,40]
voxelmap_shape = tuple(np.round(voxelmap_shape).astype(np.int32).tolist()) # 数组转列表转元组(1440,1440,40)
voxelmap_shape = voxelmap_shape[::-1] # (40,1440,1440)
num_points_per_voxel = np.zeros(shape=(max_voxels, ), dtype=np.int32) # [60000,]
voxels = np.zeros(shape=(max_voxels, max_points, points.shape[-1]),dtype=points.dtype) # [60000,10,5]
voxel_point_mask = np.zeros(shape=(max_voxels, max_points),dtype=points.dtype) # [60000,10]
coors = np.zeros(shape=(max_voxels, 3), dtype=np.int32) # [60000,3]
res = {
"voxels": voxels,
"coordinates": coors,
"num_points_per_voxel": num_points_per_voxel,
"voxel_point_mask": voxel_point_mask,
}
if full_mean: # False
#......略
else:
if block_filtering: # False
#......略
else:
# centerpoint_voxel走这里
# points_to_voxel_3d_np调用c++
voxel_num = points_to_voxel_3d_np(points, # [N,5]
voxels, # [60000,10,5]
voxel_point_mask, # [60000,10]
coors, # [60000,3]
num_points_per_voxel, # [60000]
coor_to_voxelidx, # np.numpy[40,1440,1440] -1填充
voxel_size.tolist(), # [0.075, 0.075, 0.2]
coors_range.tolist(), # [-54.0, -54.0, -5.0, 54.0, 54.0, 3.0]
max_points, # 10
max_voxels # 60000
)
res["voxel_num"] = voxel_num
res["voxel_point_mask"] = res["voxel_point_mask"].reshape(-1, max_points, 1)
return res
src/utils/all.cc
namespace py = pybind11;
using namespace pybind11::literals;
PYBIND11_MODULE(spconv_utils, m) {
m.doc() = "util pybind11 functions for spconv";
.....
m.def("points_to_voxel_3d_np", &spconv::points_to_voxel_3d_np<float, 3>,
"matrix tensor_square", "points"_a = 1, "voxels"_a = 2,
"voxel_point_mask"_a = 3, "coors"_a = 4, "num_points_per_voxel"_a = 5,
"coor_to_voxelidx"_a = 6, "voxel_size"_a = 7, "coors_range"_a = 8,
"max_points"_a = 9, "max_voxels"_a = 10);
m.def("points_to_voxel_3d_np", &spconv::points_to_voxel_3d_np<double, 3>,
"matrix tensor_square", "points"_a = 1, "voxels"_a = 2,
"voxel_point_mask"_a = 3, "coors"_a = 4, "num_points_per_voxel"_a = 5,
"coor_to_voxelidx"_a = 6, "voxel_size"_a = 7, "coors_range"_a = 8,
"max_points"_a = 9, "max_voxels"_a = 10);
pybind11
是一个只包含头文件的轻量级库,可以通过它实现C/C++
接口的调用,相比于boost.python, swig, ctypes
等,pybind11
的优点是API比较简单且对C++ 11
支持很好。
PYBIND11_MODULE()
创建一个函数,当从python
中import
调用时,将调用该函数。
spconv_utils
)作为第一个宏参数给出(即在python
中作为模块名导入,这个名称最好与解决方案名称一致,这样后续生成解决方案后就不需要再进行修改)m
是一个内置的接口,把C++
和python
搭建一个桥梁。def
就是将C++函数暴露给python
,在def
中还可以声明接受参数,甚至是默认赋值。下面看核心的C++ 模板函数points_to_voxel_3d_np
:
template <typename DType, int NDim>
int points_to_voxel_3d_np(py::array_t<DType> points, // [N,5]
py::array_t<DType> voxels, // [60000,10,5]
py::array_t<DType> voxel_point_mask, // [60000,10]
py::array_t<int> coors, // [60000,3]
py::array_t<int> num_points_per_voxel,// [60000]
py::array_t<int> coor_to_voxelidx, // 维度[40,1440,1440] -1填充
std::vector<DType> voxel_size, // [0.075, 0.075, 0.2]
std::vector<DType> coors_range, // [-54.0, -54.0, -5.0, 54.0, 54.0, 3.0]
int max_points, // 10
int max_voxels // 60000
) {
auto points_rw = points.template mutable_unchecked<2>(); // Will throw if ndim != 2 or flags.writeable is false
auto voxels_rw = voxels.template mutable_unchecked<3>(); // Will throw if ndim != 3 or flags.writeable is false
auto voxel_point_mask_rw = voxel_point_mask.template mutable_unchecked<2>();
auto coors_rw = coors.mutable_unchecked<2>(); // Will throw if ndim != 2 or flags.writeable is false
auto num_points_per_voxel_rw = num_points_per_voxel.mutable_unchecked<1>(); // Will throw if ndim != 1 or flags.writeable is false
auto coor_to_voxelidx_rw = coor_to_voxelidx.mutable_unchecked<NDim>(); // Will throw if ndim != 3 or flags.writeable is false
auto N = points_rw.shape(0); // N
auto num_features = points_rw.shape(1); // 4
// auto ndim = points_rw.shape(1) - 1;
constexpr int ndim_minus_1 = NDim - 1; // 2
int voxel_num = 0;
bool failed = false;
int coor[NDim]; // int coor[3]
int c;
int grid_size[NDim]; // int grid_size[3]
for (int i = 0; i < NDim; ++i) {
grid_size[i] = round((coors_range[NDim + i] - coors_range[i]) / voxel_size[i]); // [1440,1440,40]
}
int voxelidx, num;
for (int i = 0; i < N; ++i) {
failed = false;
for (int j = 0; j < NDim; ++j) {
c = floor((points_rw(i, j) - coors_range[j]) / voxel_size[j]); // voxel网格坐标
if ((c < 0 || c >= grid_size[j])) { // 超出坐标范围
failed = true;
break;
}
coor[ndim_minus_1 - j] = c; // z,y,x
}
if (failed) // 该点超出范围
continue;
voxelidx = coor_to_voxelidx_rw(coor[0], coor[1], coor[2]);
if (voxelidx == -1) {
voxelidx = voxel_num; // voxel索引,代表第几个voxel
if (voxel_num >= max_voxels)
continue;
voxel_num += 1;
coor_to_voxelidx_rw(coor[0], coor[1], coor[2]) = voxelidx;
for (int k = 0; k < NDim; ++k) {
coors_rw(voxelidx, k) = coor[k]; // z,y,x voxel网格坐标
}
}
num = num_points_per_voxel_rw(voxelidx); // voxel的点个数,初始为0
if (num < max_points) { // 10
voxel_point_mask_rw(voxelidx, num) = DType(1); // 1
for (int k = 0; k < num_features; ++k) { // 特征维度遍历
voxels_rw(voxelidx, num, k) = points_rw(i, k);// voxel特征[60000,10,5]
}
num_points_per_voxel_rw(voxelidx) += 1;
}
}
for (int i = 0; i < voxel_num; ++i) {
coor_to_voxelidx_rw(coors_rw(i, 0), coors_rw(i, 1), coors_rw(i, 2)) = -1; // 对存在的voxel网格坐标 coor_to_voxelidx_rw 取1
}
return voxel_num;
}
mutable_unchecked
是对维度进行check
,具体看代码注释