4. 点云分割与聚类

点云分割,说白了就是把一堆乱七八糟的点,按物体或结构分开。你想想看,机械臂要抓一个零件,总得先知道哪个点属于这个零件吧?这就是分割要做的事。

我刚开始做无序分拣时,觉得分割很简单——不就是把点分开吗?结果第一次上产线就翻车了。点云噪声、物体堆叠、反光干扰……各种问题全冒出来。后来我才明白,选对分割方法,比调参重要十倍。

这一章,我带你过四种最常用的点云分割方法。每种我都踩过坑,咱们直接说干货。

4.1 欧式聚类:最基础的距离分割

欧式聚类,原理简单到离谱:点与点之间距离近的,归为一类。它基于欧几里得距离,配合 KD-Tree 加速搜索。

核心参数就两个:

  • 聚类容忍度(Cluster Tolerance):两点之间的最大距离阈值。设小了,一个物体被切成好几块;设大了,不同物体粘在一起。
  • 最小/最大点数:过滤掉太小(噪声)或太大(背景墙)的聚类。

我在项目里遇到过一个问题:工件表面有凹槽,凹槽内的点距离稍远,结果一个工件被分成了两半。后来我加了体素滤波预处理,把点云均匀化,问题就解决了。

适用场景:物体间距明显、背景干净的场景。比如传送带上的单个零件、料箱里稀疏摆放的工件。

// PCL 欧式聚类示例
pcl::EuclideanClusterExtraction<pcl::PointXYZ> ec;
ec.setClusterTolerance(0.02);  // 2cm
ec.setMinClusterSize(100);
ec.setMaxClusterSize(25000);
ec.setSearchMethod(tree);
ec.setInputCloud(cloud_filtered);
ec.extract(cluster_indices);

我的调参习惯:先设一个偏小的容忍度(比如 1cm),看聚类结果。如果物体被切碎,逐步增大,每次增加 0.5cm。别一次加太多,容易把不同物体粘一起。

4.2 区域生长:基于法线一致性的分割

欧式聚类只看距离,但有些场景距离近的物体其实不是同一个——比如堆叠的工件。这时候就要用区域生长了。

区域生长的思路是:从种子点出发,检查邻居点的法线方向和曲率是否接近。如果接近,就归入同一区域。

为什么会这样?因为同一个光滑表面上的点,法线变化是连续的。而不同物体之间的法线会突变。

我记得有一次做汽车钣金件分拣,工件表面有油污反光,点云噪声很大。欧式聚类完全不行,但区域生长靠着法线一致性,硬是把工件从背景里抠出来了。

参数 作用 我的经验值
法线夹角阈值 判断两点是否在同一平面 15°~30°
曲率阈值 判断是否为平滑区域 0.05~0.1
最小聚类点数 过滤小区域 50~200

我曾经踩过的坑:法线估计的半径设得太小,导致法线方向不稳定,区域生长结果像狗啃的一样。后来我把法线估计半径设为点云分辨率的 10~15 倍,效果才稳定下来。

4.3 基于超体素的聚类:过分割再合并

超体素(Supervoxel)的思路很有意思:先把点云过分割成很多小片(超体素),然后根据这些小片的特征(颜色、法线、空间位置)合并成最终物体。

你可以把它理解成:先切碎,再拼回去。这样做的好处是——对噪声和局部缺失不敏感。

超体素聚类的流程:

  1. 体素化:把点云分成小立方体网格
  2. 流形距离计算:计算相邻体素之间的几何距离和颜色距离
  3. 局部聚类:基于流形距离生成超体素
  4. 超体素合并:用图割或层次聚类合并成最终物体

我个人的习惯是,在物体表面纹理复杂、或者有轻微堆叠时,优先用超体素。它比欧式聚类鲁棒,比区域生长快。

// PCL 超体素聚类示例
pcl::SupervoxelClustering<pcl::PointXYZRGBA> super(voxel_resolution, seed_resolution);
super.setInputCloud(cloud);
super.setColorImportance(0.2f);
super.setSpatialImportance(0.4f);
super.setNormalImportance(1.0f);
super.extract(supervoxel_clusters);

参数调整技巧:voxel_resolution 设小一点(比如 0.5cm),seed_resolution 设大一点(比如 3cm)。这样超体素数量适中,合并时不容易出错。

4.4 平面与圆柱分割:用模型拟合找结构

有时候我们不需要分割出完整物体,只需要找到特定的几何结构——比如桌面、墙面、圆柱形工件。这时候就用模型拟合分割。

最经典的方法是 RANSAC(随机采样一致性)。它的逻辑很简单:随机选几个点拟合一个模型,然后看有多少点支持这个模型。支持的点多,就认为找到了一个有效结构。

4.4.1 平面分割

平面分割在分拣里太常用了。比如:

  • 从点云里去掉桌面,只保留工件
  • 找到料箱的底面,确定抓取高度
  • 分割出工件上的平面特征,辅助位姿估计
// PCL 平面分割
pcl::SACSegmentation<pcl::PointXYZ> seg;
seg.setModelType(pcl::SACMODEL_PLANE);
seg.setMethodType(pcl::SAC_RANSAC);
seg.setDistanceThreshold(0.01);  // 1cm
seg.setInputCloud(cloud);
seg.segment(inliers, coefficients);

避坑指南:RANSAC 的距离阈值很关键。设太大,会把弯曲表面也当成平面;设太小,平面上的点被漏掉。我一般先看点云分辨率,阈值设为分辨率的 2~3 倍。

4.4.2 圆柱分割

圆柱分割在分拣圆形工件(如轴承、瓶罐、管件)时特别有用。RANSAC 拟合圆柱需要估计:轴线方向、半径、中心点。

参数比平面多,但效果很稳。我记得有一次做饮料瓶分拣,瓶子是透明的,深度相机几乎拍不到表面。但瓶口和瓶底是圆柱形,用圆柱分割反而能稳定检出。

模型 最少点数 关键参数 典型应用
平面 3 距离阈值 桌面去除、料箱底面
圆柱 6 距离阈值 + 半径范围 圆形工件、管件
球体 4 距离阈值 + 半径范围 轴承、球状物体

4.5 四种方法怎么选?一张图说清楚

下面这张图是我自己总结的选型逻辑,每次做项目前我都会过一遍。

点云分割方法选型流程图 输入点云 物体间距明显?背景干净? 欧式聚类 物体堆叠?表面复杂? 简单场景,快速分割 超体素聚类 有平面/圆柱结构? 纹理复杂、轻微堆叠 区域生长 平面/圆柱分割 光滑表面、法线一致 已知几何结构 实际项目中常组合使用多种方法

4.6 实战中的组合策略

说实话,实际项目里很少只用一种分割方法。我通常的做法是:

  1. 先用平面分割去掉桌面或料箱底面
  2. 再用欧式聚类或超体素分割出单个物体
  3. 最后用区域生长或圆柱分割提取物体上的特征结构

举个例子,做轴承分拣时:平面分割去掉工作台 → 欧式聚类分出每个轴承 → 圆柱分割提取轴承外圈 → 用外圈点云做位姿估计。一套流程下来,抓取成功率从 60% 提到了 95%。

注意:分割不是越精细越好。过度分割会导致后续位姿估计不稳定。我的原则是——只要能稳定抓取,分割粗一点没关系。