4. 直通滤波(PassThrough)
各位好,今天我们聊点云滤波里最基础、也最常用的一招——直通滤波。
说实话,我刚开始接触激光雷达点云的时候,第一反应就是:这数据也太多了吧?一帧几十万个点,里面还有大量没用的背景、地面、远处的噪声。那时候我就在想,有没有一种简单粗暴的方法,先把明显没用的区域切掉?
嗯,直通滤波就是干这个的。
直通滤波的原理
直通滤波的原理,说白了就一句话:在某个坐标轴上设定一个范围,范围内的点保留,范围外的点扔掉。
你想想看,激光雷达扫描出来的点云,每个点都有 (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 万点。而且行人、车辆的关键特征一点没丢。
在X轴方向的应用
X 轴方向,也就是车辆的前后方向,这个在高速场景下特别有用。
你想想看,一辆车在高速上跑,前方 100 米以外的点云,其实对当前决策意义不大。而且远处的点稀疏、噪声大,反而会干扰特征提取。
我一般会设一个 X 轴的前向范围,比如 [0 米, 80 米]。0 米是车头位置,80 米是有效感知距离。超过 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);
你看,逻辑很清楚。每一步的输出作为下一步的输入,最终得到一个「长方体」范围内的点云。
知识体系图
下面我用一张 SVG 图来总结直通滤波的核心逻辑:
这张图很直观:原始点云进来,经过 Z、X、Y 三个轴的依次裁剪,最终得到一个规整的长方体区域。这就是直通滤波的完整流程。
总结一下
直通滤波,说白了就是「切豆腐」。把点云空间切成你想要的形状,剩下的都是有效数据。
- 优点:速度快、实现简单、效果立竿见影
- 缺点:只能做矩形裁剪,无法处理不规则形状
- 适用场景:已知目标空间分布、需要快速降采样、预处理阶段
我个人建议,在点云处理的预处理阶段,永远先做一次直通滤波。哪怕只是粗略地切一下,也能让后续的算法跑得更快、更准。
好,这一节就到这里。直通滤波虽然基础,但用好了能解决很多实际问题。下一节我们聊点更有意思的——体素滤波,看看怎么在保持点云形状的前提下进一步降采样。
可可精品资料库