3. 传感器数据处理:激光雷达点云滤波、聚类与特征提取
大家好,我是你们的老朋友。今天咱们聊聊激光雷达的数据处理。说实话,很多新手一上来就急着跑算法,结果点云数据乱七八糟,后面决策全崩了。我当年也踩过这个坑——有一次在园区测试,车突然对着一个垃圾桶急刹,后来发现是点云没滤干净,把垃圾桶当成了行人。嗯,从那以后,我养成了一个习惯:先花 70% 的精力把数据洗干净,再谈后面的决策。
这一章,我们就聚焦在三个核心环节:滤波、聚类、特征提取。说白了,就是把原始点云变成机器人能理解的障碍物信息。
3.1 点云滤波:去噪与下采样
激光雷达扫出来的原始点云,其实挺脏的。有飘在空中的噪点,有地面反射的杂波,还有远处的稀疏点。你想想看,如果把这些都喂给聚类算法,结果会怎样?
我个人习惯,先做两步滤波:
- 直通滤波(PassThrough):直接砍掉距离太远或太近的点。比如我只保留 0.5m 到 50m 范围内的点,超出这个范围的,要么是车身上的点,要么是噪声。
- 体素滤波(VoxelGrid):把空间切成小立方体,每个立方体只保留一个代表点。这样点云数量能减少 80% 以上,但形状特征基本保留。
重要经验:体素滤波的格子大小很关键。设太大,障碍物细节丢失;设太小,计算量降不下来。我在项目中一般设 0.1m 到 0.2m,具体看激光雷达的线束密度。
下面是我常用的滤波代码片段:
# 直通滤波
pass_through = cloud_filtered.passthrough(filter_axis='z',
min_range=-0.5,
max_range=2.0)
# 体素滤波
voxel_filter = cloud_filtered.voxel_grid_filter(leaf_size=0.15)
小技巧:如果你用的是 16 线雷达,体素大小建议 0.2m;如果是 64 线或 128 线,可以缩到 0.1m。线束越多,点越密,格子就可以设小一点。
3.2 点云聚类:把点变成块
滤波之后,我们得到了一堆干净的点。但机器人需要知道:哪些点属于同一个障碍物?这就靠聚类了。
最常用的方法是 欧几里得聚类(Euclidean Clustering)。它的逻辑很简单:如果两个点之间的距离小于某个阈值,就认为它们属于同一个物体。
我记得有一次在雨雾天气测试,点云变得特别稀疏,聚类阈值设小了,一个行人被分成了三块;设大了,又把旁边的树和行人粘在一起。后来我总结了一个经验:
- 聚类半径:一般设 0.3m 到 0.5m。室内场景可以小一点,室外大一点。
- 最小点数:少于 5 个点的簇直接丢弃,大概率是噪声。
- 最大点数:超过 5000 个点的簇,可能是墙面或大型车辆,需要单独处理。
避坑指南:我曾经在高速场景下,把路边的护栏和一辆静止的轿车聚在了一起。原因是护栏的点太密,聚类半径覆盖了车。后来我加了距离自适应——近处用大半径,远处用小半径,效果好了很多。
聚类后的效果,可以用下面这张图来理解:
3.3 特征提取:从块到属性
聚类之后,我们有了一个个障碍物点云块。但机器人还需要知道:这个块是什么?它有多大?它朝哪个方向运动?
特征提取就是干这个的。我一般提取以下几类特征:
| 特征类别 | 具体特征 | 计算方法 | 用途 |
|---|---|---|---|
| 几何特征 | 中心点、长宽高、体积 | 计算点云的最小包围盒 | 判断障碍物大小 |
| 形状特征 | 线性度、平面度、散度 | PCA 分解点云协方差矩阵 | 区分行人(散状)vs 车辆(平面状) |
| 运动特征 | 速度、加速度、运动方向 | 多帧跟踪 + 卡尔曼滤波 | 预测障碍物未来位置 |
| 统计特征 | 点云数量、密度、强度均值 | 直接统计 | 辅助分类(如金属反射强度高) |
这里重点说一下形状特征。为什么它重要?你想想看,一个行人和一根电线杆,在点云里可能大小差不多,但形状完全不同。行人点云比较散乱,而电线杆的点云呈细长条状。
我常用的方法是 PCA(主成分分析)。简单来说,就是计算点云的三个主方向,然后看它们的比值:
- 线性度(Linearity):如果点云主要沿一个方向分布,说明是细长物体(如电线杆、树干)。
- 平面度(Planarity):如果点云主要分布在两个方向上,说明是平面物体(如墙面、车辆侧面)。
- 散度(Scatter):如果三个方向分布均匀,说明是散乱物体(如行人、灌木丛)。
实战经验:我在做行人检测时,发现单纯靠几何大小(比如宽度 0.5m)会误检很多。后来加了散度特征,行人的散度一般在 0.3 以上,而车辆在 0.1 以下。这个阈值帮我过滤掉了 90% 的误检。
特征提取的代码示例:
# 计算点云协方差矩阵
cov_matrix = np.cov(points.T)
# PCA 分解
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)
# 归一化特征值
eigenvalues = np.sort(eigenvalues)[::-1]
linearity = (eigenvalues[0] - eigenvalues[1]) / eigenvalues[0]
planarity = (eigenvalues[1] - eigenvalues[2]) / eigenvalues[0]
scatter = eigenvalues[2] / eigenvalues[0]
小提示:如果你用的是 ROS,PCL 库里有现成的 MomentOfInertiaEstimation 类,可以直接算包围盒和特征值。但建议自己手写一遍 PCA,理解原理后调参才不慌。
3.4 本章小结
好了,这一章的内容就这些。总结一下核心要点:
- 滤波:直通滤波砍范围,体素滤波降密度。别偷懒,这一步做不好,后面全是白费。
- 聚类:欧几里得聚类最常用,注意半径和点数阈值。雨雾天气记得调参。
- 特征提取:几何特征看大小,形状特征看 PCA,运动特征靠跟踪。三者结合才能准确判断障碍物类型。
说实话,传感器数据处理这部分,看起来基础,但真正做好的工程师不多。我见过太多人直接拿开源代码跑,结果在真实场景里各种翻车。嗯,希望你能沉下心来,把每一步都吃透。
下一章,我们会把这些处理好的障碍物信息,用到决策规划里去。到时候你就知道,今天花的时间,绝对值。