import math import numpy as np def pcd2coord2d(pcd, fov, depth_range, labels=None): # laser parameters fov_up = fov[0] / 180.0 * np.pi # field of view up in rad fov_down = fov[1] / 180.0 * np.pi # field of view down in rad fov_range = abs(fov_down) + abs(fov_up) # get field of view total in rad # get depth (distance) of all points depth = np.linalg.norm(pcd, 2, axis=-1) # mask points out of range mask = np.logical_and(depth > depth_range[0], depth < depth_range[1]) if pcd.ndim == 3: mask = mask.all(axis=1) depth, pcd = depth[mask], pcd[mask] # get scan components scan_x, scan_y, scan_z = pcd[..., 0], pcd[..., 1], pcd[..., 2] # get angles of all points yaw = -np.arctan2(scan_y, scan_x) pitch = np.arcsin(scan_z / depth) # get projections in image coords proj_x = np.clip(0.5 * (yaw / np.pi + 1.0), 0., 1.) # in [0.0, 1.0] proj_y = np.clip(1.0 - (pitch + abs(fov_down)) / fov_range, 0., 1.) # in [0.0, 1.0] proj_coord2d = np.stack([proj_x, proj_y], axis=-1) if labels is not None: proj_labels = labels[mask] else: proj_labels = None return proj_coord2d, proj_labels def pcd2range(pcd, size, fov, depth_range, remission=None, labels=None, **kwargs): # laser parameters fov_up = fov[0] / 180.0 * np.pi # field of view up in rad fov_down = fov[1] / 180.0 * np.pi # field of view down in rad fov_range = abs(fov_down) + abs(fov_up) # get field of view total in rad # get depth (distance) of all points depth = np.linalg.norm(pcd, 2, axis=1) # mask points out of range mask = np.logical_and(depth > depth_range[0], depth < depth_range[1]) depth, pcd = depth[mask], pcd[mask] # get scan components scan_x, scan_y, scan_z = pcd[:, 0], pcd[:, 1], pcd[:, 2] # get angles of all points yaw = -np.arctan2(scan_y, scan_x) pitch = np.arcsin(scan_z / depth) # get projections in image coords proj_x = 0.5 * (yaw / np.pi + 1.0) # in [0.0, 1.0] proj_y = 1.0 - (pitch + abs(fov_down)) / fov_range # in [0.0, 1.0] # scale to image size using angular resolution proj_x *= size[1] # in [0.0, W] proj_y *= size[0] # in [0.0, H] # round and clamp for use as index proj_x = np.maximum(0, np.minimum(size[1] - 1, np.floor(proj_x))).astype(np.int32) # in [0,W-1] proj_y = np.maximum(0, np.minimum(size[0] - 1, np.floor(proj_y))).astype(np.int32) # in [0,H-1] # order in decreasing depth order = np.argsort(depth)[::-1] proj_x, proj_y = proj_x[order], proj_y[order] # project depth depth = depth[order] proj_range = np.full(size, -1, dtype=np.float32) proj_range[proj_y, proj_x] = depth # project point feature if remission is not None: remission = remission[mask][order] proj_feature = np.full(size, -1, dtype=np.float32) proj_feature[proj_y, proj_x] = remission elif labels is not None: labels = labels[mask][order] proj_feature = np.full(size, 0, dtype=np.float32) proj_feature[proj_y, proj_x] = labels else: proj_feature = None return proj_range, proj_feature def range2pcd(range_img, fov, depth_range, depth_scale, log_scale=True, label=None, color=None, **kwargs): # laser parameters size = range_img.shape fov_up = fov[0] / 180.0 * np.pi # field of view up in rad fov_down = fov[1] / 180.0 * np.pi # field of view down in rad fov_range = abs(fov_down) + abs(fov_up) # get field of view total in rad # inverse transform from depth depth = (range_img * depth_scale).flatten() if log_scale: depth = np.exp2(depth) - 1 scan_x, scan_y = np.meshgrid(np.arange(size[1]), np.arange(size[0])) scan_x = scan_x.astype(np.float64) / size[1] scan_y = scan_y.astype(np.float64) / size[0] yaw = (np.pi * (scan_x * 2 - 1)).flatten() pitch = ((1.0 - scan_y) * fov_range - abs(fov_down)).flatten() pcd = np.zeros((len(yaw), 3)) pcd[:, 0] = np.cos(yaw) * np.cos(pitch) * depth pcd[:, 1] = -np.sin(yaw) * np.cos(pitch) * depth pcd[:, 2] = np.sin(pitch) * depth # mask out invalid points mask = np.logical_and(depth > depth_range[0], depth < depth_range[1]) pcd = pcd[mask, :] # label if label is not None: label = label.flatten()[mask] # default point color if color is not None: color = color.reshape(-1, 3)[mask, :] else: color = np.ones((pcd.shape[0], 3)) * [0.7, 0.7, 1] return pcd, color, label def range2xyz(range_img, fov, depth_range, depth_scale, log_scale=True, **kwargs): # laser parameters size = range_img.shape fov_up = fov[0] / 180.0 * np.pi # field of view up in rad fov_down = fov[1] / 180.0 * np.pi # field of view down in rad fov_range = abs(fov_down) + abs(fov_up) # get field of view total in rad # inverse transform from depth if log_scale: depth = (np.exp2(range_img * depth_scale) - 1) else: depth = range_img scan_x, scan_y = np.meshgrid(np.arange(size[1]), np.arange(size[0])) scan_x = scan_x.astype(np.float64) / size[1] scan_y = scan_y.astype(np.float64) / size[0] yaw = np.pi * (scan_x * 2 - 1) pitch = (1.0 - scan_y) * fov_range - abs(fov_down) xyz = -np.ones((3, *size)) xyz[0] = np.cos(yaw) * np.cos(pitch) * depth xyz[1] = -np.sin(yaw) * np.cos(pitch) * depth xyz[2] = np.sin(pitch) * depth # mask out invalid points mask = np.logical_and(depth > depth_range[0], depth < depth_range[1]) xyz[:, ~mask] = -1 return xyz def pcd2bev(pcd, x_range, y_range, z_range, resolution, **kwargs): # mask out invalid points mask_x = np.logical_and(pcd[:, 0] > x_range[0], pcd[:, 0] < x_range[1]) mask_y = np.logical_and(pcd[:, 1] > y_range[0], pcd[:, 1] < y_range[1]) mask_z = np.logical_and(pcd[:, 2] > z_range[0], pcd[:, 2] < z_range[1]) mask = mask_x & mask_y & mask_z pcd = pcd[mask] # points to bev coords bev_x = np.floor((pcd[:, 0] - x_range[0]) / resolution).astype(np.int32) bev_y = np.floor((pcd[:, 1] - y_range[0]) / resolution).astype(np.int32) # 2D bev grid bev_shape = (math.ceil((x_range[1] - x_range[0]) // resolution), math.ceil((y_range[1] - y_range[0]) // resolution)) bev_grid = np.zeros(bev_shape, dtype=np.float64) # populate the BEV grid with bev coords bev_grid[bev_x, bev_y] = 1 return bev_grid if __name__ == '__main__': # test = np.loadtxt('test_range.txt') # pcd, _, _ = range2pcd(test, (32, 1024), (10, -30)) # np.savetxt('test_pcd.txt', pcd, fmt='%.4f') # import matplotlib.pyplot as plt # pcd = np.loadtxt('test_origin.txt') # bev_grid = pcd2bev(pcd) # plt.imshow(bev_grid[:, :, 0], cmap='gray') # Display the BEV for the first height level # plt.savefig('test.png', dpi=300, bbox_inches='tight', pad_inches=0, transparent=True) from PIL import Image img = Image.open('assets/kitti/range.png') img.convert('L') img = np.array(img) / 255.