点云教程

介绍

本教程介绍了如何使用Zivid SDK进行 点云 数据的相关操作。

小技巧

如果您更喜欢观看视频教程,我们的网络研讨会 Getting your point cloud ready for your application (为您的应用准备好点云)涵盖了点云教程。

先决条件

帧(Frame)

Zivid::Frame 包含了点云和彩色图像(存储在计算设备内存中)以及捕获设置和相机的信息。

Zivid.NET.Frame 包含了点云和彩色图像(存储在计算设备内存中)以及捕获设置和相机信息。

zivid.Frame 包含了点云和彩色图像(存储在计算设备内存中)以及捕获设置和相机信息。

捕获(Capture)

使用 Zivid 进行捕获时,您会得到一个 frame 作为返回数据。点云存储在该 frame 中,而该 frame 又存储在 GPU 内存中。捕获的图像可以选择包含或者不包含颜色信息,具体取决于您调用的方法。更多信息请参阅 包含不同捕获模式的表格

带颜色数据的捕获

如果要捕获带有颜色的点云,可以使用 Zivid::Camera::capture2D3D() 方法。

跳转到源码

源码

const auto frame = camera.capture2D3D(settings);
跳转到源码

源码

using (var frame = camera.Capture2D3D(settings))
跳转到源码

源码


frame = camera.capture_2d_3d(settings)

不带颜色数据的捕获

如果要捕获没有颜色信息的点云,可以使用 Zivid::Camera::capture3D() 方法。

跳转到源码

source

const auto frame3D = camera.capture3D(settings);
跳转到源码

源码

using (var frame3D = camera.Capture3D(settings))
跳转到源码

源码

frame_3d = camera.capture_3d(settings)

查看 捕获教程 了解有关如何捕获图像的详细说明。

加载(Load)

可以通过ZDF文件加载图像帧。

跳转到源码

源码

const auto dataFile = std::string(ZIVID_SAMPLE_DATA_DIR) + "/Zivid3D.zdf";
std::cout << "Reading ZDF frame from file: " << dataFile << std::endl;
const auto frame = Zivid::Frame(dataFile);
跳转到源码

源码

var dataFile =
    Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "/Zivid/Zivid3D.zdf";
Console.WriteLine("Reading ZDF frame from file: " + dataFile);

using (var frame = new Zivid.NET.Frame(dataFile))
{
跳转到源码

源码

data_file = get_sample_data_path() / "Zivid3D.zdf"
print(f"Reading point cloud from file: {data_file}")

frame = zivid.Frame(data_file)

点云

从Frame获取句柄

您现在可以从GPU上的点云数据获取句柄。

跳转到源码

源码

const auto pointCloud = frame.pointCloud();
跳转到源码

源码

var pointCloud = frame.PointCloud;
跳转到源码

源码

point_cloud = frame.point_cloud()

点云包含了XYZ、RGB和SNR数据,分布在2D网格上。

如需了解更多信息,请查看 点云结构

方法 Zivid::Frame::pointCloud() 不从GPU内存执行任何复制。

备注

Zivid::Camera::capture2D3D() ` :code:`Zivid::Camera::capture3D() 方法会在相机完成原始图像采集后的某个时刻返回。 Zivid::Frame::pointCloud() 句柄可立即使用。然而,实际的点云数据只有在 GPU 处理完成后才可用。任何对数据复制函数(见下文)的调用都会被阻塞,并等待处理完成后再继续执行请求的复制操作。

请查看 点云捕获过程 了解更多细节。

获取 Zivid.NET.Frame.PointCloud 不会从GPU内存执行任何复制。

备注

Zivid.NET.Camera.Capture2D3D()Zivid.NET.Camera.Capture3D() 方法会在相机完成原始图像采集后的某个时刻返回。 Zivid.NET.Frame.PointCloud 句柄可立即使用。然而,实际的点云数据只有在 GPU 处理完成后才可用。任何对数据复制方法(见下文)的调用都会被阻塞,并等待处理完成后再继续执行请求的复制操作。

请查看 点云捕获过程 了解更多细节。

功能 zivid.frame.point_cloud() 不从GPU内存执行任何复制。

备注

zivid.camera.capture_2d_3d()zivid.camera.capture_3d() 方法会在相机完成原始图像采集后的某个时刻返回。 zivid.frame.point_cloud() 句柄可立即使用。但是,实际的点云数据只有在 GPU 处理完成后才可用。任何对数据复制函数(见下文)的调用都会被阻塞,并等待处理完成后再继续执行请求的复制操作。

请查看 点云捕获过程 了解更多细节。

无序点云

可以将有序点云转换为无序点云。转换过程中,所有 NaN 值都会被移除,点云会被展平为一维数组。

跳转到源码

source

const auto unorganizedPointCloud = frame.pointCloud().toUnorganizedPointCloud();
跳转到源码

源码

unorganized_point_cloud = frame.point_cloud().to_unorganized_point_cloud()

组合多个无序点云

无序点云可以通过附加的无序点云进行扩展。

跳转到源码

源码

stitchedPointCloud.extend(currentPointCloud.transform(transformationMatrixZivid));
跳转到源码

源码

stitched_point_cloud.extend(current_point_cloud.transform(transformation_matrix))

将数据从GPU复制到CPU内存

现在,您可以根据需要选择性地复制数据。这是输出数据格式的完整列表以及如何从 GPU 复制它们。这些 API 中的大多数也适用于无序点云。

返回类型

复制功能

单像素数据量

总数据量

Zivid::Array2D<Zivid::PointXYZ>

PointCloud::copyPointsXYZ()PointCloud::copyData<Zivid::PointXYZ>()

12 bytes

28 MB

Zivid::Array2D<Zivid::PointXYZW>

PointCloud::copyPointsXYZW()PointCloud::copyData<Zivid::PointXYZW>()

16 bytes

37 MB

Zivid::Array2D<Zivid::PointZ>

PointCloud::copyPointsZ()PointCloud::copyData<Zivid::PointZ>()

4 bytes

9 MB

Zivid::Array2D<Zivid::ColorRGBA>

PointCloud::copyColorsRGBA()PointCloud::copyData<Zivid::ColorRGBA>()

4 bytes

9 MB

Zivid::Array2D<Zivid::SNR>

PointCloud::copySNRs()PointCloud::copyData<Zivid::SNR>()

4 bytes

9 MB

Zivid::Array2D<Zivid::PointXYZColorRGBA>

PointCloud::copyData<PointXYZColorRGBA>()

16 bytes

37 MB

Zivid::Array2D<Zivid::PointXYZColorBGRA>

PointCloud::copyPointsXYZColorsBGRA()PointCloud::copyData<PointXYZColorBGRA>()

16 bytes

37 MB

Zivid::Image<Zivid::ColorRGBA>

PointCloud::copyImageRGBA()

4 bytes

9 MB

Zivid::Image<Zivid::ColorBGRA>

PointCloud::copyImageBGRA()

4 bytes

9 MB

Zivid::Image<Zivid::ColorsRGB>

PointCloud::copyImagesRGB()

4 bytes

9 MB

返回类型

复制方法

单像素数据量

总数据量

float[height,width,3]

PointCloud.CopyPointsXYZ()

12 bytes

28 MB

float[height,width,4]

PointCloud.CopyPointsXYZW()

16 bytes

37 MB

float[height,width,1]

PointCloud.CopyPointsZ()

4 bytes

9 MB

byte[height,width,4]

PointCloud.CopyColorsRGBA()

4 bytes

9 MB

float[height,width]

PointCloud.CopySNRs()

4 bytes

9 MB

Zivid.NET.PointXYZColorRGBA[height, width]

PointCloud.CopyPointsXYZColorsRGBA()

16 bytes

37 MB

Zivid.NET.PointXYZColorBGRA[height, width]

PointCloud.CopyPointsXYZColorsBGRA()

16 bytes

37 MB

Zivid.NET.ImageRGBA

PointCloud.CopyImageRGBA()

4 bytes

9 MB

Zivid.NET.ImageBGRA

PointCloud.CopyImageBGRA()

4 bytes

9 MB

Zivid.NET.ImageSRGB

PointCloud.CopyImageSRGB()

4 bytes

9 MB

返回类型

复制功能

单像素数据量

总数据量

numpy.ndarray([height,width,3], dtype=float32)

PointCloud.copy_data("xyz")

12 bytes

28 MB

numpy.ndarray([height,width,3], dtype=float32)

PointCloud.copy_data("xyzw")

16 bytes

37 MB

numpy.ndarray([height,width], dtype=float32)

PointCloud.copy_data("z")

4 bytes

9 MB

numpy.ndarray([height,width,4], dtype=uint8)

PointCloud.copy_data("rgba")

4 bytes

9 MB

numpy.ndarray([height,width,4], dtype=uint8)

PointCloud.copy_data("bgra")

4 bytes

9 MB

numpy.ndarray([height,width,4], dtype=uint8)

PointCloud.copy_data("srgb")

4 bytes

9 MB

numpy.ndarray([height,width], dtype=float32)

PointCloud.copy_data("snr")

4 bytes

9 MB

numpy.ndarray([height,width], dtype=[('x', '<f4'), ('y', '<f4'), ('z', '<f4'), (' r', 'u1'), ('g', 'u1'), ('b', 'u1'), ('a', 'u1')])

PointCloud.copy_data("xyzrgba")

16 bytes

37 MB

以下是如何复制数据的示例。

跳转到源码

源码

const auto data = pointCloud.copyData<Zivid::PointXYZColorRGBA_SRGB>();
跳转到源码

源码

var pointCloudData = pointCloud.CopyPointsXYZColorsRGBA();
跳转到源码

源码

xyz = point_cloud.copy_data("xyz")
rgba = point_cloud.copy_data("rgba_srgb")

内存分配选项

在内存分配方面,复制数据有两种方式:

  • Zivid SDK可以分配内存缓冲区并将数据复制到其中。

  • 用户可以将指针传递给预分配的内存缓冲区,Zivid SDK会将数据复制到预分配的内存缓冲区。

我们将展示两个使用了OpenCV的内存分配的示例。

将选定数据从GPU复制到 CPU内存(Zivid 分配)

如果您只关心例如点云的RGB颜色数据,您可以仅将该数据复制到CPU内存。

跳转到源码

源码

std::cout << "Capturing frame" << std::endl;
frame = camera.capture2D3D(settings);

std::cout << "Copying colors with Zivid API from GPU to CPU" << std::endl;
auto colors = frame.frame2D().value().imageBGRA_SRGB();

std::cout << "Casting the data pointer as a void*, since this is what the OpenCV matrix constructor requires."
          << std::endl;
auto *dataPtrZividAllocated = const_cast<void *>(static_cast<const void *>(colors.data()));

std::cout << "Wrapping this block of data in an OpenCV matrix. This is possible since the layout of \n"
          << "Zivid::ColorBGRA_SRGB exactly matches the layout of CV_8UC4. No copying occurs in this step."
          << std::endl;
const cv::Mat bgraZividAllocated(colors.height(), colors.width(), CV_8UC4, dataPtrZividAllocated);

std::cout << "Displaying image" << std::endl;
cv::imshow("BGRA image Zivid Allocated", bgraZividAllocated);
cv::waitKey(CI_WAITKEY_TIMEOUT_IN_MS);

将选定数据从GPU复制到CPU内存(用户分配)

在上面的示例中,数据的所有权由返回的 Zivid::Array2D<> 对象拥有。或者您可以提供预先分配的内存缓冲区到 Zivid::PointCloud::copyData(dataPtr)dataPtr 的类型定义了将被复制的数据(PointXYZ, ColorRGBA 等。)。

现在让我们看一下与上面完全相同的用例。但是,这一次我们允许OpenCV来分配必要的存储空间。然后我们让Zivid API将数据直接从GPU复制到这个内存位置。

跳转到源码

源码

std::cout << "Allocating the necessary storage with OpenCV API based on resolution info before any capturing"
          << std::endl;
auto bgraUserAllocated = cv::Mat(resolution.height(), resolution.width(), CV_8UC4);

std::cout << "Capturing frame" << std::endl;
auto frame = camera.capture2D3D(settings);
auto pointCloud = frame.pointCloud();

std::cout << "Copying data with Zivid API from the GPU into the memory location allocated by OpenCV"
          << std::endl;
pointCloud.copyData(&(*bgraUserAllocated.begin<Zivid::ColorBGRA_SRGB>()));

std::cout << "Displaying image" << std::endl;
cv::imshow("BGRA image User Allocated", bgraUserAllocated);
cv::waitKey(CI_WAITKEY_TIMEOUT_IN_MS);

将无序点云数据从 GPU 复制到 CPU 内存(Open3D-tensor)

跳转到源码

source

open3d::t::geometry::PointCloud copyToOpen3D(const Zivid::UnorganizedPointCloud &pointCloud)
{
    using namespace open3d::core;
    auto device = Device("CPU:0");
    auto xyzTensor = Tensor({ static_cast<int64_t>(pointCloud.size()), 3 }, Dtype::Float32, device);
    auto rgbTensor = Tensor({ static_cast<int64_t>(pointCloud.size()), 3 }, Dtype::Float32, device);

    pointCloud.copyData(reinterpret_cast<Zivid::PointXYZ *>(xyzTensor.GetDataPtr<float>()));

    // Open3D does not store colors in 8-bit
    const auto rgbaColors = pointCloud.copyColorsRGBA_SRGB();
    for(size_t i = 0; i < pointCloud.size(); ++i)
    {
        const auto r = static_cast<float>(rgbaColors(i).r) / 255.0f;
        const auto g = static_cast<float>(rgbaColors(i).g) / 255.0f;
        const auto b = static_cast<float>(rgbaColors(i).b) / 255.0f;
        rgbTensor.SetItem(TensorKey::Index(i), Tensor::Init({ r, g, b }));
    }

    open3d::t::geometry::PointCloud cloud(device);
    cloud.SetPointPositions(xyzTensor);
    cloud.SetPointColors(rgbTensor);
    return cloud;
}

转换

你可能想要 转换 点云,将其原点从相机坐标系转换到机器人基坐标系,或者比如, 通过将点云从mm转换为m来缩放点云

跳转到源码

源码

pointCloud.transform(baseToCameraTransform);
跳转到源码

源码

pointCloud.Transform(transformBaseToCamera);
跳转到源码

源码

point_cloud.transform(base_to_camera_transform)

转换可以就地完成:

  • Zivid::PointCloud::transform()

  • Zivid::UnorganizedPointCloud::transform()

或者通过创建一个新实例:

  • Zivid::PointCloud::transformed()

  • Zivid::UnorganizedPointCloud::transformed()

以下示例展示了如何创建并应用变换的 Zivid::UnorganizedPointCloud 的新实例。请注意,此示例中无需创建新实例,因为变换后未使用的点云不会被使用。

跳转到源码

source

const auto transformedUnorganizedPointCloud = unorganizedPointCloud.transformed(transformationMatrix);
跳转到源码

源码

transformed_unorganized_point_cloud = unorganized_point_cloud.transformed(transformation_matrix)

甚至就地 API 也会返回转换后的点云,因此您可以直接使用它,如下例所示。

跳转到源码

源码

stitchedPointCloud.extend(currentPointCloud.transform(transformationMatrixZivid));
跳转到源码

源码

stitched_point_cloud.extend(current_point_cloud.transform(transformation_matrix))

下采样

有时您可能不需要相机输出的高空间分辨率(高空间分辨率意味着更多的细节和更短的点之间的距离)点云。那么您对点云进行 下采样

备注

Sampling(采样) - 3D 描述了一种基于硬件的子/下采样方法,它可以降低捕获过程中点云的分辨率,同时也减少采集和捕获时间。

备注

Zivid::UnorganizedPointCloud 不支持下采样,但它支持体素下采样,请参阅 体素下采样

可以就地进行下采样,从而修改当前的点云。

跳转到源码

源码

pointCloud.downsample(Zivid::PointCloud::Downsampling::by2x2);
跳转到源码

源码

pointCloud.Downsample(Zivid.NET.PointCloud.Downsampling.By2x2);
跳转到源码

源码

point_cloud.downsample(zivid.PointCloud.Downsampling.by2x2)

也可以将下采样后的点云作为一个新的点云实例,它不会改变现有的点云。

跳转到源码

源码

auto downsampledPointCloud = pointCloud.downsampled(Zivid::PointCloud::Downsampling::by2x2);
跳转到源码

源码

var downsampledPointCloud = pointCloud.Downsampled(Zivid.NET.PointCloud.Downsampling.By2x2);
跳转到源码

源码

downsampled_point_cloud = point_cloud.downsampled(zivid.PointCloud.Downsampling.by2x2)

Zivid SDK支持以下下采样率: by2x2, by3x3, 和 by4x4, 可以进行多次下采样。

体素下采样

Zivid::UnorganizedPointCloud 支持体素下采样。该 API 接受两个参数:

  1. voxelSize - 体素的大小(以毫米为单位)。

  2. minPointsPerVoxel - 每个体素保留的最小点数。

体素下采样将三维空间细分为具有给定大小的立方体素网格。如果给定体素包含的点数等于或超过给定限制,则所有这些源点都将被替换为具有以下属性的单个点:

  • 位置(XYZ)是源点位置的 SNR 加权平均值,即高置信度的源点对最终位置的影响将大于低置信度的源点。

  • 颜色(RGBA)是源点颜色的平均值。

  • 信噪比 (SNR) 是源点 SNR 值的 sqrt(sum(SNR^2)),即新点的 SNR 将随着用于计算其位置的源点的数量和置信度的增加而增加。

使用 minPointsPerVoxel > 1 特别适用于去除由不同角度捕获的点云组合而成的无序点云中的噪点和伪影。这是因为某个伪影很可能只出现在其中一次捕获中,而 minPointsPerVoxel 可用于仅填充两次捕获 "一致" 的体素。

跳转到源码

source

const auto finalPointCloud = stitchedPointCloud.voxelDownsampled(0.5, 1);
跳转到源码

源码

final_point_cloud = stitched_point_cloud.voxel_downsampled(0.5, 1)

法线

一些应用需要计算点云的 法线 数据。

跳转到源码

源码

std::cout << "Computing normals and copying them to CPU memory" << std::endl;
const auto normals = pointCloud.copyData<Zivid::NormalXYZ>();
跳转到源码

源码

Console.WriteLine("Computing normals and copying them to CPU memory");
var normals = pointCloud.CopyNormalsXYZ();
跳转到源码

源码

print("Computing normals and copying them to CPU memory")
normals = point_cloud.copy_data("normals")

法线API将计算点云中每个点的法线,并将法线从GPU内存复制到CPU内存。其结果是一个法向量矩阵,每条法线对应输入点云的每个点。法线的大小和输入点云的大小相等。

可视化

您可以通过帧(frame)可视化点云。

跳转到源码

源码

std::cout << "Setting up visualization" << std::endl;
Zivid::Visualization::Visualizer visualizer;

std::cout << "Visualizing point cloud" << std::endl;
visualizer.showMaximized();
visualizer.show(frame);
visualizer.resetToFit();

std::cout << "Running visualizer. Blocking until window closes." << std::endl;
visualizer.run();
跳转到源码

源码

Console.WriteLine("Setting up visualization");
using (var visualizer = new Zivid.NET.Visualization.Visualizer())
{
    Console.WriteLine("Visualizing point cloud");
    visualizer.Show(frame);
    visualizer.ShowMaximized();
    visualizer.ResetToFit();

    Console.WriteLine("Running visualizer. Blocking until window closes.");
    visualizer.Run();
}

您也可以从点云对象来可视化点云。

跳转到源码

源码

std::cout << "Getting point cloud from frame" << std::endl;
auto pointCloud = frame.pointCloud();

std::cout << "Setting up visualization" << std::endl;
Zivid::Visualization::Visualizer visualizer;

std::cout << "Visualizing point cloud" << std::endl;
visualizer.showMaximized();
visualizer.show(pointCloud);
visualizer.resetToFit();

std::cout << "Running visualizer. Blocking until window closes." << std::endl;
visualizer.run();
跳转到源码

源码

Console.WriteLine("Getting point cloud from frame");
var pointCloud = frame.PointCloud;

Console.WriteLine("Setting up visualization");
var visualizer = new Zivid.NET.Visualization.Visualizer();

Console.WriteLine("Visualizing point cloud");
visualizer.Show(pointCloud);
visualizer.ShowMaximized();
visualizer.ResetToFit();

Console.WriteLine("Running visualizer. Blocking until window closes.");
visualizer.Run();

如需了解更多信息,请查看 可视化教程,里面包含了如何使用第三方库实现点云、彩色图像、深度图和法线的可视化。

结论

本教程展示了如何使用Zivid SDK来提取、操作、转换和可视化点云。

版本历史

SDK

变更

2.16.0

增加了对 Zivid::UnorganizedPointCloud 的支持。 transformed 作为函数添加到 Zivid::PointCloud (也可在 Zivid::UnorganizedPointCloud 中使用)。

2.11.0

添加了对 SRGB 色彩空间(SRGB color space)的支持。

2.10.0

Monochrome Capture(单色捕获) 引入了比 下采样 更快的替代方案。