首先我们看一下example在哪里用到的,然后反推一下:
可以看到exaple是从eval_dataloader迭代器里面进行引用的,这是常规操作,然后看一下eval_dataloader的生成过程:
eval_dataloader = torch.utils.data.DataLoader(
eval_dataset,
batch_size=input_cfg.batch_size,
shuffle=False,
num_workers=input_cfg.num_workers,
pin_memory=False,
collate_fn=merge_second_batch)
然后我们看下eval_dataset是如何来的:
eval_dataset = input_reader_builder.build(
input_cfg,
model_cfg,
training=False,
voxel_generator=voxel_generator,
target_assigner=target_assigner)
这里来进行dataset的建立,我们传入了voxel_generator和target_assigner.
那就先看一下它俩:
1.voxel_generator:通过voxel_builder.build(model_cfg.voxel_generator)
来建立,然后调用VoxelGenerator()这个类的构造函数定义一些函数等但是这里面的generate还没被调用,这里面定义好后后面会在input_reader_builder.build()里面用到,最终接收到的是A tensor dict based on the input_reader_config.
;
class VoxelGenerator:
def __init__(self,
voxel_size,
point_cloud_range,
max_num_points,
max_voxels=20000):
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)
self._voxel_size = voxel_size
self._point_cloud_range = point_cloud_range
self._max_num_points = max_num_points
self._max_voxels = max_voxels
self._grid_size = grid_size
def generate(self, points, max_voxels):
return points_to_voxel(
points, self._voxel_size, self._point_cloud_range,
self._max_num_points, True, max_voxels)
接着我们进入到:input_reader_builder.build
这个函数
2. target_assigner:target_assigner_builder.build(..)
建立,
其中box_coder是由 box_coder = box_coder_builder.build(..)
建立
box_coder: {
ground_box3d_coder: {
linear_dim: false
encode_angle_vector: false
}
}
定义了一大串函数....
最后进入target_assigner = target_assigner_builder.build(..)
调用build函数:
def build(input_reader_config,
model_config,
training,
voxel_generator,
target_assigner=None) -> DatasetWrapper:
"""Builds a tensor dictionary based on the InputReader config.
Args:
input_reader_config: A input_reader_pb2.InputReader object.
Returns:
A tensor dict based on the input_reader_config.
Raises:
ValueError: On invalid input reader proto.
ValueError: If no input paths are specified.
"""
if not isinstance(input_reader_config, input_reader_pb2.InputReader):
raise ValueError('input_reader_config not of type '
'input_reader_pb2.InputReader.')
dataset = dataset_builder.build(input_reader_config, model_config,
training, voxel_generator, target_assigner)
dataset = DatasetWrapper(dataset)
return dataset
同样的主要还是调用:dataset_builder.build()
这个函数:先从配置文件读取一些参数:
这里有个重要的函数:prep_func = partial(…)
关于python的偏函数可以看这里:偏函数
prep_pointcloud的这个函数,大多数我们想要的到的example参数使用过这个函数得到的,首先我们传入大多数的参数值,这里面我们会先进入到KittiDataset()这个函数读取我们的配置文件,比如info_path.
1.anchor的生成:
target_assigner.generate_anchors(feature_map_size)
进入的我们的anchor生成,中间步骤挺繁琐…,最终生成1x248x216x2=107136个anchor,每个anchor有7个值,长宽高,中心点,航向,返回的anchors=(1, 248, 216, 2, 7),最后reshape成
令大家需要知道的是dataset = KittiDataset(…)这个函数返回的是含有pkl信息的kitti_infos和pre_func函数,这里面还有一个内置函数:
def __getitem__(self, idx):
return _read_and_prep_v9(
info=self._kitti_infos[idx],
root_path=self._root_path,
num_point_features=self._num_point_features,
prep_func=self._prep_func)
实际上我们通过dataloader进行索引的时候最终调用的是这个函数,其实之前的都是在做准备工作,在进行dataloader迭代的时候我们会调用_getitem_这个函数,在里面还会调用到pre_func函数,最终返回一个example.
接下来我们仔细看一下read_and_prep_v9()
在干嘛:
1.读取infos里面的velodyne_path的路径;
2.读取点云信息points = np.fromfile()
这时候下面的example这些信息就已经有了,剩余的一些需要生成.
input_dict = {
'points': points,
'rect': rect,
'Trv2c': Trv2c,
'P2': P2,
'image_shape': np.array(info["img_shape"], dtype=np.int32),
'image_idx': image_idx,
'image_path': info['img_path'],
'gt_boxes': gt_boxes,
'gt_names': gt_names,
'difficulty': difficulty,
'group_ids' = group_ids,
# 'pointcloud_num_features': num_point_features,
}
3.进入prep_func(input_dict=input_dict)函数,函数说明:convert point cloud to voxels, create targets if ground truths exists.
4.执行voxels, coordinates, num_points = voxel_generator.generate(..)
函数.里面执行的是points_to_voxel()
这个函数,说明如下:
Returns:
voxels: [M, max_points, ndim] float tensor. only contain points.
coordinates: [M, 3] int32 tensor.
num_points_per_voxel: [M] int32 tensor
name | shape | info |
---|---|---|
voxels | [M, max_points, ndim] [voxel_nums,100,4] | 含有点云的voxel以及信息 |
coordinates | [M, 3] -> [voxel_nums,3] | 每个点的XYZ在map上的索引 |
num_points_per_voxel | [voxel_nums] | 每个voxel内点的个数 |
随后这些信息会增添到example里面.
至此,我们网络需要的example已经完全Done了;
接下来我们进行网络部分的解析:
Pointpillars —数据流动篇
PS:
我们通过create_data.py得到了各种pkl文件.我们生成的pkl文件有25个属性:{‘image_idx’,‘pointcloud_num_features’,‘velodyne_path’’,img_path’:,‘img_shape’,‘calib/P0/p1/p2/p3/R0_rect_Tr/velo_to_cam/Tr_imu_to_velo’,‘annos’’,‘truncated’,‘occluded’,‘alpha’,‘bbox’,‘dimensions’,‘location’,‘rotation_y’,‘score’,‘index’,‘group_ids’,‘difficulty’,‘num_points_in_gt’}
这里我们可以注意一下下面的操作,为什么要gridsize要除以2得到features呢?
:
#grid_size [432,496,1]
#feature_size [1,248,216]
grid_size = voxel_generator.grid_size
feature_map_size = grid_size[:2] // out_size_factor
feature_map_size = [*feature_map_size, 1][::-1]
随后会把之前生成的anchor添加到example里面,anchor的生成还有些没看懂,后面再详细补充, 这里是通过partial函数传入的.
待补充:anchor及anchor_mask的生成过程
这是anchor的一些东西:
读取配置:
target_assigner: {
anchor_generators: {
anchor_generator_stride: {
sizes: [1.6, 3.9, 1.56] # wlh
strides: [0.32, 0.32, 0.0] # if generate only 1 z_center, z_stride will be ignored
offsets: [0.16, -39.52, -1.78] # origin_offset + strides / 2
rotations: [0, 1.57] # 0, pi/2
matched_threshold : 0.6
unmatched_threshold : 0.45
}
}
sample_positive_fraction : -1
sample_size : 512
region_similarity_calculator: {
nearest_iou_similarity: {
}
}
}
文章内容部分参考:
1.pointpillars代码阅读----prep_pointcloud篇