点云数据一般表示为N行,至少三列的numpy数组。每行对应一个单独的点,所以使用至少3个值的空间位置点(X, Y, Z)来表示。
如果点云数据来自于激光雷达传感器,那么它可能有每个点的附加值,在KITTI数据中就有一个“反射率”,这是衡量激光光束在那个位置被反射回来了多少。所以在KITTI数据中,其点云数据就是N*4的矩阵。
图像的坐标轴和点云的坐标轴是不同的,下图显示了蓝色的图像轴和橙色的点云轴。
关于图像坐标轴:
1、图像中的坐标值总是正的。
2、原点位于左上角。
3、坐标是整数值。
关于点云坐标轴:
1、点云的坐标值可以是正数也可以是负数。
2、坐标是实数。
3、正X轴代表前方。
4、正Y轴代表左边。
5、正Z轴代表上方。
如上图,鸟瞰图中的点云的相关轴为X和Y轴。同时,要考虑以下几点:
1、X和Y轴意味着是相反的
2、X和Y轴指向相反方向的
3、将点云的数值转换到图像上,使得(0, 0)是图像中最小的值。
将注意力集中于点云的特殊区域通常是有用的,对此,需要创建一个过滤器,保留感兴趣区域内的点。
矩形区域设置为距离原点两侧10米,前方20米。
side_range=(-10, 10) # 最左到最右
fwd_range=(0, 20) # 最后到最前
创建一个过滤器,只保留位于我们指定的矩形内的点
# 提取每个轴的点数
x_points = points[:, 0]
y_points = points[:, 1]
z_points = points[:, 2]
# 过滤器 - 仅返回立方体内点的索引
# 三个过滤器用于前后,左右,高度范围
# 雷达坐标系中左侧是正Y轴
f_filt = np.logical_and((x_points > fwd_range[0]), (x_points < fwd_range[1]))
s_filt = np.logical_and((y_points > -side_range[1]), (y_points < -side_range[0]))
filter = np.logical_and(f_filt, s_filt)
indices = np.argwhere(filter).flatten()
# 保留的点
x_points = x_points[indices]
y_points = y_points[indices]
z_points = z_points[indices]
在点云数据中,其数据为实数,而映射到的图像数据为整数,若只是单纯的取整,将会丢失很多分辨率。
如果计量单位是米,想要一个5厘米的分辨率,则:
res = 0.05 # 转换为像素位置的值 - 基于分辨率
x_img = (-y_points / res).astype(np.int32) # X轴在点云坐标系-Y上
y_img = (-x_points / res).astype(np.int32) # Y轴在点云坐标系-X上
因为x, y 仍然有负值,所以需要移动数据使得(0, 0)为最小值。
x_img -= int(np.floor(side_range[0] / res))
y_img += int(np.ceil(fwd_range[1] / res))
将点云数据(Z轴,代表高度)填充到图像像素上去。设置一个想要关注的高度值范围,高于或低于该范围内的值都会被剪切到最大值和最小值,这能从感兴趣区域获得大量的细节。
根据点的高度填充到图像上,调整在0-255之间。
height_range = (-2, 0.5) # 最低到最高
# 剪切高度值
pixel_values = np.clip(a = z_points,
a_min=height_range[0],
a_max=height_range[1])
将值调整在0-255
def scale_to_255(a, min, max, dtype=np.uint8):
""" 将指定的最小值,最大值范围的值数组缩放到0-255
可以指定输出的数据类型(默认是uint8)
"""
return (((a - min) / float(max - min)) * 255).astype(dtype)
# 调整高度值
pixel_values = scale_to_255(pixel_values, min=height_range[0], max=height_range[1])
# INITIALIZE EMPTY ARRAY - of the dimensions we want
x_max = 1+int((side_range[1] - side_range[0])/res)
y_max = 1+int((fwd_range[1] - fwd_range[0])/res)
im = np.zeros([y_max, x_max], dtype=np.uint8)
# FILL PIXEL VALUES IN IMAGE ARRAY
im[y_img, x_img] = pixel_values
from PIL import Image
im2 = Image.fromarray(im)
im2.show()
光谱色彩映射
import matplotlib.pyplot as plt
plt.imshow(im, cmap="spectral", vmin=0, vmax=255)
plt.show()
import numpy as np
# ==============================================================================
# SCALE_TO_255
# ==============================================================================
def scale_to_255(a, min, max, dtype=np.uint8):
""" Scales an array of values from specified min, max range to 0-255
Optionally specify the data type of the output (default is uint8)
"""
return (((a - min) / float(max - min)) * 255).astype(dtype)
# ==============================================================================
# POINT_CLOUD_2_BIRDSEYE
# ==============================================================================
def point_cloud_2_birdseye(points,
res=0.1,
side_range=(-10., 10.), # left-most to right-most
fwd_range = (-10., 10.), # back-most to forward-most
height_range=(-2., 2.), # bottom-most to upper-most
):
""" Creates an 2D birds eye view representation of the point cloud data.
Args:
points: (numpy array)
N rows of points data
Each point should be specified by at least 3 elements x,y,z
res: (float)
Desired resolution in metres to use. Each output pixel will
represent an square region res x res in size.
side_range: (tuple of two floats)
(-left, right) in metres
left and right limits of rectangle to look at.
fwd_range: (tuple of two floats)
(-behind, front) in metres
back and front limits of rectangle to look at.
height_range: (tuple of two floats)
(min, max) heights (in metres) relative to the origin.
All height values will be clipped to this min and max value,
such that anything below min will be truncated to min, and
the same for values above max.
Returns:
2D numpy array representing an image of the birds eye view.
"""
# EXTRACT THE POINTS FOR EACH AXIS
x_points = points[:, 0]
y_points = points[:, 1]
z_points = points[:, 2]
# FILTER - To return only indices of points within desired cube
# Three filters for: Front-to-back, side-to-side, and height ranges
# Note left side is positive y axis in LIDAR coordinates
f_filt = np.logical_and((x_points > fwd_range[0]), (x_points < fwd_range[1]))
s_filt = np.logical_and((y_points > -side_range[1]), (y_points < -side_range[0]))
filter = np.logical_and(f_filt, s_filt)
indices = np.argwhere(filter).flatten()
# KEEPERS
x_points = x_points[indices]
y_points = y_points[indices]
z_points = z_points[indices]
# CONVERT TO PIXEL POSITION VALUES - Based on resolution
x_img = (-y_points / res).astype(np.int32) # x axis is -y in LIDAR
y_img = (-x_points / res).astype(np.int32) # y axis is -x in LIDAR
# SHIFT PIXELS TO HAVE MINIMUM BE (0,0)
# floor & ceil used to prevent anything being rounded to below 0 after shift
x_img -= int(np.floor(side_range[0] / res))
y_img += int(np.ceil(fwd_range[1] / res))
# CLIP HEIGHT VALUES - to between min and max heights
pixel_values = np.clip(a=z_points,
a_min=height_range[0],
a_max=height_range[1])
# RESCALE THE HEIGHT VALUES - to be between the range 0-255
pixel_values = scale_to_255(pixel_values,
min=height_range[0],
max=height_range[1])
# INITIALIZE EMPTY ARRAY - of the dimensions we want
x_max = 1 + int((side_range[1] - side_range[0]) / res)
y_max = 1 + int((fwd_range[1] - fwd_range[0]) / res)
im = np.zeros([y_max, x_max], dtype=np.uint8)
# FILL PIXEL VALUES IN IMAGE ARRAY
im[y_img, x_img] = pixel_values
return im
特别说明:本文为本人学习所做笔记。
具体参考:http://ronny.rest/tutorials/module/pointclouds_01/point_cloud_birdseye/