直通滤波(PassThrough):原理与参数调优

直通滤波,说白了就是「一刀切」。

你设定一个范围,比如 x 轴从 0 到 5 米,那么在这个范围之外的点,全部砍掉。就这么简单粗暴。但你别小看它,我做过那么多点云项目,直通滤波几乎是每次预处理的第一步。为什么?因为激光雷达扫出来的数据,有一大半都是没用的背景。

原理:一维范围裁剪

直通滤波的核心逻辑其实就一句话:对指定维度,保留区间内的点,删除区间外的点。

你想想看,点云里的每个点都有 (x, y, z) 坐标。我们只需要指定一个轴,比如 z 轴,然后设一个下限和上限。所有 z 值小于下限或大于上限的点,直接扔掉。剩下的点,就是你要的。

嗯,这里要注意:直通滤波只对单个轴操作。如果你想同时过滤 x 和 y,那就得调两次。不过 PCL 里可以链式调用,写起来不麻烦。

核心公式(其实不算公式):
保留条件:axis_min ≤ point.axis ≤ axis_max
删除条件:point.axis < axis_min 或 point.axis > axis_max

参数调优:三个轴的范围怎么定?

我个人习惯,先看场景再设参数。比如你放了一个 Velodyne 在车顶,地面高度大概是 -1.5 米,车顶高度 2 米。那 z 轴范围就可以设成 -1.5 到 2.0。但如果你在室内,地面是 0,天花板是 3 米,那范围就变了。

调参没有万能公式,但有经验法则:

参数 说明 调优建议
filter_field_name 指定过滤轴(x/y/z) 根据目标物体分布选轴。去除地面用 z,去除左右墙壁用 x 或 y
filter_limit_min 下限值 先看直方图,取目标点云的最低值再留 0.1 米余量
filter_limit_max 上限值 同理,取最高值加 0.1 米余量
setFilterLimitsNegative 是否取反(保留区间外) 默认 false。设为 true 时,保留区间外的点,用于去除特定区域
我的小技巧: 先用可视化工具(比如 pcl_viewer)看一眼点云的分布范围。或者用代码打印出 x/y/z 的最大最小值。这样你设参数就有底了,不用瞎猜。

实战案例:去除背景点云

我记得有一次做室内机器人定位,激光雷达放在机器人顶部。扫描出来的点云里,天花板和地板占了将近 60% 的点。这些点对定位毫无帮助,反而增加计算量。直通滤波就是干这个的。

下面是一个完整的 C++ 示例,用 PCL 库实现:

#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/io/pcd_io.h>

int main() {
    // 1. 加载原始点云
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::io::loadPCDFile("room_scan.pcd", *cloud);
    std::cout << "原始点数: " << cloud->size() << std::endl;

    // 2. 创建直通滤波对象
    pcl::PassThrough<pcl::PointXYZ> pass;
    pass.setInputCloud(cloud);

    // 3. 过滤 z 轴:去除天花板和地板
    pass.setFilterFieldName("z");
    pass.setFilterLimits(0.2, 2.5);  // 保留 0.2m 到 2.5m 之间的点
    // pass.setFilterLimitsNegative(true);  // 如果设为 true,则保留 0.2m 以下和 2.5m 以上的点

    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered_z(new pcl::PointCloud<pcl::PointXYZ>);
    pass.filter(*cloud_filtered_z);
    std::cout << "z轴过滤后: " << cloud_filtered_z->size() << std::endl;

    // 4. 再过滤 x 轴:去除左右墙壁
    pass.setInputCloud(cloud_filtered_z);
    pass.setFilterFieldName("x");
    pass.setFilterLimits(-3.0, 3.0);  // 保留 -3m 到 3m 之间的点

    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered_x(new pcl::PointCloud<pcl::PointXYZ>);
    pass.filter(*cloud_filtered_x);
    std::cout << "x轴过滤后: " << cloud_filtered_x->size() << std::endl;

    // 5. 保存结果
    pcl::io::savePCDFile("room_filtered.pcd", *cloud_filtered_x);
    return 0;
}

这段代码做了两件事:先砍掉天花板和地板(z 轴),再砍掉左右墙壁(x 轴)。最终留下的就是机器人周围的障碍物点云。

避坑指南: 我曾经犯过一个低级错误——先过滤了 x 轴,再过滤 z 轴,结果顺序搞反了。因为 x 轴过滤后点云变少,z 轴过滤时输入点云已经不对了。记住:先过滤范围大的轴,再过滤范围小的轴,这样效率更高。

直通滤波的适用场景

  • 去除固定背景:比如天花板、地板、墙壁、地面
  • 提取感兴趣区域:比如只保留前方 10 米内的点云
  • 预处理降采样:在体素滤波之前,先砍掉无用区域,减少计算量

但直通滤波也有局限。它只能做矩形裁剪,不能做圆形或任意形状。如果你需要更复杂的空间裁剪,那就得上条件滤波或区域生长分割了。

知识体系图

下面这张图帮你理清直通滤波在整个点云处理流程中的位置:

直通滤波知识体系 原始点云 (x,y,z) 直通滤波核心步骤 选择轴 → 设范围 → 执行裁剪 filter_field_name 选择 x/y/z 轴 filter_limit_min/max 设置上下限 setFilterLimitsNegative 是否取反 过滤后点云

从图上可以看得很清楚:直通滤波就是一条直线流程。输入原始点云,选轴、设范围、执行裁剪,输出过滤后的点云。没有复杂的数学,没有迭代计算,就是纯粹的「切一刀」。

但这一刀切得好不好,直接决定后续处理的效率。我见过有人上来就做体素滤波,结果体素滤波把背景和前景一起降采样了,后面分割根本分不开。先做直通滤波砍掉背景,再做体素滤波降采样,顺序对了,效果天差地别。

好了,直通滤波就讲这么多。记住一句话:先砍背景,再谈其他。

可可精品资料库