地面分割与道路提取:RANSAC平面拟合、基于射线的地面分割、基于网格的地面分割、道路边界提取

地面分割这件事,说白了就是让自动驾驶的车知道「哪里能走,哪里不能走」。我刚开始做这个模块时,总觉得地面不就是个平面嘛,随便拟合一下就行。结果第一次上路测试,车在坡道上差点冲进绿化带——嗯,从那以后我再也不敢小看地面分割了。

今天咱们聊聊四种主流方法:RANSAC平面拟合、基于射线的地面分割、基于网格的地面分割,以及道路边界提取。每种方法我都踩过坑,咱们一个一个说。

RANSAC平面拟合:最经典,但别迷信

RANSAC(Random Sample Consensus)是地面分割的「老大哥」。它的核心思想很简单:从点云中随机选三个点,拟合一个平面,然后看有多少点落在这个平面上。重复这个过程,找到支持点最多的那个平面。

代码实现其实不复杂:

def ransac_plane_fitting(points, max_iter=100, distance_threshold=0.2):
    best_plane = None
    best_inliers = []
    
    for _ in range(max_iter):
        # 随机选三个点
        idx = np.random.choice(len(points), 3, replace=False)
        p1, p2, p3 = points[idx]
        
        # 计算平面参数
        normal = np.cross(p2 - p1, p3 - p1)
        d = -np.dot(normal, p1)
        
        # 计算所有点到平面的距离
        distances = np.abs(np.dot(points, normal) + d) / np.linalg.norm(normal)
        inliers = points[distances < distance_threshold]
        
        if len(inliers) > len(best_inliers):
            best_inliers = inliers
            best_plane = (normal, d)
    
    return best_plane, best_inliers

我个人习惯把距离阈值设成0.15到0.25米之间。太小了会把起伏路面误判为障碍物,太大了又会把低矮障碍物当成地面。

注意:RANSAC假设地面是平面。但真实道路有坡度、有拱形、有减速带。我在高速项目上吃过亏——车辆上坡时,RANSAC把坡面当成地面,结果把坡顶的静止车辆当成了地面点,差点追尾。

所以我的建议是:RANSAC适合结构化道路、停车场等平坦场景。遇到复杂地形,得换方法。

基于射线的地面分割:更适合非结构化道路

这个方法是我在越野场景项目中学会的。它的思路很直观:从激光雷达原点出发,沿每条射线方向看过去,地面点的高度应该是连续变化的。

具体做法是:

  1. 把点云按方位角分成若干条射线(比如每0.5度一条)
  2. 对每条射线上的点,按距离排序
  3. 计算相邻点的高度差和坡度角
  4. 如果坡度角小于阈值(比如5度),就认为是地面点

这里有个关键参数——坡度角阈值。我曾经在矿山项目上调试这个参数,发现不同地形需要的阈值差别很大:

场景类型 推荐坡度阈值 典型问题
高速公路 3° - 5° 阈值太小会漏掉缓坡
城市道路 5° - 8° 路沿容易被误判为地面
越野/矿山 10° - 15° 大石块可能被当成地面
小技巧:我后来改用自适应阈值——根据车辆当前俯仰角和侧倾角动态调整。比如车辆上坡时,把坡度阈值放宽一些。效果比固定阈值好很多。

基于网格的地面分割:工程中最实用

说实话,我现在大部分项目都用网格法。为什么?因为它快、稳定、好调参。

核心思路是把点云投影到二维网格上,每个网格单元记录最低点、最高点、平均高度等信息。然后判断每个网格是地面还是障碍物。

我常用的网格尺寸是0.2米×0.2米。太大了会丢失细节,太小了计算量爆炸。

判断逻辑是这样的:

  • 如果网格内点的高度方差很小 → 可能是地面
  • 如果网格内点的高度差很大 → 可能是障碍物边缘
  • 如果网格内点很少 → 可能是噪声或远处点

我曾经在雨夜测试中遇到一个问题:地面有水洼,激光雷达打到水面上会产生镜面反射,导致地面点云出现空洞。网格法会把空洞区域误判为障碍物。后来我加了一个「邻居投票」机制——如果一个网格被判定为障碍物,但周围8个邻居都是地面,那就把它也标为地面。

经验之谈:网格法最适合做实时系统。我在嵌入式平台上用C++实现过,单帧处理时间可以控制在5ms以内。RANSAC和射线法都做不到这么快。

道路边界提取:从「能走」到「怎么走」

地面分割告诉我们哪里是路面,但车还需要知道路在哪里结束。这就是道路边界提取要做的事。

我常用的方法是基于高度跳变检测:

  1. 在地面分割的基础上,沿横向扫描线分析
  2. 检测地面点到非地面点的高度突变
  3. 连续多个扫描线都有突变的位置,就是道路边界

这里有个坑——路沿和绿化带怎么区分?路沿通常只有10-15厘米高,而绿化带可能半米高。我一般设两个阈值:

  • 高度跳变在5-20厘米 → 路沿/路肩
  • 高度跳变大于20厘米 → 障碍物/绿化带

另外,道路边界不一定是直线。弯道、匝道、交叉口,边界都是曲线。我建议用B样条曲线拟合边界点,而不是硬套直线模型。

下面是我画的一张流程图,把今天讲的四种方法串起来:

地面分割与道路提取流程 原始点云输入 场景判断 RANSAC平面拟合 射线法地面分割 网格法地面分割(通用后处理) 道路边界提取(高度跳变检测) 地面点 + 道路边界

你看,实际工程中很少只用一种方法。我通常的做法是:先用场景分类器判断是结构化道路还是非结构化道路,然后选择主算法,最后用网格法做统一的后处理。这样既保证了精度,又兼顾了实时性。

总结一下:
  • 结构化道路 → RANSAC平面拟合(快、准)
  • 非结构化道路 → 射线法(适应性强)
  • 任何场景 → 网格法做后处理(稳定、鲁棒)
  • 道路边界 → 高度跳变 + 曲线拟合

最后说一句,地面分割没有银弹。我在不同项目上试过各种组合,最终发现最好的方案往往是「场景感知 + 多方法融合」。你想想看,高速公路和矿山小路怎么可能用同一套参数?

嗯,今天就聊到这儿。这些方法我都亲手调过,踩过的坑也都写在里面了。希望能帮你少走些弯路。

避坑指南:我曾经在隧道场景中吃过亏——隧道内地面平坦,但顶部也是平面,RANSAC经常把天花板当成地面。后来我加了高度先验(车辆上方3米以上的点不考虑),才解决这个问题。类似的坑还有很多,做地面分割一定要结合场景先验知识。

可可精品资料库