3D点云数据转为俯瞰图Python实现代码

我主要是参考了英文博客来撰写本篇文章,仅作为个人学习笔记参考使用。

一、点云数据

点云数据应该表示为一个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()

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_51143578/article/details/130823784