深度图转3D点云

从立体相机到 3D 传感器的许多设备都可以深度图的形式提供有关它们正在捕获的场景的距离信息。 为了从此类图像中获取有意义的数据,通常需要将这些深度图转换为 3D 点云。

在这篇文章中,我们将使用 Microsoft Kinect V2 获得的深度帧执行此类转换,但所描述的方法可以轻松移植到其他硬件。 尽管有非常优雅的数学方法可以做到这一点,但我们仍将坚持一些基本的三角学。

首先,什么是深度图? 根据维基百科关于它的文章:

深度图是包含与场景对象表面距视点的距离有关的信息的图像或图像通道。

这些信息可用于图像处理、地图绘制、导航等领域的各种应用。 对于其中一些应用程序,需要导出每个样本的 3D 坐标 (x, y, z),以获得可直接使用的点云。

对于使用 3D 相机生成的深度图,每个像素的深度值可以直接分配给 z 坐标。 只要知道相机的FOV角度(水平和垂直),其他两个坐标就可以很容易计算出来。

1、计算X坐标

考虑以下使用 3D 相机捕获深度样本的示意图。 从物体 i(图中的红点)到相机传感器的距离 d_i(与 x 轴正交)是作为像素值存储在深度图中的距离。

获得所述样本i的x坐标即计算出物体与传感器中心之间的水平距离Δ_x。 这可以通过以下方式完成:

其中 d_i 是样本 i 的深度距离,γ 是物体 i 与传感器之间的角度。 如何计算角度γ_i?

假设深度图的每个像素列代表一个与水平视野角 θ_h 成线性比例的角度 γ_i,我们有:

其中c_i是深度图中样本i的列,n_c是列总数,α是第一个样本的角度。

对于 Kinect V2:

2、计算Y坐标

对于 y 坐标,分析非常相似,因此我们有:

以下 C++ 代码片段展示了如何从文件打开深度图并将其转换为 3D 点云:

FILE* inFile;
//read the file
fopen_s(&inFile,"depth.txt", "rb");
fread(d, sizeof(UINT16), nVertices, inFile);
fclose(inFile);

for(int i=0 ; i < nVertices ; i++)
{
    int r_i = i / (int)n_c;
    int c_i = i % (int)n_c;

    //normalize depth
    float d_i = (float)d[i] / (float)MAX_DEPTH;
    //color
    vertices[i].a = 255.;
    //the color of the point is a shade of gray proportional to the depth
    vertices[i].b = vertices[i].g = vertices[i].r = d_i;

    //calculate x-coordinate
    float alpha_h = (M_PI - theta_h) / 2;
    float gamma_i_h = alpha_h + (float)c_i*(theta_h / n_c);
    vertices[i].x = d_i / tan(gamma_i_h);

    //calculate y-coordinate
    float alpha_v = 2 * M_PI - (theta_v / 2);
    float gamma_i_v = alpha_v + (float)r_i*(theta_v / n_r);
    vertices[i].y = d_i * tan(gamma_i_v)*-1;

    //z-coordinate
    vertices[i].z = d_i;
}

你可以从此链接下载 Visual Studio 解决方案。

关于源代码的一些重要说明:

  • 该文件夹包含一个名为 height.txt 的二进制文件,其中包含 217088 个 16 位无符号整数,表示来自 Kinect V2 的 512 x 424 深度帧。 文件 height.bmp 包含同一帧的 BMP 表示形式。
  • Kinect V2 的水平和垂直 FOV 分别约为 70°(1.22 弧度)和 60°(1.04 弧度)。
    升级到 Visual Studio 2017 后,在调试模式下构建会触发一些 DirectX 断言语句。 解决方法是在发布模式下构建。
  • 可以使用键盘上的箭头和 W、A、S、D 键导航渲染的场景。

原文链接:Transforming a depth map into a 3D point cloud

BimAnt翻译整理,转载请标明出处