我主要是参考了英文博客来撰写本篇文章,仅作为个人学习笔记参考使用。
文章目录
- 一、点云数据
- 二、图像与点云坐标
- 三、创建点云数据的鸟瞰视图
- 3.1 鸟瞰图的相关坐标轴
- 3.2 限制点云数据范围
- 3.3 将点位置映射到像素位置
- 3.4 切换到新的零点
- 3.5 像素值
- 3.6 创建图像矩阵
- 3.7 显示
一、点云数据
点云数据应该表示为一个numpy数组,有 N N N 行,至少 3 3 3 列。每一行对应一个点,用至少 3 3 3 个值表示它在空间中的位置 ( x , y , z ) (x,y,z) (x,y,z)。
如果点云数据来自激光雷达传感器,那么它可能会为每个点提供额外的值,例如“反射率”,这是对该位置障碍物反射回多少激光光束的测度。在这种情况下,点云数据可能是一个 N × 4 N\times 4 N×4 数组。
二、图像与点云坐标
关于图像需要注意的一些重要事项:
-
图像中的坐标值总是正的。
-
原点位于左上角。
-
坐标是整数值。
关于点云坐标需要注意的事项:
-
点云中的坐标值可以是正的,也可以是负的。
-
坐标可以取实数。
-
正x轴代表向前。
-
正y轴代表左。
-
正z轴代表向上。
三、创建点云数据的鸟瞰视图
3.1 鸟瞰图的相关坐标轴
为了创建鸟瞰图像,点云数据的相关轴将是x轴和y轴。
然而,正如我们从上图中看到的,我们必须小心,并考虑到以下几点:
-
x轴和y轴的意思正好相反。
-
x和y轴指向相反的方向。
-
你必须移动这些值,使 ( 0 , 0 ) (0,0) (0,0) 成为图像中最小的坐标值。
3.2 限制点云数据范围
通常只关注点云的特定区域是有用的。因此,我们想要创建一个过滤器,它只保留我们感兴趣的区域内的点。
因为我们是从顶部看数据,我们目的在于将其转换为图像,我将使用与图像轴更一致的方向。下面,我指定了相对于原点我想要关注的值范围。原点左边的任何东西都被认为是负的,右边的任何东西都被认为是正的。点云的x轴将被解释为向前方向(这将是我们的鸟瞰图像的向上方向)。
下面的代码将感兴趣的矩形设置为在原点两侧的跨度为10m,原点向前面的跨度为20m。
side_range=(-10, 10) # left-most to right-most
fwd_range=(0, 20) # back-most to forward-most
接下来,我们创建一个过滤器,只保留实际位于我们指定的矩形内的点。
# 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]
3.3 将点位置映射到像素位置
现在,我们有一堆取实数值的点。以便将这些值映射到整型位置值。我们可以简单地将所有的x和y值类型转换为整数,但最终可能会失去很多分辨率。例如,如果这些点的测量单位是米,那么每个像素将代表点云中 1 × 1 1\times1 1×1 米的矩形,我们将失去比这更小的细节。如果你有一个像山景一样的点云,这可能很好。但如果你想捕捉更精细的细节,识别人类、汽车,甚至更小的东西,那么这种方法就不好了。
然而,上面的方法可以稍微修改一下,这样我们就可以得到我们想要的分辨率。在类型转换为整数之前,我们可以先缩放数据。例如,如果测量单位是米,我们想要5cm的分辨率,我们可以这样做:
res = 0.05
# 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
你可能已经注意到x轴和y轴被交换了,方向颠倒了,这样我们就可以开始处理图像坐标了。
3.4 切换到新的零点
x和y数据还没有完全准备好映射到图像。我们可能还有负的x和y值。所以我们需要移动数据使(0,0)成为最小值。
# SHIFT PIXELS TO HAVE MINIMUM BE (0,0)
# floor and 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))
height_range = (-2, 0.5) # bottom-most to upper-most
# 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])
3.5 像素值
因此,我们已经使用点数据来指定图像中的 x 和 y 位置。我们现在需要做的是指定我们想要用什么值来填充这些像素位置。一种可能性是用高度数据填充它。两件事一定记住的是:
-
像素值应该是整数。
-
像素值应该是介于0-255之间的值。
我们可以从数据中获取最小和最大高度值,并重新缩放该范围以适应 0~255 的范围。另一种方法,这里将使用的是设置我们想要集中的高度值的范围,并且高于或低于该范围的任何东西都被剪辑为最小和最大值。这是有用的,因为它允许我们从感兴趣的区域获得最大数量的细节。
height_range = (-2, 0.5) # bottom-most to upper-most
# 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])
接下来,我们将这些值重新调整为0-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)
# 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])
3.6 创建图像矩阵
现在我们已经准备好实际创建图像了,我们只需要初始化一个数组,它的尺寸取决于我们想要的图像阵列的的范围和我们选择的分辨率。然后我们使用转换为像素位置的 x 和 y 点值来指定数组中的索引,并将我们在上一小节中选择的作为像素值的值分配给这些索引。
# 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
3.7 显示
目前,图像被存储为numpy数组。如果我们希望可视化它,我们可以将其转换为PIL图像,并查看它。
# CONVERT FROM NUMPY ARRAY TO A PIL IMAGE
from PIL import Image
im2 = Image.fromarray(im)
im2.show()