上篇把点云滤波的四种方法讲透了——体素降采样、统计滤波、直通滤波、半径滤波,以及参数怎么选、实时性能怎么优化。滤波之后拿到干净的点云,下一步就是把点云里的不同物体分开,这就是点云分割。

面试时候被问"你怎么从点云里识别出障碍物",很多候选人直接跳到"用深度学习做语义分割"。但实际项目中,传统的几何分割方法用得更多,因为它们速度快、可解释性强、不需要大量标注数据。

今天讲三种最经典的分割方法:RANSAC地面分离、欧式聚类、区域生长。搞懂这三种,大部分机器人项目的分割需求都能覆盖。

地面分离:RANSAC平面拟合

移动机器人的点云里,地面通常占了40%-60%的点。把地面去掉,剩下的就是障碍物和感兴趣的目标。

地面在大多数场景下可以近似为一个平面。RANSAC(Random Sample Consensus)是拟合平面最鲁棒的方法。

RANSAC的思路很直觉:随机选3个点确定一个平面,看有多少点落在这个平面附近(内点)。重复N次,选内点最多的那个平面。

import open3d as o3d

pcd = o3d.io.read_point_cloud("scan.pcd")

# RANSAC平面拟合
plane_model, inliers = pcd.segment_plane(
    distance_threshold=0.1,  # 点到平面距离小于0.1m算内点
    ransac_n=3,              # 每次随机选3个点
    num_iterations=100       # 迭代100次
)

# 提取地面和障碍物
ground = pcd.select_by_index(inliers)
obstacles = pcd.select_by_index(inliers, invert=True)

print(f"地面点数: {len(ground.points)}")
print(f"障碍物点数: {len(obstacles.points)}")
print(f"平面方程: {plane_model}")

plane_model返回的是平面方程的四个系数[a, b, c, d],对应ax + by + cz + d = 0。正常情况下c(z的系数)应该接近1,因为地面大致是水平的。如果c很小,说明拟合出来的平面几乎是竖直的,大概率是拟合失败了。

这里值得展开讲讲RANSAC的原理。RANSAC的核心是"随机采样+一致性验证"。每次随机选3个点算一个平面,然后统计有多少点落在这个平面附近(内点)。重复N次,取内点最多的那次作为最终结果。

为什么需要多次迭代?因为你不知道哪3个点能确定一个"好"平面。如果随机选到的3个点恰好都在一个噪声簇上,拟合出来的平面就是错的。迭代次数越多,至少有一次选到3个好点的概率就越大。

迭代次数怎么定?有个理论公式:N = log(1-p) / log(1-w³),其中p是期望成功概率(通常0.99),w是内点比例。实际中一般设100次——计算量小(100次迭代总共几毫秒),且内点比例可能比估计的低。

面试时候如果被问"RANSAC和最小二乘拟合平面有什么区别",回答要点:最小二乘用所有点来拟合,对离群点非常敏感(一个远处噪声点就能把平面拉歪)。RANSAC用随机采样+内点投票,对离群点天然鲁棒。所以点云分割几乎都用RANSAC而不是最小二乘。

distance_threshold的选择很关键。室内地面比较平整,0.05-0.1米就够了。室外地面有坡度、有起伏,要放宽到0.15-0.3米。但也不能太宽松,否则会把低矮障碍物的点也算进地面里。

RANSAC有个前提假设:地面是一个完整的平面。如果你的场景里有楼梯、斜坡、台阶,地面不是单一平面,RANSAC就只能拟合出其中一个面。这种情况下需要多平面分割或者用其他方法,后面会提。

PCL里的写法:

#include <pcl/segmentation/sac_segmentation.h>

pcl::SACSegmentation<pcl::PointXYZ> seg;
seg.setOptimizeCoefficients(true);
seg.setModelType(pcl::SACMODEL_PLANE);
seg.setMethodType(pcl::SAC_RANSAC);
seg.setDistanceThreshold(0.1);
seg.setInputCloud(cloud);
seg.segment(*inliers, *coefficients);

欧式聚类:最常用的点云聚类方法

去掉地面之后,剩下的点云需要按物体分开。欧式聚类(Euclidean Cluster Extraction)是最直接的方法:把距离足够近的点归为同一簇。

# Open3D欧式聚类
clustering = pcd.cluster_dbscan(eps=0.15, min_points=20)
labels = np.array(clustering)

# 按标签提取每个簇
for i in set(labels):
    if i == -1:  # 噪声点
        continue
    cluster = pcd.select_by_index(np.where(labels == i)[0])
    print(f"簇{i}: {len(cluster.points)}个点")

eps是两个点被认为属于同一簇的最大距离,min_points是一个簇至少包含的点数。室内场景eps一般设0.1-0.2米,室外可以放宽到0.3-0.5米。

欧式聚类的优点是简单高效,缺点也很明显:只看距离不看几何特征。两个距离很近但法线方向完全不同的面会被合并成一个簇。比如墙角的两面墙,距离很近但属于不同平面。

区域生长:结合法线信息的分割

区域生长解决了欧式聚类"只看距离"的问题。它的思路是:从种子点出发,把法线方向相近、空间相邻的点归为同一区域。

# PCL区域生长
pcl::RegionGrowingRGB<pcl::PointXYZRGB> reg;
reg.setMinClusterSize(50);
reg.setMaxClusterSize(100000);
reg.setSearchMethod(tree);
reg.setNumberOfNeighbours(30);
reg.setInputCloud(cloud);
// 设置法线阈值(弧度)
reg.setSmoothnessThreshold(3.0 / 180.0 * M_PI);
reg.setColorThreshold(10.0);
reg.extract(*clusters);

区域生长同时考虑了法线方向和颜色(或曲率)的相似性。和欧式聚类相比,它能区分"距离近但朝向不同"的表面,比如区分墙面和地面。但计算量更大,因为需要先估计每个点的法线。

在机器人项目中,我的经验是:先用RANSAC去地面,再用欧式聚类做粗分割,对需要精细分割的物体再用区域生长。

面试中怎么聊

面试官问点云分割,按这个顺序回答:先说RANSAC去地面(原理、参数、局限),再说欧式聚类(原理、参数、和区域生长的对比),最后结合项目说你的分割流程和效果。

如果面试官问"欧式聚类有什么缺点",你可以说:只看距离不看几何特征,近距离的不同物体会被合并。改进方案是区域生长(结合法线信息)或者用深度学习方法做语义分割。

如果面试官问"实时性怎么保证",你可以说:RANSAC迭代次数可以限制(100次通常足够),欧式聚类用KD树加速,整个分割流程在降采样后的点云上跑,5万点以内可以在20毫秒内完成。实际项目中建议先用离线数据调好参数,再上在线系统验证,避免在实时系统上反复试参数浪费时间。

下一篇讲相机成像原理。从点云到图像,传感器的世界不只是激光雷达,视觉传感器同样重要。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。

「机器人软件开发面试·从入门到精通」连载系列 上一篇:第149篇 激光雷达点云滤波——体素降采样、统计滤波和直通滤波 下一篇预告:第151篇 相机成像原理——小孔模型、镜头畸变和成像几何

有任何问题欢迎评论区留言,我会尽量回复。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐