4、点云分割(上):RANSAC算法原理、平面分割、圆柱体分割

各位同学,欢迎来到点云分割的实战环节。

点云分割,说白了就是把一堆乱七八糟的点,按照它们的几何形状归类。比如地面归为一类,墙面归为一类,路上的车归为一类。今天咱们先聊最经典的两个:平面分割圆柱体分割。而实现它们的核心算法,就是大名鼎鼎的 RANSAC

4.1 RANSAC 算法原理:为什么它能「抗噪声」?

RANSAC,全称 Random Sample Consensus,随机采样一致性。名字挺长,但思想很简单。

想象一下,你面前有一堆点,大部分都落在一个平面上,但有几个点飞得很远(噪声)。如果用最小二乘法去拟合,那几个噪声点会把整个平面拉歪。RANSAC 的做法是:我不跟你讲道理,我直接随机抽三个点,算一个平面,然后看看有多少点支持这个平面。支持的点多,我就认为这个平面是对的。

核心思想: 通过反复随机采样,找到能容纳最多「内点」的模型参数。

具体步骤我总结为四步:

  1. 随机采样:从点云中随机选 n 个点(平面选3个,圆柱体选5个)。
  2. 计算模型:用这 n 个点算出模型参数(平面方程、圆柱体轴线和半径)。
  3. 统计内点:遍历所有点,计算它们到模型的距离。距离小于阈值的,算内点。
  4. 迭代更新:重复1-3步,保留内点最多的那个模型。

嗯,这里要注意:迭代次数不是越多越好。我刚开始做项目时,傻乎乎设了10万次迭代,结果跑了一分多钟。后来发现,根据内点比例和置信度,用公式算一下最优次数就够了。

我的经验: 迭代次数公式:k = log(1-p) / log(1-w^n)。其中 p 是期望成功率(一般0.99),w 是内点比例,n 是采样点数。实际项目中,我通常设 100-500 次,效果就很好了。

4.2 平面分割:地面检测的「基本功」

平面分割是点云处理里最基础的操作。你想想看,自动驾驶场景中,地面、墙面、路面标识牌,基本都是平面。尤其是地面分割,几乎是每个感知系统的第一步。

RANSAC 做平面分割,流程是这样的:

  • 随机选3个点,计算平面方程 ax + by + cz + d = 0
  • 计算每个点到该平面的距离 dist = |ax0 + by0 + cz0 + d| / sqrt(a² + b² + c²)
  • 距离小于阈值(比如0.1米)的点,标记为内点。
  • 重复迭代,保留内点最多的平面。

我给大家看一段伪代码,实际项目中我经常这么写:

// 伪代码:RANSAC 平面分割
int best_inliers = 0;
Plane best_plane;

for (int iter = 0; iter < max_iterations; iter++) {
    // 1. 随机选3个点
    Point p1 = points[rand() % N];
    Point p2 = points[rand() % N];
    Point p3 = points[rand() % N];

    // 2. 计算平面参数
    Plane plane = computePlane(p1, p2, p3);

    // 3. 统计内点
    int inlier_count = 0;
    for (auto &p : points) {
        float dist = pointToPlaneDist(p, plane);
        if (dist < distance_threshold) inlier_count++;
    }

    // 4. 更新最优
    if (inlier_count > best_inliers) {
        best_inliers = inlier_count;
        best_plane = plane;
    }
}

避坑指南: 我曾经在高速场景下,把路边的护栏误检成了地面。为什么?因为护栏也是一个大平面,而且距离阈值设得太大了。后来我加了两个约束:法向量必须朝上(z轴分量大于0.8),平面高度必须在合理范围(比如-0.5米到0.5米之间)。

4.3 圆柱体分割:从「杆状物」说起

圆柱体分割,在自动驾驶里主要用来检测路灯杆、交通标志杆、树干等。这些目标虽然看起来简单,但用 RANSAC 做起来比平面复杂不少。

为什么?因为圆柱体有5个自由度:轴线的方向(2个)、轴线上的一个点(3个)、半径(1个)。所以每次随机采样需要至少5个点。

具体做法是这样的:

  1. 随机选5个点。
  2. 用其中2个点确定轴线方向,再用另外3个点确定半径和位置。
  3. 计算每个点到圆柱体表面的距离(点到轴线的距离减去半径)。
  4. 统计内点,迭代优化。

我个人习惯在圆柱体分割时,先做一次地面滤波。因为地面上的点太多,会干扰圆柱体的拟合。我一般先跑一遍平面分割,把地面去掉,再对剩下的点做圆柱体分割。这样效率能提升30%以上。

关键参数:

参数 推荐值 说明
距离阈值 0.05 - 0.1 米 点到圆柱体表面的允许误差
半径范围 0.05 - 0.5 米 路灯杆一般0.1-0.3米,树干可能更粗
最小内点数 50 - 200 太少了可能是噪声,太多了可能漏检

我记得有一次做园区物流车项目,路两边全是细长的路灯杆。用 RANSAC 做圆柱体分割,一开始总是把两个相邻的杆子拟合成一个。后来我把最小内点数调高,并且加了轴线方向约束(必须垂直于地面),问题就解决了。

4.4 本章知识体系

为了让大家更直观地理解,我画了一张流程图,把 RANSAC 分割的核心逻辑串起来:

RANSAC 点云分割核心流程 输入:原始点云 随机采样 n 个点 计算模型参数 统计内点数量 输出最优模型 平面分割:n=3 随机选3个点算平面 圆柱体分割:n=5 随机选5个点算圆柱 迭代次数: 根据内点比例动态调整 距离阈值: 平面:0.1-0.2m 圆柱:0.05-0.1m

这张图把 RANSAC 的迭代循环画得很清楚。你想想看,每次迭代就是一次「猜模型、验证、保留最优」的过程。说白了,RANSAC 就是一个暴力枚举的优化版本,只不过它用随机采样代替了全量搜索。

我的建议: 在实际工程中,不要只跑一次 RANSAC。我通常的做法是:先分割出地面(平面),把地面点去掉;再分割墙面(平面),去掉;最后剩下的点,做圆柱体分割。这样层层剥离,效果比一次全量分割好得多。

好了,这一章的内容就到这里。RANSAC 的原理和两种分割方法,大家一定要动手写代码跑一跑。下一章我们会聊点云分割的下半部分——欧式聚类和区域生长,到时候咱们再接着聊。


公众号:蓝海资料掘金营,微信deep3321