点云基础与预处理:从原始数据到可用特征
大家好,我是你们这堂课的主讲。今天咱们聊聊点云预处理。说实话,我刚入行那会儿,觉得点云不就是一堆三维点嘛,直接扔进网络里训练不就完了?结果被现实狠狠教育了一顿——原始点云数据,那叫一个脏乱差。
你想想看,激光雷达扫一圈下来,少说几万到几十万个点。这里面有地面、有车辆、有行人,还有各种噪声点、离群点。直接处理?算力扛不住,精度也上不去。所以,预处理这一步,说白了就是给数据「洗个澡」,让它变得干净、规整、好用。
点云数据结构:理解你的数据长什么样
先说说点云的数据结构。最常见的格式就是 PCD(Point Cloud Data)和 PLY(Polygon File Format)。我个人习惯用 PCD,因为 ROS 生态里支持得最好。
一个点云文件,本质上就是一个 N×D 的矩阵。N 是点的数量,D 是每个点的维度。最基本的维度是 x、y、z 三个坐标。但实际项目中,我们还会用到强度(intensity)、时间戳、甚至 RGB 颜色信息。
核心概念:点云不是图像,它没有规则的网格结构。每个点都是独立存在的,点与点之间没有固定的邻居关系。这一点非常重要,后面所有的滤波算法都基于这个特性。
举个例子,一个典型的 Velodyne HDL-64E 激光雷达,每秒能产生约 130 万个点。每个点包含:
| 字段 | 说明 | 典型范围 |
|---|---|---|
| x, y, z | 三维坐标(米) | -100 ~ 100 |
| intensity | 反射强度(0-255) | 0 ~ 255 |
| ring | 激光线束编号 | 0 ~ 63 |
| timestamp | 相对时间戳(微秒) | 0 ~ 1e6 |
嗯,这里要注意:不同雷达的数据格式差异很大。我曾经在项目里遇到过,把 Ouster 的数据当成 Velodyne 来解析,结果点云全飞到天上去了。所以拿到数据第一步,先确认传感器型号和数据结构。
体素滤波:给点云「降采样」
体素滤波(Voxel Grid Filter)是我用得最多的预处理手段。它的思路很简单:把三维空间划分成一个个小立方体(体素),每个体素内只保留一个点(通常是重心点)。
为什么要这么做?说白了,就是降采样。原始点云太密了,尤其是近处的点,密密麻麻全是。直接处理的话,计算量爆炸。体素滤波能有效减少点数,同时保留点云的几何结构。
// C++ 示例:PCL 中的体素滤波
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(cloud);
voxel_filter.setLeafSize(0.1f, 0.1f, 0.1f); // 体素大小 10cm
pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud(new pcl::PointCloud<pcl::PointXYZ>());
voxel_filter.filter(*filtered_cloud);
我的经验:体素大小的选择很关键。设得太小(比如 0.01m),降采样效果不明显;设得太大(比如 0.5m),会丢失细节。我一般从 0.1m 开始试,根据实际场景调整。高速场景可以大一点,低速场景小一点。
你可能会问:体素滤波会不会把障碍物弄没了?会的。如果体素太大,小物体(比如行人、锥桶)可能直接被「吞掉」。所以降采样和保真度之间,需要做个权衡。
直通滤波:快速裁剪感兴趣区域
直通滤波(PassThrough Filter)更简单——直接根据坐标轴范围裁剪点云。比如我只想看前方 50 米内的障碍物,那就把 x 轴范围设在 0 到 50 米。
这个操作在工程中非常实用。你想啊,激光雷达能扫 100 多米,但远处的点稀疏且噪声大,对检测没什么帮助。直接切掉,既减少数据量,又避免远处噪声干扰。
// C++ 示例:PCL 中的直通滤波
pcl::PassThrough<pcl::PointXYZ> pass;
pass.setInputCloud(cloud);
pass.setFilterFieldName("z"); // 沿 z 轴(高度)滤波
pass.setFilterLimits(-1.0, 2.0); // 保留 -1m 到 2m 之间的点
pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud(new pcl::PointCloud<pcl::PointXYZ>());
pass.filter(*filtered_cloud);
避坑指南:我曾经在高速场景中,把直通滤波的 x 轴范围设得太小(只保留 0-30 米),结果前方 50 米处的大货车完全没检测到,差点出事。记住:直通滤波是「裁剪」,不是「优化」。裁剪范围要根据实际感知需求来定,不能一刀切。
统计滤波:干掉离群点
统计滤波(Statistical Outlier Removal)是用来去除噪声点的。它的原理是:计算每个点与其 k 个最近邻的平均距离,如果这个距离超过某个阈值,就认为是离群点。
为什么会需要这个?因为激光雷达在边缘处、或者遇到雨雾天气时,会产生一些「飘」在空中的孤立点。这些点对障碍物检测是纯干扰。
// C++ 示例:PCL 中的统计滤波
pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
sor.setInputCloud(cloud);
sor.setMeanK(50); // 考虑 50 个最近邻
sor.setStddevMulThresh(1.0); // 标准差倍数阈值
pcl::PointCloud<pcl::PointXYZ>::Ptr filtered_cloud(new pcl::PointCloud<pcl::PointXYZ>());
sor.filter(*filtered_cloud);
关键参数:
- meanK:邻居数量。越大,滤波越平滑,但可能误删真实点。
- stddevMulThresh:标准差倍数。越小,去除的点越多。我一般用 1.0 到 2.0 之间。
我记得有一次做矿区无人驾驶项目,点云里全是扬尘产生的噪声点。统计滤波设了 meanK=30、stddevMulThresh=1.5,效果出奇的好。但要注意,如果场景本身点云就很稀疏(比如远距离),统计滤波可能会把真实物体也干掉。
点云可视化:眼见为实
预处理做得对不对,光看数字没用。你得把点云画出来看看。我常用的可视化工具是 PCL Visualizer 和 Open3D。
PCL 自带的可视化器虽然丑了点,但胜在轻量。Open3D 的界面更现代,交互也更友好。
// C++ 示例:PCL 可视化
pcl::visualization::PCLVisualizer viewer("Point Cloud Viewer");
viewer.addPointCloud(filtered_cloud, "cloud");
viewer.setBackgroundColor(0, 0, 0); // 黑色背景
viewer.spin();
我的习惯:可视化时,我会把原始点云和滤波后的点云放在两个窗口对比。这样一眼就能看出滤波效果。另外,给点云上色也很重要——按高度上色(z 轴)能直观看出地面和障碍物的分布。
你想想看,如果预处理做得好,可视化出来的点云应该是:地面平整、障碍物轮廓清晰、没有飘在空中的孤立点。如果看到点云里有一团一团的「毛刺」,那大概率是滤波参数没调好。
本章知识体系
下面这张图,是我自己总结的点云预处理流程。你可以把它当作一个检查清单:
这张图展示了点云预处理的典型流水线。实际项目中,顺序可以调整。比如先体素滤波再直通滤波,或者先统计滤波再体素滤波,效果差别不大。但有一点要注意:直通滤波最好放在最前面,因为先裁剪掉无关区域,后续滤波的计算量会小很多。
好了,关于点云基础与预处理,我就讲这么多。记住一句话:预处理做得好,后续算法事半功倍;预处理做得糙,后面全是坑。希望你们在实际项目中,能把这些方法用起来,调出最适合自己场景的参数。
可可精品资料库