4、点云分割:地面分割(RANSAC)、欧几里得聚类、区域生长分割
点云分割,说白了就是把一堆乱七八糟的点云,按照不同的物体或者区域给分开。你想想看,激光雷达扫回来的数据,有地面、有车辆、有行人、有树木,如果不做分割,后续的目标检测和跟踪根本没法搞。
我个人习惯把点云分割分成两大步:先去掉地面,再把剩下的点云按物体分开。今天我们就聊三种最常用的方法:RANSAC地面分割、欧几里得聚类、区域生长分割。
核心逻辑:地面分割 → 障碍物聚类 → 精细分割。这三步走完,点云数据才算真正“可读”。
4.1 RANSAC 地面分割
RANSAC,全称 Random Sample Consensus,随机采样一致性。这名字听着挺唬人,其实原理很简单:从一堆点里随机挑几个,拟合一个平面,然后看看有多少点落在这个平面上。重复这个过程,找到点数最多的那个平面,就是地面。
我在项目中遇到过一个问题:如果地面有坡度,RANSAC 会误把坡面也当成地面的一部分。后来我加了一个高度约束,只保留 z 值在一定范围内的点,效果好了很多。
我的经验:RANSAC 的迭代次数不要设太大,50-100 次就够。设太大反而容易过拟合,把路沿石也当成地面。
// Autoware 中 RANSAC 地面分割的核心参数
pcl::SACSegmentation<pcl::PointXYZ> seg;
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_PLANE);
seg.setMethodType(pcl::SAC_RANSAC);
seg.setMaxIterations(100); // 迭代次数
seg.setDistanceThreshold(0.15); // 距离阈值,单位米
seg.setInputCloud(cloud);
seg.segment(*inliers, *coefficients);
这里有个坑:距离阈值很关键。设小了,地面点会漏掉;设大了,会把低矮障碍物也当成地面。我一般设 0.1-0.2 米,具体看激光雷达的安装高度和路面平整度。
避坑指南:我曾经在雨天测试时发现 RANSAC 分割效果很差,因为雨滴反射的点云会干扰平面拟合。后来我在预处理阶段加了离群点滤除,才解决了这个问题。
4.2 欧几里得聚类
地面去掉之后,剩下的点云就是障碍物了。但障碍物之间怎么区分?欧几里得聚类就是干这个的。
它的原理特别直观:两个点如果距离很近,就认为它们属于同一个物体。具体做法是:选一个种子点,找它周围半径 r 内的所有点,把这些点归为一类,然后继续找这些点的邻居,直到找不到新的点为止。
说白了,这就是一个基于距离的连通域算法。你想想看,一辆车上的点云,点与点之间的距离肯定很小;而车和行人之间,距离就大多了。
| 参数 | 说明 | 推荐值 |
|---|---|---|
| clusterTolerance | 聚类半径,单位米 | 0.5 - 1.0 |
| minClusterSize | 最小聚类点数 | 10 - 30 |
| maxClusterSize | 最大聚类点数 | 10000 - 50000 |
// Autoware 中欧几里得聚类的典型配置
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance(0.8); // 聚类半径
ec.setMinClusterSize(20); // 最小点数
ec.setMaxClusterSize(30000); // 最大点数
ec.setSearchMethod(tree);
ec.setInputCloud(cloud_filtered);
ec.extract(cluster_indices);
我建议你把 clusterTolerance 设成 0.6-0.8 米。为什么?因为一辆普通轿车的宽度大约是 1.8 米,长度 4-5 米,0.8 米的半径足够把车身点云聚在一起,又不会把相邻的车连起来。
注意:欧几里得聚类对稀疏点云效果不好。如果激光雷达线数少(比如 16 线),远处的物体点云很稀疏,聚类可能会把一个物体分成好几块。这时候可以适当增大聚类半径。
4.3 区域生长分割
区域生长分割,比欧几里得聚类更“聪明”一些。它不光看距离,还看点的法向量和曲率。
原理是这样的:先选一些种子点,然后检查种子点周围的点,如果它们的法向量方向差不多、曲率也接近,就归为一类。这样可以把墙面、地面、树干等不同几何形状的区域分开。
我记得有一次做园区自动驾驶,路中间有个锥桶,欧几里得聚类把它和旁边的灌木丛连在了一起。换成区域生长分割后,因为锥桶表面是光滑的,灌木丛是杂乱的,法向量差异很大,一下子就分开了。
// 区域生长分割的关键参数
pcl::RegionGrowing<pcl::PointXYZ, pcl::Normal> rg;
rg.setMinClusterSize(50); // 最小聚类点数
rg.setMaxClusterSize(100000); // 最大聚类点数
rg.setNumberOfNeighbours(30); // 邻居点数
rg.setSmoothnessThreshold(3.0); // 平滑度阈值(度)
rg.setCurvatureThreshold(1.0); // 曲率阈值
rg.setInputCloud(cloud);
rg.setInputNormals(normals);
rg.extract(clusters);
我的经验:setSmoothnessThreshold 设 3-5 度比较合适。设太大,会把不同平面合并;设太小,一个平面会被切成好几块。曲率阈值一般设 0.5-1.0,用来区分平面和曲面。
区域生长分割的缺点是计算量大。因为要先计算每个点的法向量,再做区域生长,比欧几里得聚类慢不少。我一般只在需要精细分割的场景用,比如识别路沿石、交通岛这些。
4.4 三种方法怎么选?
我整理了一个对比表,方便你根据场景选择:
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| RANSAC 地面分割 | 平坦路面、结构化道路 | 速度快、实现简单 | 对坡度敏感、参数调优麻烦 |
| 欧几里得聚类 | 障碍物粗分割、实时性要求高 | 计算快、参数少 | 对稀疏点云效果差、无法区分粘连物体 |
| 区域生长分割 | 精细分割、几何特征明显的场景 | 分割精度高、能区分不同几何面 | 计算量大、需要法向量预处理 |
在实际的 Autoware 项目中,我通常的做法是:先用 RANSAC 去掉地面,再用欧几里得聚类做粗分割,最后对感兴趣的区域(比如车辆、行人)用区域生长做精细分割。这样既保证了实时性,又兼顾了精度。
避坑指南:我曾经在隧道场景里吃过亏。隧道里地面和墙壁连在一起,RANSAC 把墙壁的一部分也当成地面去掉了。后来我加了高度阈值和角度阈值,只保留与水平面夹角小于 10 度的平面,才解决了这个问题。
好了,点云分割这块就聊到这儿。这三种方法各有各的脾气,关键是要根据你的场景和数据特点来选。多试几次参数,找到最适合你那一套数据组合,比死记硬背参数值有用得多。