使用 Open3D 从图像生成点云时出现黑屏

Blank screen when generating point cloud from image with Open3D

所以我尝试使用 python 中的 Open3D 库创建点云,最后,它基本上只是 中引用的 2 行,但是当我运行 我的代码(见下文)我得到的只是一个弹出的白屏。 我在 Jupyter notebook 中 运行 它,但是 运行 在 python 来自控制台的脚本没有改变任何东西,也没有抛出错误。 我应该提到我在 Blender 中创建了图像并将其保存为 OpenExr,这意味着深度值 运行ge 在 0 和 4 之间(我已经 t运行 将其设置为 4 作为背景)。您可以在下面看到它们是正确的图像,我也可以将它们运行转换为 Open3D 图片并毫无问题地显示它们。

编辑 (27-03-2020):添加了完整的最小示例

import OpenEXR
import numpy as np
import array
import matplotlib.pyplot as plt
import open3d as o3d
%matplotlib inline

exr_img = OpenEXR.InputFile('frame0100.exr')

depth_img = array.array('f', exr_img.channel('View Layer.Depth.Z'))
r_img = array.array('f', exr_img.channel('View Layer.Combined.R'))
g_img = array.array('f', exr_img.channel('View Layer.Combined.G'))
b_img = array.array('f', exr_img.channel('View Layer.Combined.B'))

def reshape_img(img):
    return np.array(img).reshape(720, 1280)

img_array = np.dstack((reshape_img(r_img),
                     reshape_img(g_img),
                     reshape_img(b_img),
                     reshape_img(depth_img)))

#Background returns very large value, truncate it to 4
img_array[img_array == 10000000000.0] = 4

colour = o3d.geometry.Image(np.array(img_array[:, :, :3]))
depth = o3d.geometry.Image(np.array(img_array[:, :, 3]*1000).astype('uint16'))
o3d.draw_geometries([depth])

pinhole_cam = o3d.open3d.camera.PinholeCameraIntrinsic(width= 1280, height=720, cx=640,cy=360,fx=500,fy=500)

rgbd = o3d.create_rgbd_image_from_color_and_depth(colour, depth, convert_rgb_to_intensity = False, depth_scale=1000)
pcd = o3d.create_point_cloud_from_rgbd_image(rgbd, pinhole_cam)

o3d.draw_geometries([pcd])

请忽略导入数据的方式,因为我是 Open3D 的新手并且自己生成数据,我一步一步地进行检查和确认数据完整性

我想这可能与我的针孔相机参数有关。 老实说,我不知道什么是正确的参数,除了 cy,cy 应该是图像和fx的中心,fy应该合理。由于我的深度值以 Blender 米为单位,但 Open3D 显然需要毫米,因此缩放应该有意义。

如果您能帮助我调试它,我将不胜感激。但是,如果您要向我指出一个更好的工作库的方向,以从图像创建 3D 点云,我也不介意。我在 Open3D 中找到的文档充其量是缺乏的。

简而言之,Open3D 期望您的 3 通道彩色图像是 uint8 类型

否则,它会 return 一个空的点云,导致你看到的空白 window。


2020-3-27 更新,我所在时区的深夜:)

现在您已经提供了代码,让我们开始吧!

从你的函数名称来看,我猜你正在使用 Open3D 0.7.0 或类似的东西。我提供的代码在 0.9.0 中。一些函数名称已更改并添加了 new functionality

当我 运行 你的代码在 0.9.0 中时(当然经过一些小的修改),有一个 RuntimeError:

RuntimeError: [Open3D ERROR] [CreatePointCloudFromRGBDImage] Unsupported image format.

并且我们可以从 Open3D 0.9.0 source 中看出,您的彩色图像必须是 3 个通道并且每个只占用 1 个字节 (uint8)或为1个通道并占用4个字节(浮点数,表示强度图像):

std::shared_ptr<PointCloud> PointCloud::CreateFromRGBDImage(
        const RGBDImage &image,
        const camera::PinholeCameraIntrinsic &intrinsic,
        const Eigen::Matrix4d &extrinsic /* = Eigen::Matrix4d::Identity()*/,
        bool project_valid_depth_only) {
    if (image.depth_.num_of_channels_ == 1 &&
        image.depth_.bytes_per_channel_ == 4) {
        if (image.color_.bytes_per_channel_ == 1 &&
            image.color_.num_of_channels_ == 3) {
            return CreatePointCloudFromRGBDImageT<uint8_t, 3>(
                    image, intrinsic, extrinsic, project_valid_depth_only);
        } else if (image.color_.bytes_per_channel_ == 4 &&
                   image.color_.num_of_channels_ == 1) {
            return CreatePointCloudFromRGBDImageT<float, 1>(
                    image, intrinsic, extrinsic, project_valid_depth_only);
        }
    }
    utility::LogError(
            "[CreatePointCloudFromRGBDImage] Unsupported image format.");
    return std::make_shared<PointCloud>();
}

不然会出现我遇到的错误
但是,在0.7.0的版本中,source code是:

std::shared_ptr<PointCloud> CreatePointCloudFromRGBDImage(
        const RGBDImage &image,
        const camera::PinholeCameraIntrinsic &intrinsic,
        const Eigen::Matrix4d &extrinsic /* = Eigen::Matrix4d::Identity()*/) {
    if (image.depth_.num_of_channels_ == 1 &&
        image.depth_.bytes_per_channel_ == 4) {
        if (image.color_.bytes_per_channel_ == 1 &&
            image.color_.num_of_channels_ == 3) {
            return CreatePointCloudFromRGBDImageT<uint8_t, 3>(image, intrinsic,
                                                              extrinsic);
        } else if (image.color_.bytes_per_channel_ == 4 &&
                   image.color_.num_of_channels_ == 1) {
            return CreatePointCloudFromRGBDImageT<float, 1>(image, intrinsic,
                                                            extrinsic);
        }
    }   
    utility::PrintDebug(
            "[CreatePointCloudFromRGBDImage] Unsupported image format.\n");
    return std::make_shared<PointCloud>();
}

这意味着Open3D仍然不支持它,但它只会警告你。并且仅在调试模式下!
之后,它将return一个空点云。 (实际上两个版本都这样做。)这解释了空白 window.

现在你应该知道了,你可以convert_rgb_to_intensity=True并且成功。尽管您仍然应该先标准化彩色图像。
或者您可以将彩色图像转换为 [0, 255] 范围内且类型为 uint8.
两者都会起作用。

现在我希望一切都清楚了。万岁!

P.S。实际上,我通常发现 Open3D 源代码很容易阅读。如果您了解 C++,那么每当发生这种情况时,您都可以阅读它。


2020-3-27 更新:

我无法重现你的结果,我也不知道为什么会这样(你应该提供一个 minimal reproducible example)。

使用您在评论中提供的图像,我编写了以下代码并且运行良好。如果它仍然无法在您的计算机上运行,​​则可能是您的 Open3D 损坏了。

(我不熟悉 .exr 图像,因此数据提取可能很难看:)

import Imath
import array
import OpenEXR

import numpy as np
import open3d as o3d


# extract data from exr files
f = OpenEXR.InputFile('frame.exr')
FLOAT = Imath.PixelType(Imath.PixelType.FLOAT)
cs = list(f.header()['channels'].keys())  # channels
data = [np.array(array.array('f', f.channel(c, FLOAT))) for c in cs] 
data = [d.reshape(720, 1280) for d in data]
rgb = np.concatenate([data[i][:, :, np.newaxis] for i in [3, 2, 1]], axis=-1)
# rgb /= np.max(rgb)  # this will result in a much darker image
np.clip(rgb, 0, 1.0)  # to better visualize as HDR is not supported?

# get rgbd image
img = o3d.geometry.Image((rgb * 255).astype(np.uint8))
depth = o3d.geometry.Image((data[-1] * 1000).astype(np.uint16))
rgbd = o3d.geometry.RGBDImage.create_from_color_and_depth(img, depth, convert_rgb_to_intensity=False)

# some guessed intrinsics
intr = o3d.open3d.camera.PinholeCameraIntrinsic(1280, 720, fx=570, fy=570, cx=640, cy=360)

# get point cloud and visualize
pcd = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd, intr)
o3d.visualization.draw_geometries([pcd])

结果是:


原回答:

你误解了depth_scale的意思。

使用这一行:
depth = o3d.geometry.Image(np.array(img_array[:, :, 3]*1000).astype('uint16'))


Open3D documentation said the depth values will first be scaled and then truncated. Actually it means the pixel values in your depth image will first divide this number rather than multiply, as you can see in the Open3D source:

std::shared_ptr<Image> Image::ConvertDepthToFloatImage(
        double depth_scale /* = 1000.0*/, double depth_trunc /* = 3.0*/) const {
    // don't need warning message about image type
    // as we call CreateFloatImage
    auto output = CreateFloatImage();
    for (int y = 0; y < output->height_; y++) {
        for (int x = 0; x < output->width_; x++) {
            float *p = output->PointerAt<float>(x, y);
            *p /= (float)depth_scale;
            if (*p >= depth_trunc) *p = 0.0f;
        }
    }
    return output;
}

实际上我们通常认为深度图像中的值应该是整数(我想这就是为什么 Open3D 没有在他们的文档中明确指出),因为浮动 -点图像不太常见。
您不能在 .png 图像中存储 1.34 米,因为它们会失去精度。因此,我们将 1340 存储在深度图像中,随后的过程将首先将其转换回 1.34


至于深度图像的相机内在函数,我想当您创建它时,Blender 中会有配置参数。我对 Blender 不熟悉,所以我不会谈论它。 但是,如果您不了解一般的相机内在知识,您可能想看看 this

@Jing Zhao 的回答有效! 但是,我假设他的 Open3D 版本与我的不同,我不得不像这样更改 2 个函数调用(并稍微更改命名) :

exr_img = OpenEXR.InputFile('frame0100.exr')
cs = list(exr_img.header()['channels'].keys())  # channels
FLOAT = Imath.PixelType(Imath.PixelType.FLOAT)

img_data = [np.array(array.array('f', exr_img.channel(c, FLOAT))) for c in cs]
img_data = [d.reshape(720,1280) for d in img_data]

rgb = np.concatenate([img_data[i][:, :, np.newaxis] for i in [3, 2, 1]], axis=-1)
np.clip(rgb, 0, 1.0)  # to better visualize as HDR is not supported?


img = o3d.geometry.Image((rgb * 255).astype(np.uint8))
depth = o3d.geometry.Image((img_data[-1] * 1000).astype(np.uint16))
#####rgbd = o3d.geometry.RGBDImage.create_from_color_and_depth(img, depth, convert_rgb_to_intensity=False)

rgbd = o3d.create_rgbd_image_from_color_and_depth(img, depth, convert_rgb_to_intensity=False)


# some guessed intrinsics
intr = o3d.open3d.camera.PinholeCameraIntrinsic(1280, 720, fx=570, fy=570, cx=640, cy=360)

# get point cloud and visualize
#####pcd = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd, intr)
pcd = o3d.create_point_cloud_from_rgbd_image(rgbd, intr)


o3d.visualization.draw_geometries([pcd])

否则我得到以下错误:

AttributeError: type object 'open3d.open3d.geometry.RGBDImage' has no attribute 'create_from_color_and_depth'

希望这也能帮助其他人使用我的 Python/Open3D 版本。不太确定我的代码中的错误到底在哪里,但我很满意有可用的代码。 再次感谢赵靖!