第四章:点云分割基础
点云分割,说白了就是把一堆乱七八糟的点,按规则分成几堆。我刚开始接触这玩意儿的时候,觉得不就是分类嘛,后来才发现——这里面的门道可不少。
今天咱们聊四种最常用的分割方法:RANSAC平面分割、欧几里得聚类、区域生长、还有模型分割。每种方法都有它的脾气,选对了事半功倍,选错了...嗯,你懂的。
4.1 RANSAC 平面分割
RANSAC,全称 Random Sample Consensus。名字挺唬人,其实核心思想就一句话:从乱糟糟的点里,找到那个最符合规律的面。
我当年第一次用RANSAC做地面分割,心想这玩意儿能靠谱吗?结果跑完一看,地面点被干干净净地拎出来了,连路沿石都分得清清楚楚。那一刻我服了。
4.1.1 基本原理
RANSAC的套路是这样的:
- 随机选三个点,确定一个平面
- 算算其他点到这个平面的距离
- 距离小于阈值的,算作"内点"
- 重复以上步骤N次,选内点最多的那个平面
说白了就是"瞎蒙+验证",蒙的次数多了,总能蒙对。
关键参数:
- 距离阈值:决定哪些点算"内点"。设太小,平面不完整;设太大,会把噪声包进来。我一般先设0.1米试试。
- 迭代次数:越多越准,但越慢。通常1000次就够用了。
- 最小内点数:低于这个数,就认为没找到平面。
4.1.2 代码实战
用PCL实现RANSAC平面分割,代码其实不长:
// PCL 平面分割示例
pcl::SACSegmentation<pcl::PointXYZ> seg;
pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_PLANE);
seg.setMethodType(pcl::SAC_RANSAC);
seg.setDistanceThreshold(0.1); // 距离阈值
seg.setMaxIterations(1000); // 迭代次数
seg.setInputCloud(cloud);
seg.segment(*inliers, *coefficients);
// 提取平面点
pcl::ExtractIndices<pcl::PointXYZ> extract;
extract.setInputCloud(cloud);
extract.setIndices(inliers);
extract.setNegative(false);
extract.filter(*plane_cloud);
我的小技巧: 如果场景里有多个平面,可以循环执行RANSAC——每次提取一个平面,把内点移除,再对剩余点继续跑。这样就能把地面、墙面、桌面一个个拎出来。
4.2 欧几里得聚类分割
欧几里得聚类,名字听着高大上,其实就是按距离分堆。两个点离得近,就归为一类;离得远,就各玩各的。
这方法特别适合做物体检测。比如自动驾驶里,把激光雷达扫到的点云,按距离聚类,就能分出哪堆是车、哪堆是行人、哪堆是树。
4.2.1 算法流程
步骤很简单:
- 建一棵KD-Tree,方便快速找近邻
- 随便选一个点,找它半径r内的所有邻居
- 对每个邻居,继续找它的邻居
- 直到找不到新邻居了,这一堆就算一个聚类
- 换下一个没被访问的点,重复以上步骤
这里有个坑——半径r怎么选? 设太小,一个物体会被切成好几块;设太大,两个物体又粘在一起。我一般先看点云密度,密度大的设小点,密度小的设大点。
避坑指南: 我曾经在一个项目里,把聚类半径设成0.5米,结果一辆自行车被分成了三块——车把、车身、后轮各成一堆。后来改成0.3米,才把自行车完整地拎出来。所以,半径一定要根据物体尺寸来调。
4.2.2 代码示例
// PCL 欧几里得聚类
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
tree->setInputCloud(cloud_filtered);
std::vector<pcl::PointIndices> cluster_indices;
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance(0.3); // 聚类半径
ec.setMinClusterSize(100); // 最小点数
ec.setMaxClusterSize(25000); // 最大点数
ec.setSearchMethod(tree);
ec.setInputCloud(cloud_filtered);
ec.extract(cluster_indices);
// 遍历每个聚类
for (const auto& indices : cluster_indices) {
pcl::PointCloud<pcl::PointXYZ>::Ptr cluster(new pcl::PointCloud<pcl::PointXYZ>);
for (const auto& idx : indices.indices) {
cluster->push_back((*cloud_filtered)[idx]);
}
// 处理每个聚类...
}
4.3 区域生长分割
区域生长,思路跟欧几里得聚类有点像,但多了一个条件——不光看距离,还要看法向量和曲率。
你想想看,一个平面上的点,法向量应该差不多。如果两个点虽然离得近,但法向量差很大,那它们大概率不在同一个面上。
4.3.1 生长准则
区域生长需要两个条件同时满足:
- 距离条件:点与种子点的距离小于阈值
- 法向量条件:点与种子点的法向量夹角小于阈值
我习惯先算曲率,曲率小的点优先当种子。因为曲率小的地方比较"平",生长起来不容易跑偏。
参数调优建议:
| 参数 | 作用 | 典型值 |
|---|---|---|
| 法向量夹角阈值 | 控制生长方向一致性 | 15° ~ 30° |
| 曲率阈值 | 控制曲面平滑度 | 0.05 ~ 0.1 |
| 邻域搜索半径 | 控制生长范围 | 0.1 ~ 0.5 m |
4.3.2 适用场景
区域生长特别适合做室内场景分割。比如墙面、天花板、地板,这些面都是平滑的,法向量变化小,用区域生长一抓一个准。
但如果是室外场景,树啊、灌木啊这些乱七八糟的东西,法向量乱飞,区域生长就容易翻车。这时候我宁愿用欧几里得聚类。
4.4 基于模型的圆柱/球体分割
最后聊一个"高级"玩法——用数学模型去拟合点云。
比如你有一堆点,看起来像个圆柱。那就可以用RANSAC的思路,只不过这次不是拟合平面,而是拟合圆柱。同理,球体、圆锥、圆环都能拟合。
4.4.1 圆柱拟合
圆柱有7个参数:轴方向(3个)、轴上一点(3个)、半径(1个)。所以最少需要7个点才能确定一个圆柱。
实际用RANSAC拟合时,流程跟平面分割一模一样,只是把模型类型从SACMODEL_PLANE换成SACMODEL_CYLINDER。
// PCL 圆柱拟合
pcl::SACSegmentationFromNormals<pcl::PointXYZ, pcl::Normal> seg;
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_CYLINDER);
seg.setMethodType(pcl::SAC_RANSAC);
seg.setNormalDistanceWeight(0.1); // 法向量权重
seg.setMaxIterations(10000);
seg.setDistanceThreshold(0.05);
seg.setRadiusLimits(0.1, 1.0); // 半径范围
seg.setInputCloud(cloud);
seg.setInputNormals(normals);
seg.segment(*inliers, *coefficients);
我的经验: 圆柱拟合对法向量质量要求很高。如果法向量算得不准,拟合出来的圆柱半径能差一倍。所以做圆柱分割前,一定要先做好法向量估计,最好用MLS(移动最小二乘)平滑一下。
4.4.2 球体拟合
球体更简单,4个参数:球心(3个) + 半径(1个)。最少4个点就能确定一个球。
球体分割在工业检测里用得挺多。比如检测轴承里的滚珠、管道里的球阀。我做过一个项目,用球体分割去检测管道接头,准确率能到98%。
// PCL 球体拟合
seg.setModelType(pcl::SACMODEL_SPHERE);
seg.setDistanceThreshold(0.02);
seg.setRadiusLimits(0.05, 0.5);
seg.setInputCloud(cloud);
seg.segment(*inliers, *coefficients);
// coefficients 里存的是 [cx, cy, cz, r]
4.5 方法对比与选型
说了这么多,到底该用哪个?我整理了一个对比表,方便你快速决策:
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| RANSAC平面 | 地面、墙面、桌面 | 速度快、抗噪强 | 只能提取平面 |
| 欧几里得聚类 | 物体检测、实例分割 | 简单、通用 | 对密度敏感 |
| 区域生长 | 平滑曲面分割 | 分割精细 | 参数多、调参麻烦 |
| 模型分割 | 工业零件检测 | 精度高、可解释性强 | 只适用于规则形状 |
选型其实没有标准答案。我个人的习惯是:先试最简单的。如果欧几里得聚类能搞定,就别上区域生长。如果RANSAC能提取出地面,就别搞模型拟合。简单的方法往往最可靠。
好了,这四种分割方法就聊到这儿。每种方法都有它的脾气,多试试、多调调参数,你就能摸透它们的性格。下次遇到点云分割的问题,心里就有底了。