直通滤波(PassThrough):原理讲解、在Z轴和X轴方向的应用、PCL代码实现与多轴联合滤波

各位好,今天我们聊点云滤波里最基础、也最常用的一招——直通滤波。

说实话,我刚开始接触激光雷达点云的时候,第一反应就是:这数据也太多了吧?一帧几十万个点,里面还有大量没用的背景、地面、远处的噪声。那时候我就在想,有没有一种简单粗暴的方法,先把明显没用的区域切掉?

嗯,直通滤波就是干这个的。

直通滤波的原理

直通滤波的原理,说白了就一句话:在某个坐标轴上设定一个范围,范围内的点保留,范围外的点扔掉。

你想想看,激光雷达扫描出来的点云,每个点都有 (x, y, z) 三个坐标值。如果你知道目标物体只出现在某个高度范围内,比如 0.5 米到 2.0 米之间,那低于 0.5 米的地面点和高于 2.0 米的树枝点,直接滤掉就行了。

它的数学表达也很简单:

if (min <= point.z <= max) then keep
else discard

就这么一个条件判断。但你别小看它,我在项目中遇到过很多情况,光靠这一招就能把数据量砍掉 60% 以上,而且几乎不损失有效信息。

核心思想: 直通滤波的本质是「空间裁剪」——在已知目标空间分布的前提下,快速剔除无关区域。

在Z轴方向的应用

Z轴方向,也就是高度方向,是我用得最多的场景。

我记得有一次做园区自动驾驶的项目,激光雷达装在车顶,高度大概 1.8 米。周围环境有地面、有行人、有低矮的灌木、还有高处的树枝。我们的目标是检测行人和车辆,那地面和树枝就是典型的噪声。

我当时设的 Z 轴范围是:[-0.3 米, 2.0 米]。

  • 低于 -0.3 米的点:一般是地面反射或者路面凹陷,直接滤掉
  • 高于 2.0 米的点:树枝、路灯、广告牌,也滤掉

效果怎么样?一帧 30 万点的数据,滤完之后剩下不到 10 万点。而且行人、车辆的关键特征一点没丢。

我的习惯: Z 轴的下限不要设成 0,留一点负值余量。因为路面有起伏,激光雷达安装也有误差,设成 0 可能会把一些有用的低矮障碍物切掉。

在X轴方向的应用

X 轴方向,也就是车辆的前后方向,这个在高速场景下特别有用。

你想想看,一辆车在高速上跑,前方 100 米以外的点云,其实对当前决策意义不大。而且远处的点稀疏、噪声大,反而会干扰特征提取。

我一般会设一个 X 轴的前向范围,比如 [0 米, 80 米]。0 米是车头位置,80 米是有效感知距离。超过 80 米的点,直接扔掉。

这样做的好处有两个:

  1. 减少计算量:远处的点占用了大量计算资源,但贡献很小
  2. 提高精度:远处的点噪声大,容易造成误检
注意: X 轴滤波要小心处理负方向。如果车后面也有激光雷达的视场角,负方向的点可能是后方来车,不能一刀切。我一般会保留 [-10 米, 80 米] 的范围,给后方留一点余量。

PCL代码实现

好了,原理讲完了,咱们上代码。PCL 里直通滤波的实现非常简洁,我直接贴一个我常用的模板:

#include <pcl/filters/passthrough.h>

// 输入点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);

// 创建直通滤波对象
pcl::PassThrough<pcl::PointXYZ> pass;

// 设置输入点云
pass.setInputCloud(cloud);

// 设置滤波轴为 Z 轴
pass.setFilterFieldName("z");

// 设置范围:保留 0.0 到 2.0 米之间的点
pass.setFilterLimits(0.0, 2.0);

// 执行滤波
pass.filter(*cloud_filtered);

// 输出结果
std::cout << "滤波前点数: " << cloud->size() << std::endl;
std::cout << "滤波后点数: " << cloud_filtered->size() << std::endl;

这段代码,你直接复制就能跑。唯一要注意的是 setFilterFieldName 这个函数,参数是字符串,可以是 "x"、"y"、"z",对应三个轴。

多轴联合滤波

单轴滤波虽然好用,但很多时候我们需要同时限制多个方向。比如,我只想保留车辆前方 80 米、高度 0.5 到 2.0 米、左右宽度 ±5 米范围内的点。

这时候就需要多轴联合滤波。

PCL 里没有直接提供多轴滤波的函数,但实现起来很简单——串行调用就行了。先对 Z 轴滤波,再对 X 轴滤波,最后对 Y 轴滤波。

// 第一步:Z 轴滤波
pcl::PassThrough<pcl::PointXYZ> pass_z;
pass_z.setInputCloud(cloud);
pass_z.setFilterFieldName("z");
pass_z.setFilterLimits(0.5, 2.0);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_z(new pcl::PointCloud<pcl::PointXYZ>);
pass_z.filter(*cloud_z);

// 第二步:X 轴滤波
pcl::PassThrough<pcl::PointXYZ> pass_x;
pass_x.setInputCloud(cloud_z);
pass_x.setFilterFieldName("x");
pass_x.setFilterLimits(0.0, 80.0);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_x(new pcl::PointCloud<pcl::PointXYZ>);
pass_x.filter(*cloud_x);

// 第三步:Y 轴滤波
pcl::PassThrough<pcl::PointXYZ> pass_y;
pass_y.setInputCloud(cloud_x);
pass_y.setFilterFieldName("y");
pass_y.setFilterLimits(-5.0, 5.0);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pass_y.filter(*cloud_filtered);

你看,逻辑很清楚。每一步的输出作为下一步的输入,最终得到一个「长方体」范围内的点云。

避坑指南: 我曾经犯过一个错误——先对 X 轴滤波,再对 Z 轴滤波。结果发现远处的点虽然被切掉了,但近处高处的树枝点还在。后来我意识到,滤波顺序会影响结果。我的建议是:先滤 Z 轴(高度),再滤 X 轴(前后),最后滤 Y 轴(左右)。因为 Z 轴滤波通常能砍掉最多的点,先做可以减轻后续计算压力。

知识体系图

下面我用一张 SVG 图来总结直通滤波的核心逻辑:

直通滤波核心逻辑 原始点云 Z轴滤波 高度范围裁剪 X轴滤波 前后范围裁剪 Y轴滤波 左右范围裁剪 多轴联合滤波 → 长方体区域点云 滤波后点云

这张图很直观:原始点云进来,经过 Z、X、Y 三个轴的依次裁剪,最终得到一个规整的长方体区域。这就是直通滤波的完整流程。

总结一下

直通滤波,说白了就是「切豆腐」。把点云空间切成你想要的形状,剩下的都是有效数据。

  • 优点:速度快、实现简单、效果立竿见影
  • 缺点:只能做矩形裁剪,无法处理不规则形状
  • 适用场景:已知目标空间分布、需要快速降采样、预处理阶段

我个人建议,在点云处理的预处理阶段,永远先做一次直通滤波。哪怕只是粗略地切一下,也能让后续的算法跑得更快、更准。

好,这一节就到这里。直通滤波虽然基础,但用好了能解决很多实际问题。下一节我们聊点更有意思的——体素滤波,看看怎么在保持点云形状的前提下进一步降采样。

一句话记住: 直通滤波 = 空间裁剪,三轴联合 = 长方体区域。

可可精品资料库