3. 直通滤波

直通滤波,这个名字听起来挺唬人。其实说白了,就是一刀切。

你想想看,我们拿到的激光雷达点云,一帧数据里可能有几十万个点。但真正有用的区域,往往只占一小块。比如做高精地图,我只关心路面和路侧的结构,头顶上的树冠、远处的天空,对我来说就是噪声。这时候,直通滤波就是最干脆的武器——沿着某个坐标轴,设定一个范围,范围内的留下,范围外的砍掉。

我个人习惯把直通滤波叫做「空间剪刀」。它不跟你讲什么概率分布、邻域关系,就是简单粗暴地按坐标切。但你别小看它,在工程落地中,这招往往是效率最高的预处理手段。

3.1 单轴裁剪:最基础的玩法

先看最简单的场景。我只想保留 Z 轴(高度)在 0.5 米到 3.0 米之间的点。为什么是这个范围?因为高精地图的要素——路沿、标志牌、车道线——基本都落在这个高度区间里。

代码写起来也很直白:

// C++ 示例:PCL 直通滤波
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");      // 沿 Z 轴裁剪
pass.setFilterLimits(0.5, 3.0);    // 保留 0.5m ~ 3.0m
pass.setFilterLimitsNegative(false); // false 表示保留区间内
pass.filter(*cloud_filtered);

这里有个细节我要提醒你。setFilterLimitsNegative 这个参数,默认是 false。如果你把它改成 true,意思就反过来了——保留区间外的点。我在项目中遇到过一种情况:想快速剔除地面附近的噪点,就把下限设成 0.3 米,然后取反,只保留 0.3 米以上的点。嗯,这个技巧在清理地面反射杂波时特别好用。

核心要点: 单轴裁剪的关键是选对坐标轴。Z 轴用于高度过滤,X/Y 轴用于横向范围控制。选错了轴,你切出来的就是一堆废数据。

3.2 ROI 提取:从全局到局部

单轴裁剪只是热身。真正干活的时候,我们往往需要划定一个三维的 ROI(感兴趣区域)。比如我只想提取前方 50 米、左右各 15 米、高度 0 到 4 米范围内的点。这就是多轴联合滤波。

我习惯的做法是:先沿 X 轴切一刀,再沿 Y 轴切一刀,最后沿 Z 轴收尾。顺序其实无所谓,但有个小窍门——先切数据量最大的轴。比如你的点云在 X 方向分布很广,那就先切 X,这样后续处理的数据量会大幅减少,速度能快不少。

看代码:

// 多轴联合滤波:先 X,再 Y,最后 Z
pcl::PassThrough<pcl::PointXYZ> pass_x;
pass_x.setInputCloud(cloud);
pass_x.setFilterFieldName("x");
pass_x.setFilterLimits(0.0, 50.0);
pass_x.filter(*cloud_x);

pcl::PassThrough<pcl::PointXYZ> pass_y;
pass_y.setInputCloud(cloud_x);
pass_y.setFilterFieldName("y");
pass_y.setFilterLimits(-15.0, 15.0);
pass_y.filter(*cloud_y);

pcl::PassThrough<pcl::PointXYZ> pass_z;
pass_z.setInputCloud(cloud_y);
pass_z.setFilterFieldName("z");
pass_z.setFilterLimits(0.0, 4.0);
pass_z.filter(*cloud_roi);

这段代码看起来有点啰嗦,但逻辑很清晰。每个 PassThrough 对象只干一件事,然后串起来。我曾经试过用一个对象反复调用,结果发现内部状态会残留,容易出 bug。所以我的建议是:每个轴单独建一个滤波器对象,别偷懒。

实战技巧: 如果你用 Python 的 Open3D 或 PCL 的 Python 绑定,可以用循环来简化代码。但 C++ 里我建议保持显式,可读性更重要。

3.3 多轴联合滤波的避坑指南

多轴联合滤波看着简单,但坑不少。我踩过的几个,今天一并告诉你。

第一个坑:边界效应。 你设了 X 轴范围 0 到 50 米,但激光雷达在 0 米附近(也就是车头位置)往往有盲区。这些盲区里的点坐标可能是 NaN 或者异常值。直通滤波不会帮你处理 NaN,它会直接保留或剔除。所以我的习惯是:在滤波之前,先做一步 NaN 点剔除。

// 先剔除 NaN 点
std::vector<int> indices;
pcl::removeNaNFromPointCloud(*cloud, *cloud, indices);

第二个坑:坐标系对齐。 直通滤波是沿着坐标轴切的。如果你的点云坐标系没有和车体坐标系对齐,切出来的 ROI 就是歪的。比如车在坡道上,Z 轴不是垂直地面,你按 Z 轴切高度,切出来的根本不是真实高度。我遇到过有人拿这个数据去训练模型,结果模型学了一堆倾斜的特征,怎么调都调不好。

解决办法很简单:滤波之前,先把点云转换到车体坐标系或者一个水平基准坐标系下。这一步叫「去倾斜」或「地面校正」。后面我们会专门讲。

第三个坑:性能问题。 直通滤波虽然快,但如果你在循环里反复调用,比如每帧数据都要切好几次,累积起来也慢。我建议你把滤波参数提前算好,不要每帧都重新设置。另外,如果点云数据量特别大(比如超过 100 万点),可以考虑先用体素滤波降采样,再做直通滤波。顺序别搞反了——先降采样再直通,能省一半时间。

警告: 不要在生产环境中对每一帧都动态调整滤波范围。除非你的场景变化特别剧烈,否则固定参数就够用。动态调整容易引入不稳定因素,而且调试起来非常痛苦。

3.4 知识体系:直通滤波的核心逻辑

说了这么多,我画了一张图帮你梳理一下。直通滤波的整个流程,其实就三步:选轴、定范围、执行裁剪。但每一步都有讲究。

直通滤波核心逻辑 原始点云 第一步:选择坐标轴 X(前后) / Y(左右) / Z(高度) 第二步:设定范围与模式 下限 ~ 上限 | 保留区间内 / 保留区间外 第三步:执行裁剪 单轴 / 多轴串联 ROI 点云

这张图你看懂了吗?从原始点云出发,经过三步处理,最终得到 ROI 点云。每一步都有参数需要你根据实际场景去调。没有万能参数,只有最适合你数据的参数。

3.5 实战经验:什么时候该用直通滤波?

直通滤波不是万能的。它只适合那些「空间分布有明显边界」的场景。比如:

  • 道路场景: 只保留路面以上、标志牌以下的高度范围。
  • 停车场: 只保留车辆周围 10 米内的点,远处的墙和柱子不要。
  • 隧道内: 沿隧道走向切一个长条形的 ROI,剔除两侧墙壁的干扰。

但如果你面对的是密集的灌木丛、复杂的城市峡谷,直通滤波就有点力不从心了。这时候你需要结合统计滤波或半径滤波来剔除离群点。不过那是后面章节的内容,今天先把直通滤波吃透。

最后说一句:直通滤波是所有滤波方法里最「笨」的,但也是最可靠的。它不会引入任何数学假设,不会改变点的位置,只是简单地做取舍。在高精地图的工程实践中,我见过太多花里胡哨的算法最后被直通滤波替代的例子。有时候,简单就是最好的。

一句话总结: 直通滤波就是用坐标轴当剪刀,把点云剪成你想要的样子。别想复杂了,先剪再说。

可可精品资料库