3. 点云预处理:体素滤波、直通滤波、统计滤波、半径滤波

点云预处理,说白了就是给激光雷达的数据「洗个澡」。

我刚开始做自动驾驶感知时,总觉得原始点云挺干净的,直接丢给聚类算法不就行了?结果第一次跑出来的障碍物轮廓,简直像毕加索的画——该有的没有,不该有的全冒出来了。后来才明白,不经过预处理的点云,就像没筛过的面粉,做出来的面包里全是疙瘩。

今天咱们就把这四种最常用的滤波方法掰开揉碎讲清楚。你想想看,激光雷达一秒钟扫出几十万个点,这里面有噪声、有地面、有远处的稀疏点,还有各种飞鸟和扬尘。不处理?算法直接崩溃给你看。

核心观点:点云预处理不是可选项,而是必选项。它直接决定了后续感知模块的精度和实时性。

点云预处理流程 原始点云 体素滤波 降采样 直通滤波 裁剪ROI 统计滤波 去离群点 半径滤波 去孤立点 预处理后点云 体素滤波 → 直通滤波 → 统计/半径滤波(可并行或串行)

3.1 体素滤波:给点云「瘦身」

体素滤波的原理其实特别简单。想象一下,你把三维空间切成一个个小立方体,每个立方体就叫一个体素。然后每个体素里只保留一个点——通常是这个体素内所有点的重心。这样一来,点云数量就大幅减少了。

我个人的习惯是,体素大小设成 0.1m 到 0.2m 之间。太小了降采样效果不明显,太大了会把障碍物的细节抹掉。有一次我在高速场景测试,体素设成 0.5m,结果一辆自行车直接被滤成了两个点——聚类算法死活认不出来。

// Autoware 中体素滤波的典型配置
pcl::VoxelGrid<pcl::PointXYZI> voxel_filter;
voxel_filter.setInputCloud(input_cloud);
voxel_filter.setLeafSize(0.1f, 0.1f, 0.1f);  // 体素边长 10cm
voxel_filter.filter(*output_cloud);

小技巧:体素大小不是越小越好。城市道路建议 0.1m,高速公路可以放宽到 0.2m。如果你做的是泊车场景,0.05m 会更合适。

3.2 直通滤波:裁剪出你关心的区域

直通滤波,说白了就是「切一刀」。你指定一个坐标轴的范围,比如 X 轴从 0 到 50 米,Y 轴从 -10 到 10 米,Z 轴从 -1 到 2 米。所有在这个范围之外的点,统统砍掉。

为什么要这么做?因为激光雷达会扫到很多没用的东西。比如车顶上的树枝、远处的天空、甚至飞过的鸟。这些点不仅没用,还会干扰后续的聚类和跟踪。

我记得有一次在隧道里测试,直通滤波的 Z 轴范围设得太小了,结果把隧道顶部的点全切掉了。障碍物检测倒是正常,但定位模块因为缺少隧道顶部的特征点,直接飘了。嗯,这里要注意——直通滤波的裁剪范围要跟你的应用场景匹配。

// Autoware 中直通滤波的典型配置
pcl::PassThrough<pcl::PointXYZI> pass_filter;
pass_filter.setInputCloud(input_cloud);
pass_filter.setFilterFieldName("z");        // 沿 Z 轴裁剪
pass_filter.setFilterLimits(-1.0, 2.5);     // 保留 -1m 到 2.5m 之间的点
pass_filter.filter(*output_cloud);

注意:直通滤波的顺序很重要。先做体素滤波再做直通滤波,可以减少计算量。反过来,先切掉大部分区域再降采样,也能省内存。具体哪个先,看你更在意 CPU 还是内存。

3.3 统计滤波:干掉那些「不合群」的点

统计滤波的原理,是基于一个朴素的观察:噪声点通常离它的邻居很远。你想想看,一个正常的障碍物表面,点与点之间的距离是均匀的。但一个噪声点,比如激光打到空气中的尘埃上,它周围可能一个点都没有。

具体做法是:对每个点,计算它到所有邻居的平均距离。如果这个平均距离超过了全局均值加上若干倍标准差,就判定为离群点,直接干掉。

我一般把标准差倍数设成 1.0 到 2.0 之间。倍数太小,会把正常的点也误删;倍数太大,噪声点又滤不干净。曾经有一次我把倍数设成 3.0,结果发现点云里全是飞点——后来一查,是激光雷达的反射率校准出了问题。

// Autoware 中统计滤波的典型配置
pcl::StatisticalOutlierRemoval<pcl::PointXYZI> stat_filter;
stat_filter.setInputCloud(input_cloud);
stat_filter.setMeanK(50);                  // 邻居数量
stat_filter.setStddevMulThresh(1.0);       // 标准差倍数
stat_filter.filter(*output_cloud);

经验值:setMeanK 设成 50 是个不错的起点。如果点云密度高,可以增加到 100;密度低,减少到 20。这个参数需要根据你的激光雷达型号来调。

3.4 半径滤波:更严格的「社交距离」

半径滤波和统计滤波有点像,但判定标准更直接。它检查每个点周围指定半径内,有没有足够多的邻居。如果不够,就认为这个点是孤立的,删掉。

举个例子,你设半径 0.3 米,最少邻居数 5 个。那么一个点周围 0.3 米内如果少于 5 个点,就会被干掉。这个方法的优点是参数直观,好理解。

我个人觉得,半径滤波更适合处理稀疏场景。比如在空旷的停车场,激光雷达扫到的点本来就少,统计滤波容易误删。但半径滤波只要半径设得合理,就能保留那些真正有用的点。

// Autoware 中半径滤波的典型配置
pcl::RadiusOutlierRemoval<pcl::PointXYZI> radius_filter;
radius_filter.setInputCloud(input_cloud);
radius_filter.setRadiusSearch(0.3);        // 搜索半径 0.3m
radius_filter.setMinNeighborsInRadius(5);  // 最少邻居数
radius_filter.filter(*output_cloud);

避坑指南:我曾经在雨天测试时,发现半径滤波把雨滴点全滤掉了,但同时也把远处一辆车的轮廓给削掉了一半。后来我把半径从 0.3 米缩小到 0.15 米,问题就解决了。所以参数一定要根据实际场景微调。

3.5 四种滤波的对比与选择

滤波方法 主要用途 关键参数 适用场景
体素滤波 降采样,减少点云数量 体素大小 所有场景,尤其是计算资源受限时
直通滤波 裁剪 ROI,去除无关区域 坐标轴、范围 固定场景,如城市道路、高速公路
统计滤波 去除稀疏离群点 邻居数、标准差倍数 点云密度均匀的场景
半径滤波 去除孤立点 搜索半径、最少邻居数 稀疏场景或需要严格去噪时

实际项目中,我通常把这四种滤波组合使用。先体素滤波降采样,再直通滤波裁剪 ROI,最后根据场景选择统计滤波或半径滤波。如果点云噪声特别多,两个都上也没问题——只是要注意计算时间。

好了,点云预处理这块就聊到这儿。记住一句话:预处理做得好,后续算法没烦恼。你把这些滤波玩明白了,Autoware 的感知模块就算入门了。

可可精品资料库