点云教程

介绍

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

小技巧

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

先决条件

帧(Frame)

The Frame contains the point cloud and color image (stored on compute device memory) and the capture and camera information.

The Frame contains the point cloud and color image (stored on compute device memory) and the capture and camera information.

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

捕获(Capture)

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

带颜色数据的捕获

If you want to capture a point cloud with color, you can use the Camera::capture2D3D() method.

跳转到源码

source

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

source

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

source


frame = camera.capture_2d_3d(settings)

不带颜色数据的捕获

If you want to capture a point cloud without color, you can use the Camera::capture3D() method.

跳转到源码

source

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

source

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

source

frame_3d = camera.capture_3d(settings)

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

加载(Load)

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

跳转到源码

source

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);
跳转到源码

source

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))
{
跳转到源码

source

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

frame = zivid.Frame(data_file)

点云

从Frame获取句柄

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

跳转到源码

source

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

source

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

source

point_cloud = frame.point_cloud()

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

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

The method Frame::pointCloud() does not perform any copying from GPU memory.

备注

Camera::capture2D3D() and Camera::capture3D() methods return at some moment in time after the camera completes capturing raw images. The handle from Frame::pointCloud() is available instantly. However, the actual point cloud data becomes available only after the processing on the GPU is finished. Any calls to data-copy functions (section below) will block and wait for processing to finish before proceeding with the requested copy operation.

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

Getting the property Frame.PointCloud does not perform any copying from GPU memory.

备注

Camera.Capture2D3D() and Camera.Capture3D() methods return at some moment in time after the camera completes capturing raw images. The handle from Frame.PointCloud is available instantly. However, the actual point cloud data becomes available only after the processing on the GPU is finished. Any calls to data-copy methods (section below) will block and wait for processing to finish before proceeding with the requested copy operation.

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

功能 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();
跳转到源码

source

unorganized_point_cloud = frame.point_cloud().to_unorganized_point_cloud()

组合多个无序点云

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

跳转到源码

source

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

source

stitched_point_cloud.extend(current_point_cloud.transform(transformation_matrix))

将数据从GPU复制到CPU内存

You can now selectively copy data based on what is required. This is the complete list of output data formats and how to copy them from the GPU.

小技巧

If you want to process the data directly on the GPU without copying to the CPU, see GPU Access Tutorial.

Most of these APIs also applies to the unorganized point cloud.

返回类型

复制功能

单像素数据量

总数据量

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

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

跳转到源码

source

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

source

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

source

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

内存分配选项

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

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

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

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

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

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

跳转到源码

source

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

const auto frame2D = frame.frame2D();
if(!frame2D.has_value())
{
    throw std::runtime_error("Captured frame does not contain a 2D image.");
}
std::cout << "Copying colors with Zivid API from GPU to CPU" << std::endl;
auto colors = frame2D->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内存(用户分配)

In the above example, ownership of the data was held by the returned Zivid::Array2D<> objects. Alternatively, you may provide a pre-allocated memory buffer to Zivid::PointCloud::copyData(dataPtr). The type of dataPtr defines what shall be copied (PointXYZ, ColorRGBA, etc.).

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

跳转到源码

source

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);

参见

Copy Speed benchmarks for the time it takes to copy each data type from GPU to system memory.

转换

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

跳转到源码

source

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

source

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

source

point_cloud.transform(base_to_camera_transform)

转换可以就地完成:

  • PointCloud::transform()

  • UnorganizedPointCloud::transform()

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

  • PointCloud::transformed()

  • UnorganizedPointCloud::transformed()

The following example shows how create a new instance of UnorganizedPointCloud with a transformation applied to it. Note that in this sample is is not necessary to create a new instance, as the untransformed point cloud is not used after the transformation.

跳转到源码

source

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

源码

transformed_unorganized_point_cloud = unorganized_point_cloud.transformed(transformation_matrix)

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

跳转到源码

source

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

source

stitched_point_cloud.extend(current_point_cloud.transform(transformation_matrix))

If the object of interest is a subset of the ROI-cropped point cloud, you can further refine it by applying a binary mask. A typical workflow is to capture a 2D image, run a segmentation algorithm to identify the object, and apply the resulting mask to the 3D point cloud. This invalidates all points outside the segmented region.

See the Mask section of the Point Cloud Tutorial for API usage and code examples.

Mask

You can mask the point cloud to keep only a specific region of interest. A typical use case is to capture a 2D image, run it through a segmentation algorithm to produce a binary mask, and apply it to the 3D point cloud. Non-zero values in the mask invalidate the corresponding points (set to NaN), while zero values preserve them. The mask can be applied using mask() (in-place) or masked() (returns a new copy), on both PointCloud and Frame.

跳转到源码

source

// Create a ones-filled mask
Zivid::Mask mask(resolution);

// Calculate rectangle bounds
const int height = static_cast<int>(resolution.height());
const int width = static_cast<int>(resolution.width());
const int heightMin = (height - pixelsToDisplay) / 2;
const int heightMax = (height + pixelsToDisplay) / 2;
const int widthMin = (width - pixelsToDisplay) / 2;
const int widthMax = (width + pixelsToDisplay) / 2;

// Create OpenCV Mat wrapper for the mask data
cv::Mat maskMat(height, width, CV_8UC1, mask.data());

// Draw filled rectangle on the mask to unmask the central region
cv::rectangle(
    maskMat, cv::Point(widthMin, heightMin), cv::Point(widthMax, heightMax), cv::Scalar(0), cv::FILLED);

return mask;
auto maskedPointCloud = pointCloud.masked(mask);
跳转到源码

source

// Create a ones-filled mask
var mask = new Zivid.NET.Mask(resolution);

// Calculate rectangle bounds
int height = (int)resolution.Height;
int width = (int)resolution.Width;
int heightMin = (height - pixelsToDisplay) / 2;
int heightMax = (height + pixelsToDisplay) / 2;
int widthMin = (width - pixelsToDisplay) / 2;
int widthMax = (width + pixelsToDisplay) / 2;

// Set pixels inside the rectangle to zero
for (int y = heightMin; y < heightMax; ++y)
{
    for (int x = widthMin; x < widthMax; ++x)
    {
        mask[x, y] = 0;
    }
}

return mask;
    var maskedPointCloud = pointCloud.Masked(mask);
跳转到源码

source

pixels_to_display = 300
print(f"Generating binary mask of central {pixels_to_display} x {pixels_to_display} pixels")
height = frame.point_cloud().height
width = frame.point_cloud().width
mask = np.ones((height, width), bool)

h_min = (height - pixels_to_display) // 2
h_max = (height + pixels_to_display) // 2
w_min = (width - pixels_to_display) // 2
w_max = (width + pixels_to_display) // 2
mask[h_min:h_max, w_min:w_max] = 0
print("Masking point cloud")
point_cloud.mask(mask)

A mask can also be constructed from third-party data, such as an OpenCV image.

跳转到源码

source

// Create a circular mask in OpenCV
const int centerX = static_cast<int>(resolution.width()) / 2;
const int centerY = static_cast<int>(resolution.height()) / 2;
const int radius = pixelsToDisplay;
cv::circle(opencvMask, cv::Point(centerX, centerY), radius, cv::Scalar(0), cv::FILLED);

// Convert OpenCV mask to Zivid::Mask
const Zivid::Mask circularMask(resolution, opencvMask.datastart, opencvMask.dataend);
跳转到源码

source

opencv_mask = np.ones((height, width), dtype=np.uint8)
center_x = width // 2
center_y = height // 2
radius = pixels_to_display
cv2.circle(opencv_mask, (center_x, center_y), radius, 0, thickness=cv2.FILLED)

point_cloud.mask(opencv_mask.astype(bool))

备注

The mask supports implicit resampling: if the mask resolution differs from the point cloud resolution by an integer factor (2, 3, or 4), it is automatically resampled. This is useful when creating a mask from a 2D capture with a different resolution than the 3D capture.

下采样

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

备注

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

备注

UnorganizedPointCloud does not support downsampling, but it does support voxel downsampling, see 体素下采样.

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

跳转到源码

source

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

source

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

source

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

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

跳转到源码

source

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

source

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

source

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

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

体素下采样

UnorganizedPointCloud supports voxel downsampling. The API takes two arguments:

  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);
跳转到源码

source

final_point_cloud = stitched_point_cloud.voxel_downsampled(0.5, 1)

法线

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

跳转到源码

source

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

source

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

source

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

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

可视化

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

跳转到源码

source

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();
跳转到源码

source

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();
}

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

跳转到源码

source

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();
跳转到源码

source

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来提取、操作、转换和可视化点云。

For the corresponding API, see Applications Basic.

版本历史

SDK

变更

2.18.0

Added Zivid::Mask class and mask() / masked() methods on PointCloud and Frame for post-capture binary masking.

2.16.0

Added support for UnorganizedPointCloud. transformed is added as a function to PointCloud (also available in UnorganizedPointCloud).

2.11.0

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

2.10.0

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