第1章:PCL点云库入门

各位同学,欢迎来到《点云滤波与IMU融合定位算法实战》的第一课。

说实话,每次带新人入门点云处理,我都要先问一句:「你装好PCL了吗?」

PCL,全称Point Cloud Library,是点云处理领域最常用的开源库。我做了这么多年SLAM,几乎每个项目都离不开它。今天咱们就花一章的时间,把PCL的核心模块、数据结构和可视化工具捋一遍。

1.1 PCL核心模块介绍

PCL的模块划分得很清晰。我个人习惯把它分成三大块:滤波、配准、分割。当然还有别的模块,但这三个是咱们做SLAM最常用的。

1.1.1 滤波模块

滤波是点云预处理的第一步。你想想看,激光雷达扫出来的原始点云,噪声多、离群点多、数据量大,直接拿来用肯定不行。

PCL的滤波模块提供了很多滤波器,我挑几个常用的说说:

  • 直通滤波器(PassThrough):按坐标轴范围裁剪点云。比如我只想保留前方10米内的点,设个x轴范围就行。
  • 体素滤波器(VoxelGrid):下采样用的。把空间划分成小立方体,每个立方体只保留一个重心点。数据量能降一个数量级。
  • 统计滤波器(StatisticalOutlierRemoval):去除离群点。计算每个点邻域的统计特征,把偏离太远的点干掉。
  • 半径滤波器(RadiusOutlierRemoval):如果某个点周围指定半径内点数太少,就认为是噪声。
我的经验:体素滤波的叶子大小很关键。设太大,细节全丢;设太小,降采样效果不明显。我一般从0.05m开始试,根据场景调整。

1.1.2 配准模块

配准,说白了就是把两片点云对齐。这在SLAM里太重要了——你要把当前帧和地图对齐,才能知道自己在哪。

PCL配准模块的核心算法:

  • ICP(Iterative Closest Point):最经典的配准算法。迭代找最近点,计算变换矩阵。收敛慢,但精度高。
  • NDT(Normal Distributions Transform):把点云用正态分布表示,配准速度比ICP快很多。我在做自动驾驶定位时特别喜欢用NDT。
  • 特征匹配配准:先提取特征点(如FPFH、SHOT),再匹配特征,最后用RANSAC求变换。适合粗配准。
避坑指南:我曾经在隧道里做ICP配准,结果一直收敛不了。后来发现隧道环境太单调,点云特征太少。这时候用NDT或者加IMU辅助会好很多。

1.1.3 分割模块

分割就是把点云分成不同的物体或区域。比如从一堆点里把地面、墙壁、行人分开。

常用的分割方法:

  • 平面分割(RANSAC):用随机采样一致性算法拟合平面。提取地面、墙面特别好用。
  • 欧几里得聚类(EuclideanClusterExtraction):根据点之间的距离聚类。适合分离独立的物体。
  • 区域生长(RegionGrowing):基于法向量和曲率,把相似的点聚在一起。

1.2 PCL数据结构

学PCL,首先要搞懂它的数据结构。说白了,就是怎么存点云。

1.2.1 PointCloud

pcl::PointCloud 是PCL最核心的数据结构。它本质上是一个模板类,里面存了一堆点。

关键成员:

  • points:点数据的vector,存了所有点。
  • width 和 height:如果是有序点云,width是每行点数,height是行数;无序点云height=1。
  • is_dense:标记是否有无效点(NaN)。
// 创建一个PointCloud对象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);

// 设置点云属性
cloud->width = 1000;
cloud->height = 1;  // 无序点云
cloud->is_dense = false;
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);
}

1.2.2 PointXYZ

pcl::PointXYZ 是最基础的点类型,只包含x、y、z三个坐标。你想想看,大部分场景下我们只需要位置信息,这就够了。

其他常用点类型:

点类型 包含字段 适用场景
PointXYZ x, y, z 纯几何位置
PointXYZRGB x, y, z, r, g, b 带颜色信息的点云
PointXYZI x, y, z, intensity 激光雷达强度数据
PointNormal x, y, z, normal_x, normal_y, normal_z, curvature 需要法向量和曲率
注意:PCL里所有点类型都继承自 pcl::PointCloud 的模板参数。你可以自定义点类型,但建议优先用PCL自带的,兼容性更好。

1.3 PCL可视化工具

做点云处理,可视化太重要了。你看不到数据,怎么调试?PCL提供了两个可视化工具:CloudViewer 和 PCLVisualizer。

1.3.1 CloudViewer

CloudViewer 是最简单的可视化类。适合快速看一眼点云长什么样。

#include <pcl/visualization/cloud_viewer.h>

// 创建viewer对象
pcl::visualization::CloudViewer viewer("Simple Cloud Viewer");

// 显示点云
viewer.showCloud(cloud);

// 保持窗口打开
while (!viewer.wasStopped()) {
    // 可以在这里做其他处理
}

嗯,就这么几行代码,你就能看到点云了。但 CloudViewer 功能有限,不能添加多个点云、不能交互。

1.3.2 PCLVisualizer

如果你需要更复杂的可视化,用 PCLVisualizer。它支持:

  • 同时显示多个点云
  • 添加坐标轴、文字、形状
  • 鼠标交互(旋转、缩放、选点)
  • 回调函数(比如点击点云获取坐标)
#include <pcl/visualization/pcl_visualizer.h>

// 创建PCLVisualizer
pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));
viewer->setBackgroundColor(0, 0, 0);  // 黑色背景

// 添加点云
viewer->addPointCloud<pcl::PointXYZ>(cloud, "sample cloud");

// 添加坐标轴
viewer->addCoordinateSystem(1.0);

// 设置视角
viewer->initCameraParameters();

// 循环显示
while (!viewer->wasStopped()) {
    viewer->spinOnce(100);
    boost::this_thread::sleep(boost::posix_time::microseconds(100000));
}
我的习惯:调试时用 PCLVisualizer,因为它可以同时显示原始点云和滤波后的点云,方便对比效果。我曾经在调试地面分割时,把原始点云设成白色,地面点设成绿色,非地面点设成红色,一眼就能看出分割效果好不好。

本章知识体系

下面这张图,是我自己画的PCL入门知识结构。你看一眼,心里就有数了。

PCL点云库入门知识体系 核心模块 数据结构 可视化工具 滤波(PassThrough, VoxelGrid...) 配准(ICP, NDT, 特征匹配) 分割(RANSAC, 聚类, 区域生长) PointCloud(核心容器) PointXYZ(基础点类型) 其他类型(RGB, Intensity, Normal) CloudViewer(快速查看) PCLVisualizer(高级交互) 多窗口、回调、形状添加 应用场景:SLAM定位、地图构建、目标检测、机器人导航 三大模块相互配合:滤波预处理 → 数据结构存储 → 可视化调试 配准和分割依赖滤波后的干净数据,可视化帮助验证每一步效果

好了,第一章的内容就到这里。PCL入门其实不难,关键是动手敲代码。我建议你今晚就把上面的代码示例跑一遍,看看点云长什么样。

记住,做SLAM,数据结构和可视化是基本功。后面几章我们会深入滤波和配准,到时候这些基础就派上用场了。

课后练习:
  1. 创建一个包含1000个随机点的PointCloud,用CloudViewer显示出来。
  2. 用PCLVisualizer同时显示两个不同颜色的点云。
  3. 尝试用PassThrough滤波器裁剪点云,观察效果。

可可精品资料库