跳到主要内容

PCL 将点云保存为 PCD 文件

PCL 输出 PCD 文件

PCD 全称 Point Cloud Data,是一种存储三维点云数据的文件格式,本教程将介绍如何使用 PCL 点云库将点云数据写入 PCD 文件。由于 PCD 是一种开放数据格式,因此你可以使用各种点云处理工具来读取和写入 PCD 文件,并进一步对点云数据进行处理。

提示

本教程的示例代码和点云数据可在 GitHub 下载。

PCD 格式​

PCD 格式是由 PCL 点云库使用的文件格式,用于存储三维点云数据。PCD 文件格式有两种主要的存储方式:ASCII(文本)格式和二进制格式。其中,ASCII 格式易于阅读和编辑,但文件较大;而二进制格式通常更紧凑,适用于大型点云数据。

PCD 文件由一个头部(header)和数据体(data body)组成。头部包含元数据和格式信息,数据体包含实际的点云数据。

头部信息​

头部(Header)信息一般包括以下字段:

序号字段名称字段描述
1VERSION文件格式的版本信息。
2FIELDS点云数据的字段名称,例如 x, y, z, intensity。
3SIZE每个字段的大小(以字节为单位)。
4TYPE每个字段的数据类型,例如 F(浮点型)、U(无符号整型)。
5COUNT每个字段的元素个数,通常是 1。
6WIDTH点云数据的宽度,如果是 1 则表示无序点云。
7HEIGHT点云数据的高度,如果是 1 则表示无序点云。
8VIEWPOINT相机视点信息(通常用于三维重建)。
9POINTS点云中的点的总数。
10DATA数据存储的格式,可能是 ascii、binary、binary_compressed。

下面是一个典型的 PCD 文件头部示例:

# .PCD v0.7 - Point Cloud Data file format
VERSION 0.7
FIELDS x y z intensity
SIZE 4 4 4 4
TYPE F F F F
COUNT 1 1 1 1
WIDTH 1000
HEIGHT 1
VIEWPOINT 0 0 0 1 0 0 0
POINTS 1000
DATA ascii

数据体​

数据体(Data Body)包含实际的点云数据,根据头部的描述以 ASCII 或二进制格式存储。

在 ASCII 格式下,点云数据以文本形式存储,每个点的数据在一行中按字段顺序排列,字段之间以空格分隔。例如,下面数据中的每一行对应一个三维点坐标(XYZ)和强度(Intensity)信息。

4.1249428 7.1494288 5.167275 6.0977383
3.532496 2.1198421 8.2864342 0.50844169
0.10034604 9.304101 9.876545 68.85511
6.0308199 1.1348503 9.8444204 63.933022
...

而在二进制格式下,数据按字段顺序紧凑地存储在文件中,没有任何分隔符,所有数据以二进制形式直接写入文件。这种格式更节省存储空间和读取时间。

示例程序​

PCL 的 io 模块提供了读取和写入 PCD 文件的功能,下面我们通过三个简单的示例,演示如何将点云数据输出为 ASCII 格式和二进制格式的 PCD 文件,以及如何读取 PCD 文件并将其可视化出来。

以 ASCII 格式输出 PCD 文件​

下面示例程序演示如何使用 PCL 库将随机生成的一组点云数据保存为一个文本格式的 PCD 文件。

save_pcd_ascii.cpp
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <iostream>

int main()
{
// 创建点云对象
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);

// 随机生成点云数据
srand(static_cast<unsigned int>(time(NULL)));
for (int i = 0; i < 1000; ++i)
{
pcl::PointXYZI point;
point.x = 10.0f * rand() / RAND_MAX; // 在 x 方向上随机生成坐标
point.y = 10.0f * rand() / RAND_MAX; // 在 y 方向上随机生成坐标
point.z = 10.0f * rand() / RAND_MAX; // 在 z 方向上随机生成坐标
point.intensity = 255.0f * rand() / RAND_MAX; // 生成随机强度值
cloud->push_back(point);
}

// 保存点云数据到 PCD 文件
pcl::io::savePCDFileASCII("output_ascii.pcd", *cloud);

std::cout << "Saved " << cloud->points.size() << " data points to output_ascii.pcd in ASCII format." << std::endl;

return 0;
}

这个程序将生成一个包含 1000 个随机点的点云数据,并将其保存为名为 output_ascii.pcd 的 PCD 文件。

代码重点:

  • 使用 pcl::PointCloud<pcl::PointXYZI>::Ptr 创建一个指向点云对象的智能指针。
  • 使用 rand() 函数生成随机点,并填充到点云对象中。
  • 使用 pcl::io::savePCDFileASCII 函数将点云数据保存到 PCD 文件中。

以二进制格式输出 PCD 文件​

下面示例程序演示如何使用 PCL 库将随机生成的一组点云数据保存为一个二进制格式的 PCD 文件。

save_pcd_binary.cpp
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <iostream>

int main()
{
// 创建点云对象
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);

// 随机生成点云数据
srand(static_cast<unsigned int>(time(NULL)));
for (int i = 0; i < 1000; ++i)
{
pcl::PointXYZI point;
point.x = 10.0f * rand() / RAND_MAX; // 在 x 方向上随机生成坐标
point.y = 10.0f * rand() / RAND_MAX; // 在 y 方向上随机生成坐标
point.z = 10.0f * rand() / RAND_MAX; // 在 z 方向上随机生成坐标
point.intensity = 255.0f * rand() / RAND_MAX; // 生成随机强度值
cloud->push_back(point);
}

// 保存点云数据到二进制 PCD 文件
pcl::io::savePCDFileBinary("output_binary.pcd", *cloud);

std::cout << "Saved " << cloud->points.size() << " data points to output_binary.pcd in binary format." << std::endl;

return 0;
}

这个程序将生成一个包含 1000 个随机点的点云数据,并将其保存为名为 output_binary.pcd 的 PCD 文件。

代码和前面的示例类似,不同的是这里使用 pcl::io::savePCDFileBinary 函数将点云数据保存到 PCD 文件中。

读取 PCD 文件并可视化​

下面示例程序从命令行参数读取一个 PCD 文件(可以是 ASCII 格式或二进制格式),并使用 PCL 的可视化工具将其显示出来。

pcd_viewer.cpp
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>

int main(int argc, char** argv)
{
// 检查命令行参数
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " <path-to-pcd-file>" << std::endl;
return -1;
}

// 创建点云对象
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);

// 从文件中读取点云数据
if (pcl::io::loadPCDFile<pcl::PointXYZI>(argv[1], *cloud) == -1)
{
PCL_ERROR("Couldn't read file %s \n", argv[1]);
return -1;
}

std::cout << "Loaded " << cloud->width * cloud->height << " data points from " << argv[1] << std::endl;

// 创建可视化窗口对象
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("PCD Viewer"));

// 创建颜色处理对象
pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZI> fildColor(cloud, "intensity");

// 将点云添加到可视化窗口中
viewer->addPointCloud<pcl::PointXYZI>(cloud, fildColor, "sample cloud");

// 设置点云大小
viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "sample cloud");

// 显示可视化窗口,直到用户关闭窗口为止
while (!viewer->wasStopped())
{
viewer->spinOnce(100); // 每隔100毫秒渲染一次
}

return 0;
}

编译运行​

为了编译这三个示例程序,你可以使用以下 CMakeLists.txt 文件:

CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(pcd_example)

find_package(PCL REQUIRED)
find_package(VTK REQUIRED)

include_directories(${PCL_INCLUDE_DIRS})

add_executable(save_pcd_ascii save_pcd_ascii.cpp)
add_executable(save_pcd_binary save_pcd_binary.cpp)
add_executable(pcd_viewer pcd_viewer.cpp)

target_link_libraries(save_pcd_ascii ${PCL_LIBRARIES})
target_link_libraries(save_pcd_binary ${PCL_LIBRARIES})
target_link_libraries(pcd_viewer ${PCL_LIBRARIES} ${VTK_LIBRARIES})

依次执行以下命令编译程序:

mkdir build
cd build
cmake ..
make

在 build 目录下可以看到生成了三个可执行文件:save_pcd_ascii、save_pcd_binary 和 pcd_viewer。

  • 执行 ./save_pcd_ascii,将生成一个 output_ascii.pcd 文件;
  • 执行 ./save_pcd_binary,将生成一个 output_binary.pcd 文件。

对比两个文件的大小,可以发现:同样存储 1000 个点的数据,二进制格式比 ASCII 格式小得多。

$ ls -lh output_*
-rw-rw-r-- 1 rudy rudy 40K 5月 27 18:11 output_ascii.pcd
-rw-r--r-- 1 rudy rudy 16K 5月 27 16:31 output_binary.pcd

现在,可以执行 pcd_viewer 程序将点云显示出来:

$ ./pcd_viewer output_ascii.pcd
# 或者
$ ./pcd_viewer output_binary.pcd

可以看到类似如下窗口:

其他 PCD 文件查看方式​

除了像前面这样自己写一个 PCD Viewer 程序来显示 PCD 文件,还可以通过 pcl-tools 软件包中的 pcl_viewer 工具查看 PCD 文件,安装和使用方法如下。

$ sudo apt install pcl-tools
$ pcl_viewer output.pcd

另外,你可以在 VS Code 上安装 pcd-viewer 插件来查看 PCD 文件。安装完成后,直接打开 PCD 文件即可看到 3D 点云,插件提供了视角切换、颜色渲染、点云大小设置等基本功能,还是挺方便的!

VSCode pcd-viewer 插件可视化 PCD 点云文件