跳到主要内容

点云过滤

摘要:本文整理直通滤波、体素下采样、统计离群点过滤与半径离群点过滤,从空间范围、点云密度和邻域分布理解参数作用。每种方法分别配合 PCL 核心代码讲解,说明如何为地面分割、聚类与配准准备输入。

1. 点云过滤的目标​

过滤用于限制处理范围、减少点数或去除孤立噪声。不同方法解决的问题不同:直通滤波按坐标范围选择点,体素下采样降低空间采样密度,离群点过滤根据邻域分布剔除异常点。过滤不能直接替代地面分割,也不保证保留下来的点都是有效目标。

下文代码为处理函数内的核心片段,输入 cloud 是非空的 PCL 点云指针,坐标单位为米;配置数值是教学示例,不是传感器通用默认值。

#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/filters/filter.h>
#include <vector>

using PointT = pcl::PointXYZ;
using Cloud = pcl::PointCloud<PointT>;

1.1 代码讲解:先移除无效坐标​

Cloud::Ptr finite_cloud(new Cloud);
std::vector<int> finite_to_raw;
pcl::removeNaNFromPointCloud(*cloud, *finite_cloud, finite_to_raw);

finite_to_raw[k] 记录过滤后第 k 个点在原始输入中的索引。输入存在无效坐标时,is_dense 必须正确设为 false;该标记若错误地为 true,过滤器可能跳过逐点检查。外部数据不可信时,应先用 pcl::isFinite 检查坐标及密集标记。

删点会改变点数和索引,后续搜索结构应基于过滤后的输入重新建立。含强度、时间或线束字段的数据,需要选用匹配的点类型;直接转成 PointXYZ 会丢失这些字段。

2. PassThrough(直通滤波)​

参考:PCL 直通滤波教程。

原理:选定一个字段,保留其值在指定范围内的点。例如对高度轴:

zmin⁡≤zi≤zmax⁡.z_{\min}\leq z_i\leq z_{\max}.

流程:选择字段 → 设置范围 → 判断每个点 → 输出保留点。

配置项示例值作用
setFilterFieldName"z"指定过滤字段
setFilterLimits-2.0, 3.0限制坐标范围,单位:米
setNegativefalsetrue 时反向选择范围外的点

2.1 代码讲解:裁剪高度范围​

#include <pcl/filters/passthrough.h>

// 以下过滤语句放在处理函数内部。
Cloud::Ptr roi_cloud(new Cloud);
pcl::PassThrough<PointT> pass;
pass.setInputCloud(finite_cloud);
pass.setFilterFieldName("z");
pass.setFilterLimits(-2.0f, 3.0f);
pass.setNegative(false);
pass.filter(*roi_cloud);

setInputCloud 指定当前输入,filter 将结果写入独立输出。范围描述的是当前坐标系中的 z,不能直接理解为离地高度;传感器坐标系与车辆坐标系的原点不同,需要先明确变换关系。

2.2 代码讲解:连续裁剪多个方向​

Cloud::Ptr forward_cloud(new Cloud);
pass.setInputCloud(roi_cloud);
pass.setFilterFieldName("x");
pass.setFilterLimits(0.0f, 50.0f);
pass.filter(*forward_cloud);

第二次过滤使用第一次的结果,因此保留的是两个条件的交集。多个方向可重复使用直通过滤器;需要旋转包围盒时,可考虑 pcl::CropBox,而不是将单轴范围当成任意方向的空间区域。

3. VoxelGrid(体素下采样)​

参考:PCL 体素下采样教程。

原理:建立三维体素网格,用每个非空体素内点的质心代表这些点:

pˉk=1nk∑pi∈Vkpi.\bar{p}_k=\frac{1}{n_k}\sum_{p_i\in V_k}p_i.

质心不一定是某个原始点,也不同于体素几何中心。体素下采样主要减少冗余采样,不会自动判断一个体素是不是噪声。

配置项示例值调参作用
setLeafSize0.2, 0.2, 0.2三个方向的体素尺寸 [m];增大通常减少点数,但损失细节
setMinimumPointsNumberPerVoxel1体素所需最少原始点数;增大可能去掉稀疏有效结构

3.1 代码讲解:设置网格并生成质心​

#include <pcl/filters/voxel_grid.h>

Cloud::Ptr sampled_cloud(new Cloud);
pcl::VoxelGrid<PointT> voxel;
voxel.setInputCloud(forward_cloud);
voxel.setLeafSize(0.2f, 0.2f, 0.2f);
voxel.setMinimumPointsNumberPerVoxel(1);
voxel.filter(*sampled_cloud);

三个叶尺寸都必须为正,可以根据场景设置不同的水平与垂直分辨率。网格较大时,小障碍物、路缘或细杆可能被合并,不能只以点数减少作为效果指标。

3.2 代码讲解:检查点数与索引关系​

const auto before_count = forward_cloud->size();
const auto after_count = sampled_cloud->size();
const double retained_ratio = before_count == 0 ? 0.0
: static_cast<double>(after_count) / before_count;

retained_ratio 用于记录输出规模,不能直接表示保留了多少原始表面细节。下采样后一个点代表多个原始点,没有天然的一对一索引映射。若需要将聚类结果回填到原始点云,应维护体素与原始索引关系,具体流程见点云聚类算法。

4. StatisticalOutlierRemoval(统计离群点过滤)​

参考:PCL 统计离群点过滤教程。

原理:对每个点计算其 K 个近邻的平均距离,再与整帧平均距离分布比较:

dˉi=1K∑j∈NK(i)∥pi−pj∥2.\bar{d}_i=\frac{1}{K}\sum_{j\in\mathcal{N}_K(i)}\|p_i-p_j\|_2.

设这些平均距离的全局均值与标准差为 μd,σd\mu_d,\sigma_d,常用保留判据为:

dˉi≤μd+λσd.\bar{d}_i\leq\mu_d+\lambda\sigma_d.

距离明显偏大的点被认为较孤立。该统计假设不一定适用于密度变化很大的整帧激光雷达点云。

配置项示例值调参作用
setMeanK30统计邻域的近邻数量;应结合有效点数与局部密度设置
setStddevMulThresh1.0标准差倍数;减小会更严格,增大保留更多点
setNegativefalsetrue 时输出被判定为离群的点

4.1 代码讲解:计算邻域统计并输出内点​

#include <pcl/filters/statistical_outlier_removal.h>

Cloud::Ptr clean_cloud(new Cloud);
pcl::StatisticalOutlierRemoval<PointT> sor;
sor.setInputCloud(sampled_cloud);
sor.setMeanK(30);
sor.setStddevMulThresh(1.0);
sor.setNegative(false);
sor.filter(*clean_cloud);

PCL 内部完成近邻搜索和距离统计。示例在下采样后计算邻域,参数应按下采样后的密度设置;如果先做统计过滤,输入规模与统计分布都不同,不能沿用同一组数值并期待相同结果。

4.2 代码讲解:查看被剔除点​

Cloud::Ptr rejected_cloud(new Cloud);
sor.setNegative(true);
sor.filter(*rejected_cloud);

反向输出可以检查远处物体、细杆或边缘是否被误删。过滤后点云更平滑,不代表结果一定更准确;应将保留点和剔除点一起观察。

5. RadiusOutlierRemoval(半径离群点过滤)​

参考:PCL 半径离群点过滤教程。

原理:在固定半径内统计邻居数量,邻居太少则删除。用不含查询点自身的邻域表示:

Nr(i)={j≠i:∥pi−pj∥2≤r},∣Nr(i)∣≥nmin⁡.\mathcal{N}_r(i)=\{j\ne i:\|p_i-p_j\|_2\leq r\},\qquad |\mathcal{N}_r(i)|\geq n_{\min}.
配置项示例值调参作用
setRadiusSearch0.5搜索半径 [m];应结合点间距设置
setMinNeighborsInRadius3最少邻居数;增大过滤更严格

5.1 代码讲解:根据局部点数过滤​

#include <pcl/filters/radius_outlier_removal.h>

Cloud::Ptr radius_clean_cloud(new Cloud);
pcl::RadiusOutlierRemoval<PointT> ror;
ror.setInputCloud(sampled_cloud);
ror.setRadiusSearch(0.5);
ror.setMinNeighborsInRadius(3);
ror.setNegative(false);
ror.filter(*radius_clean_cloud);

这里与 SOR 分别使用同一份 sampled_cloud,便于比较两种去噪方法。固定半径让判断具有明确空间尺度,但远处点云更稀疏,可能把有效目标删掉。半径过小或最少邻居数过大都会增强这种现象。

6. 过滤顺序与结果检查​

常见处理链是:无效点清理 → 范围裁剪 → 体素下采样 → 按需要选择离群点过滤。也可以先去噪再下采样,具体顺序由目标尺寸、噪声类型和耗时要求决定,不必将所有过滤器都串起来。

现象优先检查
低矮障碍物或细杆消失体素尺寸、范围边界、离群点阈值
远处目标点数大幅下降固定半径与密度变化、SOR 的全局统计
点数减少但配准变差是否丢失角点、立面和其他可约束位姿的结构
索引提取结果错位索引是否对应过滤前的另一份输入

在 RViz 或 PCL 可视化中比较前后点云,记录各阶段点数与耗时,并检查地面分割和聚类结果。参数效果应在下游任务中验证。

本篇文章受 CC BY-NC-SA 4.0 协议保护,转载请注明出处。