第2章:点云数据基础 — 从PCL到你的第一个点云程序
说实话,我刚入行那会儿,面对点云数据也是一头雾水。一堆三维坐标点,能干啥?后来才明白,点云处理的核心,说白了就是跟这些点打交道——怎么存、怎么读、怎么写、怎么用。这一章,我们就来搞定这些基础。
2.1 PCL库:点云世界的“瑞士军刀”
PCL(Point Cloud Library),全称点云库。它不是某个公司出的商业软件,而是全球开发者一起维护的开源项目。我个人的习惯是,把它看作点云领域的OpenCV——功能全面,社区活跃,文档也还算友好。
PCL能做什么?我简单列一下:
- 滤波:去除噪声、下采样、离群点剔除
- 特征估计:法线、曲率、FPFH描述子
- 配准:ICP、NDT,把多帧点云拼到一起
- 分割:地面提取、聚类、平面检测
- 识别:物体识别、六自由度位姿估计
嗯,这里要注意:PCL的模块划分很清晰,但编译起来有点麻烦。我第一次编译PCL时,光依赖库就装了半小时。建议直接用包管理器安装,省心。
我的小建议: 如果你用Ubuntu,直接
sudo apt install libpcl-dev 搞定。Windows用户推荐vcpkg,别自己从源码编译,除非你想练练耐心。
2.2 点云数据结构:PointXYZ 与 PointXYZRGB
点云数据,本质上就是一堆点。每个点至少包含三维坐标(x, y, z)。但实际应用中,我们往往需要更多信息——颜色、强度、法线等等。
PCL里最常用的两个点类型:
| 点类型 | 包含字段 | 典型用途 |
|---|---|---|
| pcl::PointXYZ | x, y, z | 纯几何信息,激光雷达原始数据 |
| pcl::PointXYZRGB | x, y, z, r, g, b | 带颜色,RGB-D相机或融合视觉 |
我记得有一次做项目,客户要求把激光雷达和相机数据融合。激光雷达给的是PointXYZ,相机给的是RGB图像。怎么把颜色赋给点云?其实就是把相机像素坐标映射到激光雷达坐标系,然后填充PointXYZRGB的r、g、b字段。听起来简单,但标定搞了我整整两天。
关键点: PointXYZRGB的颜色字段是
uint32_t rgba,不是三个独立的float。赋值时要用PCL提供的 pcl::RGB 结构体,或者直接位运算。我见过有人直接写 point.r = 255,结果颜色全乱套了。
2.3 点云文件的读写:PCD与PLY
点云数据怎么存?最常用的两种格式:PCD和PLY。
- PCD:PCL的原生格式,读写速度极快,支持二进制和ASCII。我一般用二进制,文件小、加载快。
- PLY:更通用的格式,支持颜色、法线、甚至多边形网格。如果你要和MeshLab、Blender交换数据,PLY是首选。
下面是一个完整的读写示例。我习惯把代码写得简洁一点,但关键步骤一个都不能少。
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/point_types.h>
// 创建一个点云对象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 填充一些点(模拟数据)
cloud->width = 5;
cloud->height = 1; // 无序点云
cloud->points.resize(cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size(); ++i) {
cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
// 保存为PCD(二进制格式)
pcl::io::savePCDFileBinary("test.pcd", *cloud);
std::cout << "保存PCD完成" << std::endl;
// 保存为PLY(ASCII格式)
pcl::io::savePLYFileASCII("test.ply", *cloud);
std::cout << "保存PLY完成" << std::endl;
// 读取PCD文件
pcl::PointCloud<pcl::PointXYZ>::Ptr loaded_cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("test.pcd", *loaded_cloud) == -1) {
PCL_ERROR("读取PCD文件失败\n");
return -1;
}
std::cout << "读取了 " << loaded_cloud->points.size() << " 个点" << std::endl;
我曾经踩过的坑: 读取PLY文件时,如果文件里包含颜色信息,但你用PointXYZ去读,程序不会报错,但颜色数据会丢失。一定要根据文件内容选择正确的点类型。另外,PCD文件的头部信息里有个
FIELDS 字段,看一眼就知道里面存了什么。
2.4 本章知识体系
为了让你更直观地理解本章内容,我画了一张图。它展示了从原始数据到点云对象的完整流程。
这张图把本章内容串起来了。你想想看,从PCL库出发,理解点云数据结构,再掌握文件读写,这就是点云处理的第一步。后面所有的滤波、配准、分割,都建立在这个基础之上。
实战建议: 学完这一章,我建议你立刻动手写一个程序:从PCD文件读取点云,遍历每个点,打印出坐标,再保存为PLY。这个过程能帮你彻底理解点云数据的本质——不过是一堆结构体数组罢了。
好了,这一章就到这里。记住,点云处理没有捷径,但打好基础能让你少走很多弯路。下一章我们开始真正的滤波实战,到时候你会感谢今天认真学数据结构的自己。