|
|
|
|
|
""" Utility functions for processing point clouds. |
|
|
|
Author: Charles R. Qi and Or Litany |
|
""" |
|
|
|
import os |
|
import sys |
|
import torch |
|
|
|
|
|
import numpy as np |
|
from plyfile import PlyData, PlyElement |
|
|
|
|
|
import trimesh |
|
|
|
|
|
MEAN_COLOR_RGB = np.array([109.8, 97.2, 83.8]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
def random_sampling(pc, num_sample, replace=None, return_choices=False): |
|
"""Input is NxC, output is num_samplexC""" |
|
if replace is None: |
|
replace = pc.shape[0] < num_sample |
|
choices = np.random.choice(pc.shape[0], num_sample, replace=replace) |
|
if return_choices: |
|
return pc[choices], choices |
|
else: |
|
return pc[choices] |
|
|
|
|
|
def check_aspect(crop_range, aspect_min): |
|
xy_aspect = np.min(crop_range[:2]) / np.max(crop_range[:2]) |
|
xz_aspect = np.min(crop_range[[0, 2]]) / np.max(crop_range[[0, 2]]) |
|
yz_aspect = np.min(crop_range[1:]) / np.max(crop_range[1:]) |
|
return ( |
|
(xy_aspect >= aspect_min) |
|
or (xz_aspect >= aspect_min) |
|
or (yz_aspect >= aspect_min) |
|
) |
|
|
|
|
|
class RandomCuboid(object): |
|
""" |
|
RandomCuboid augmentation from DepthContrast [https://arxiv.org/abs/2101.02691] |
|
We slightly modify this operation to account for object detection. |
|
This augmentation randomly crops a cuboid from the input and |
|
ensures that the cropped cuboid contains at least one bounding box |
|
""" |
|
|
|
def __init__( |
|
self, |
|
min_points, |
|
aspect=0.8, |
|
min_crop=0.5, |
|
max_crop=1.0, |
|
box_filter_policy="center", |
|
): |
|
self.aspect = aspect |
|
self.min_crop = min_crop |
|
self.max_crop = max_crop |
|
self.min_points = min_points |
|
self.box_filter_policy = box_filter_policy |
|
|
|
def __call__(self, point_cloud, target_boxes, per_point_labels=None): |
|
range_xyz = np.max(point_cloud[:, 0:3], axis=0) - np.min( |
|
point_cloud[:, 0:3], axis=0 |
|
) |
|
|
|
for _ in range(100): |
|
crop_range = self.min_crop + np.random.rand(3) * ( |
|
self.max_crop - self.min_crop |
|
) |
|
if not check_aspect(crop_range, self.aspect): |
|
continue |
|
|
|
sample_center = point_cloud[np.random.choice(len(point_cloud)), 0:3] |
|
|
|
new_range = range_xyz * crop_range / 2.0 |
|
|
|
max_xyz = sample_center + new_range |
|
min_xyz = sample_center - new_range |
|
|
|
upper_idx = ( |
|
np.sum((point_cloud[:, 0:3] <= max_xyz).astype(np.int32), 1) == 3 |
|
) |
|
lower_idx = ( |
|
np.sum((point_cloud[:, 0:3] >= min_xyz).astype(np.int32), 1) == 3 |
|
) |
|
|
|
new_pointidx = (upper_idx) & (lower_idx) |
|
|
|
if np.sum(new_pointidx) < self.min_points: |
|
continue |
|
|
|
new_point_cloud = point_cloud[new_pointidx, :] |
|
|
|
|
|
if self.box_filter_policy == "center": |
|
|
|
new_boxes = target_boxes |
|
if ( |
|
target_boxes.sum() > 0 |
|
): |
|
box_centers = target_boxes[:, 0:3] |
|
new_pc_min_max = np.min(new_point_cloud[:, 0:3], axis=0), np.max( |
|
new_point_cloud[:, 0:3], axis=0 |
|
) |
|
keep_boxes = np.logical_and( |
|
np.all(box_centers >= new_pc_min_max[0], axis=1), |
|
np.all(box_centers <= new_pc_min_max[1], axis=1), |
|
) |
|
if keep_boxes.sum() == 0: |
|
|
|
continue |
|
new_boxes = target_boxes[keep_boxes] |
|
if per_point_labels is not None: |
|
new_per_point_labels = [x[new_pointidx] for x in per_point_labels] |
|
else: |
|
new_per_point_labels = None |
|
|
|
return new_point_cloud, new_boxes, new_per_point_labels |
|
|
|
|
|
return point_cloud, target_boxes, per_point_labels |
|
|