第四章:点云预处理基础

各位同学,今天我们来聊聊点云预处理。说实话,很多做无人机避障的朋友,一上来就急着跑算法,结果发现效果一塌糊涂。我当年也犯过这个错——第一次把激光雷达装到无人机上,点云数据直接喂给避障算法,结果无人机差点撞墙。后来才明白,原始点云就像一块没经过雕琢的璞玉,必须先打磨才能用

4.1 为什么要做点云预处理?

你想想看,激光雷达扫出来的点云,里面有多少噪声?地面反射、空中漂浮物、传感器本身的误差……这些乱七八糟的点,如果不处理,避障算法根本没法正常工作。

我个人习惯把点云预处理分成三个步骤:

  • 滤波:去掉不关心的区域,降低数据量
  • 降采样:让点云更均匀,减少计算负担
  • 去噪:剔除那些明显不合理的离群点

说白了,预处理的目的就两个:减少数据量提高数据质量。无人机上的计算资源有限,你不可能把几十万个点全拿去算一遍,对吧?

核心观点:点云预处理不是可选项,而是必选项。不做预处理的避障系统,就像闭着眼睛开车——迟早要出事。

点云预处理流程 原始点云 直通滤波 去掉无关区域 体素滤波 降采样/均匀化 统计滤波 去除离群点 干净点云 三步走:先切区域 → 再降采样 → 最后去噪

4.2 直通滤波:先切一刀

直通滤波,名字听着挺唬人,其实原理特别简单——就是按坐标轴切一刀。比如无人机飞行高度是10米,那10米以上的点云你关心吗?不关心。10米以下的点云,才是避障需要的数据。

我在项目中遇到过这样的情况:有一次在室内测试,激光雷达把天花板上的灯都扫进去了,结果避障算法以为上面有障碍物,死活不敢上升。后来加了直通滤波,把高度限制在3米以内,问题就解决了。

直通滤波的用法:

// C++ 示例:直通滤波
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z");        // 对Z轴进行滤波
pass.setFilterLimits(0.0, 3.0);      // 保留0到3米范围内的点
// pass.setFilterLimitsNegative(true); // 如果设为true,则保留范围外的点
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
pass.filter(*cloud_filtered);

小技巧:直通滤波可以同时对多个轴操作。我习惯先对Z轴滤波去掉地面和天空,再对X/Y轴滤波去掉无人机两侧不关心的区域。这样数据量能减少一半以上。

4.3 体素滤波:让点云更"均匀"

直通滤波之后,点云还是太密。你想想看,激光雷达扫到近处的墙,可能每平方厘米就有几十个点;扫到远处的树,可能几平方米才一个点。这种不均匀的数据,直接拿去算避障,结果肯定不准。

体素滤波的核心思想:把空间切成一个个小立方体(体素),每个立方体里只保留一个点。这个点可以是立方体里所有点的重心,也可以是离重心最近的点。

说白了,就是用"代表点"代替"群体点"。体素越大,降采样越狠,但精度损失也越大。

体素大小 效果 适用场景
0.01m (1cm) 几乎不降采样 高精度建图
0.05m (5cm) 适度降采样 室内避障
0.10m (10cm) 大幅降采样 室外快速避障
0.20m (20cm) 严重降采样 远距离粗略感知
// C++ 示例:体素滤波
pcl::VoxelGrid<pcl::PointXYZ> voxel;
voxel.setInputCloud(cloud_filtered);
voxel.setLeafSize(0.05f, 0.05f, 0.05f);  // 5cm体素
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_voxel(new pcl::PointCloud<pcl::PointXYZ>);
voxel.filter(*cloud_voxel);

注意:体素大小不是越小越好。我见过有人把体素设成0.01m,结果点云还是几十万个点,无人机飞控根本跑不动。对于大多数避障场景,0.05m到0.10m是比较合理的范围。

4.4 统计滤波:揪出"捣乱分子"

直通滤波和体素滤波都做完了,点云看起来干净了不少。但还有一些"捣乱分子"——离群点。这些点可能是激光打到玻璃上产生的反射噪声,也可能是飞鸟或者树叶偶尔飘过。

StatisticalOutlierRemoval(统计滤波)的原理很简单:计算每个点周围K个邻居的平均距离,如果这个距离明显大于平均值,就认为它是离群点。

嗯,这里要注意:统计滤波有两个关键参数:

  • K(邻居数量):一般取50-100,点云越密,K可以越大
  • stddev_mult(标准差倍数):一般取1.0-2.0,值越小,剔除越狠

我曾经在户外测试时,发现点云里总有一些"飘浮"的点,离主体很远。一开始以为是传感器坏了,后来用统计滤波一看,这些点的邻居距离是正常点的5倍以上。把stddev_mult设为1.5,这些噪声点就被清掉了。

// C++ 示例:统计滤波去除离群点
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud_voxel);
sor.setMeanK(50);              // 每个点考虑50个邻居
sor.setStddevMulThresh(1.5);   // 标准差倍数阈值
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_clean(new pcl::PointCloud<pcl::PointXYZ>);
sor.filter(*cloud_clean);

经验之谈:统计滤波的参数需要根据实际场景调整。室内环境噪声少,stddev_mult可以设大一点(1.5-2.0);室外环境复杂,设小一点(1.0-1.5)。我一般先在Rviz里可视化看看效果,再微调参数。

4.5 三步走:完整的预处理流程

在实际项目中,我通常把这三个滤波串起来用:

  1. 直通滤波:先切出感兴趣的区域,去掉90%的无用数据
  2. 体素滤波:降采样到可接受的点数(一般1-5万个点)
  3. 统计滤波:最后清理一下残留的噪声点

你可能会问:顺序能不能换?我试过先体素再直通,效果差不多,但计算量会大一些。因为体素滤波要对所有点做计算,如果先直通把数据量减下来,体素滤波就跑得更快。

完整的代码示例:

// 完整的预处理流程
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_raw(new pcl::PointCloud<pcl::PointXYZ>);
// ... 加载原始点云 ...

// 第一步:直通滤波
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud_raw);
pass.setFilterFieldName("z");
pass.setFilterLimits(0.0, 3.0);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_pass(new pcl::PointCloud<pcl::PointXYZ>);
pass.filter(*cloud_pass);

// 第二步:体素滤波
pcl::VoxelGrid<pcl::PointXYZ> voxel;
voxel.setInputCloud(cloud_pass);
voxel.setLeafSize(0.05f, 0.05f, 0.05f);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_voxel(new pcl::PointCloud<pcl::PointXYZ>);
voxel.filter(*cloud_voxel);

// 第三步:统计滤波
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud_voxel);
sor.setMeanK(50);
sor.setStddevMulThresh(1.5);
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_clean(new pcl::PointCloud<pcl::PointXYZ>);
sor.filter(*cloud_clean);

// 现在 cloud_clean 就是预处理后的干净点云
std::cout << "原始点数: " << cloud_raw->size() << std::endl;
std::cout << "处理后点数: " << cloud_clean->size() << std::endl;

调试建议:在ROS里,我习惯把每一步的结果都发布成话题,在Rviz里实时观察。比如 /cloud_raw/cloud_pass/cloud_voxel/cloud_clean。这样哪个步骤出了问题,一眼就能看出来。

4.6 避坑指南

最后分享几个我踩过的坑:

  • 参数调优要因地制宜:同一个参数,室内和室外效果可能天差地别。我建议做一套参数自动适配的逻辑,根据点云密度动态调整体素大小和统计滤波参数。
  • 注意坐标系:直通滤波是在传感器坐标系下操作的。如果无人机倾斜了,Z轴滤波可能会切掉不该切的部分。这时候可以先做一次坐标变换,或者用条件滤波代替。
  • 不要过度滤波:我曾经为了追求"干净"的点云,把体素设得很大,结果远处的障碍物直接被滤没了。避障算法以为前方一片坦途,差点撞上。
  • 性能监控:在嵌入式平台上,每一步滤波都要计时。如果体素滤波花了50ms,那整个系统就卡住了。我一般要求预处理总耗时不超过20ms。

好了,点云预处理的基础就讲到这里。记住一句话:预处理做得好,避障算法跑得稳。下一节我们会把这些预处理后的点云,真正用到避障算法里去。


专注资料整理