3. 点云预处理与地面滤波

各位同学,欢迎来到第三章。这一章我们聊点云预处理和地面滤波。

说实话,高精地图构建里,点云处理是绕不开的坎。原始点云数据量巨大,噪声多,地面和非地面混在一起。你想想看,如果不做预处理,直接拿原始点云去建图,那结果基本没法看。我早期做项目时就吃过这个亏,数据量太大,算法跑不动,最后发现是点云没降采样。

3.1 体素滤波降采样:给点云“减肥”

体素滤波(Voxel Filter)是点云降采样的经典方法。它的核心思想很简单:把三维空间划分成一个个小立方体(体素),每个体素内只保留一个点(通常是重心或中心点)。

为什么要降采样?

  • 减少计算量:原始点云动辄几十万甚至上百万点,直接处理太慢。
  • 均匀分布:体素滤波能让点云在空间上分布更均匀,避免局部过密。
  • 保留几何结构:只要体素大小设置合理,几何特征基本不会丢失。

体素大小怎么选?

我个人习惯:城区道路场景用 0.2m~0.5m,高速场景可以放宽到 0.5m~1.0m。体素太小,降采样效果不明显;体素太大,细节丢失严重。

代码实现(PCL 库):

pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(cloud);
voxel_filter.setLeafSize(0.3f, 0.3f, 0.3f);  // 体素边长 0.3m
voxel_filter.filter(*cloud_filtered);

避坑指南:我曾经遇到过体素滤波后点云出现“空洞”的情况。后来发现是体素大小设置得太大,导致一些细长结构(比如电线杆)被完全滤掉了。建议先小范围测试,再确定参数。

3.2 直通滤波:快速裁剪感兴趣区域

直通滤波(PassThrough Filter)是最简单的点云裁剪方法。它沿着某个坐标轴(X、Y 或 Z)设置一个范围,只保留范围内的点。

举个例子:

  • 我们只关心前方 50 米内的点云,那就沿 X 轴设置 [0, 50]。
  • 我们只关心地面附近 5 米内的点云,那就沿 Z 轴设置 [-2, 3]。

直通滤波的优点是速度快,适合做粗过滤。但它有个明显的缺点:只能沿坐标轴方向裁剪,不能处理倾斜的平面。

pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(-1.5, 2.0);  // 保留 Z 轴 -1.5m 到 2.0m 的点
pass.filter(*cloud_filtered);

注意:直通滤波不能替代地面分割。它只是粗略地去掉远处的点或过高的点,地面和非地面仍然混在一起。

3.3 RANSAC 地面分割:经典但有限

RANSAC(Random Sample Consensus)是一种迭代算法,用于从包含大量噪声的数据中拟合出数学模型。在地面分割中,我们用它来拟合一个平面方程:

ax + by + cz + d = 0

算法步骤:

  1. 随机选取 3 个点,计算平面参数。
  2. 计算所有点到该平面的距离,统计距离小于阈值的内点数量。
  3. 重复步骤 1-2,找到内点最多的平面。
  4. 内点就是地面点,外点就是非地面点。

RANSAC 的优点是简单、鲁棒。但它有个致命问题:只能拟合一个平面。如果地面有坡度、起伏,或者有路沿、减速带,RANSAC 就会出错。

我的经验:RANSAC 适合平坦的停车场、高速公路等场景。但在城市道路、山区道路中,效果很差。我曾经在一条有坡度的路上用 RANSAC,结果把整个坡面都当成了地面,路边的树和行人全被误判为地面点。

3.4 Patchwork 地面分割:更聪明的选择

Patchwork 是我个人非常推荐的地面分割算法。它解决了 RANSAC 的痛点——能处理非平坦地面。

核心思想:

  • 将点云按极坐标划分成多个扇区(Sector)和环带(Ring)。
  • 在每个小区域内,用 RANSAC 拟合局部平面。
  • 通过区域生长和一致性检查,合并相邻的平面。
  • 最终得到完整的地面点云。

Patchwork 的优势:

  • 适应起伏地面:每个小区域独立拟合,能处理坡度变化。
  • 鲁棒性强:对噪声、遮挡有较好的容忍度。
  • 实时性好:经过优化后,可以在嵌入式平台上运行。

参数调优建议:Patchwork 有几个关键参数——扇区数量(通常 8~16)、环带数量(通常 4~8)、局部 RANSAC 的阈值(0.1m~0.3m)。我一般先固定扇区和环带,再调阈值。阈值太大,会把低矮障碍物误判为地面;阈值太小,地面点会被漏掉。

3.5 点云坐标系变换:让数据对齐

点云坐标系变换是高精地图构建的基础。我们经常需要在不同坐标系之间转换:

  • 传感器坐标系:激光雷达自身的坐标系,原点在雷达中心。
  • 车辆坐标系:原点在车辆后轴中心或质心。
  • 世界坐标系:通常是 UTM 坐标系或局部 ENU 坐标系。

变换公式:

P_world = R * P_sensor + T

其中 R 是旋转矩阵,T 是平移向量。

实际应用中,我们通常用齐次坐标:

// 4x4 变换矩阵
Eigen::Matrix4f transform = Eigen::Matrix4f::Identity();
transform.block<3,3>(0,0) = rotation_matrix;
transform.block<3,1>(0,3) = translation_vector;

// 应用变换
pcl::transformPointCloud(*cloud_sensor, *cloud_world, transform);

常见错误:坐标系变换的顺序很重要。先旋转后平移,还是先平移后旋转?答案是先旋转后平移。我曾经因为搞错顺序,导致点云全部偏移了 10 米,排查了半天才发现问题。

3.6 知识体系总览

下面这张图展示了本章的核心逻辑:

点云预处理与地面滤波知识体系 原始点云 预处理阶段 体素滤波降采样 直通滤波 坐标系变换 地面分割阶段 RANSAC 地面分割 (适合平坦地面) Patchwork 地面分割 (适合起伏地面)

从图中可以看出,整个流程分为两大阶段:预处理和地面分割。预处理阶段做降采样、裁剪和坐标系对齐;地面分割阶段根据场景选择合适的算法。

3.7 本章小结

这一章我们聊了四个核心内容:

  • 体素滤波降采样:给点云减肥,保留几何结构。
  • 直通滤波:快速裁剪,适合粗过滤。
  • RANSAC 地面分割:经典方法,适合平坦场景。
  • Patchwork 地面分割:更智能,适合复杂场景。
  • 坐标系变换:让数据对齐,注意旋转和平移的顺序。

嗯,内容就这些。下一章我们进入更核心的内容——点云配准与位姿优化。到时候见。

课后练习:

  1. 用 PCL 实现体素滤波,对比不同体素大小对点云密度的影响。
  2. 在包含坡度的点云数据上,分别用 RANSAC 和 Patchwork 做地面分割,对比效果。
  3. 写一个坐标系变换函数,将激光雷达坐标系下的点云转换到车辆坐标系。