生成voxel——spconv源码剖析(六)

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()创建一个函数,当从pythonimport调用时,将调用该函数。

  • 模块名称(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,具体看代码注释

你可能感兴趣的:(模型部署,3D点云,python,numpy,开发语言)