4. RRT与RRT*算法

各位同学,今天我们来聊聊路径规划里一个非常经典的方法——RRT算法。说实话,我刚入行那会儿,第一次看到RRT的,心里想的是:「这玩意儿真能用在机器人上?」后来在AGV项目里真刀真枪干了一回,才发现它比我想象的实用得多。

1.1 快速探索随机树的核心思想

RRT,全称Rapidly-exploring Random Tree。说白了,就是让一棵树在空间里「乱长」,但长得有策略。

它的核心逻辑很简单:

  • 从起点开始,随机撒点
  • 找到树上离随机点最近的节点
  • 朝随机点方向生长一小段
  • 重复,直到碰到终点

你想想看,这就像你在黑暗里走路,每走一步就随便扔个石子,然后朝石子方向迈一步。虽然看起来笨,但最终总能走到想去的地方。

关键点:RRT不保证找到最短路径,但它保证——只要路径存在,且采样足够多,就一定能找到一条路径。这在复杂环境里特别有用。

1.2 RRT*的渐进最优性

RRT有个明显的毛病:它找到的路径往往弯弯绕绕,不是最优的。我在项目里遇到过,RRT规划出来的路径,机器人走起来像喝醉了酒——来回晃悠。

RRT*就是来解决这个问题的。它做了两件事:

  1. 重新选择父节点——新节点生成后,检查附近有没有更近的节点可以当爹
  2. 重新布线——如果新节点能让附近节点的路径更短,就「抢」过来当爹

嗯,这里要注意:RRT*的「渐进最优性」是有前提的——采样次数趋近无穷时,路径才趋近最优。实际项目中,我们不可能采样无限次,所以它只是「越来越优」,不是「最优」。

我的经验:在大多数实际场景里,RRT*跑个几千次采样,路径质量就已经比RRT好很多了。没必要追求理论上的最优,够用就行。

1.3 采样策略与步长调参

调参是RRT系列算法的灵魂。我见过太多人把参数调得乱七八糟,然后说算法不好用。其实,问题往往出在参数上。

参数 作用 调参建议
步长 每次生长的距离 步长太大容易撞墙,太小效率低。我一般设成机器人半径的1.5倍
采样概率 是否偏向终点采样 设个0.1~0.3的概率直接采样终点,能加速收敛
搜索半径 RRT*中重新布线的范围 跟步长挂钩,一般设成步长的2~3倍
最大迭代次数 算法终止条件 别设太大,500~2000次一般够用

避坑指南:我曾经在狭窄通道场景里,步长设得太大,结果树永远穿不过门缝。后来把步长缩小到通道宽度的1/3,问题就解决了。记住:步长要小于环境中最窄通道的宽度。

1.4 采样策略的进阶技巧

基本的均匀采样,在空旷环境里还行。但到了复杂环境,比如有大量障碍物、狭窄通道,均匀采样就效率很低了。

我常用的几种采样策略:

  • 偏向采样——以一定概率直接采样终点方向,加速收敛
  • 障碍物边界采样——在障碍物边缘附近多采样,更容易找到绕行路径
  • 自适应采样——根据已探索区域密度,动态调整采样分布

举个例子,在仓库AGV项目里,货架之间只有1米宽的通道。如果用均匀采样,大部分点都落在货架区域,浪费计算资源。我改成偏向通道区域的采样,规划时间从3秒降到了0.8秒。

1.5 实战应用:复杂环境中的RRT*

来,我们看一个实际代码片段。这是我在一个巡检机器人项目里用过的RRT*核心逻辑:

def rrt_star(start, goal, obstacles, max_iter=1000, step_size=0.5):
    tree = {start: None}
    costs = {start: 0}
    
    for i in range(max_iter):
        # 采样:20%概率直接采终点,80%随机
        if random.random() < 0.2:
            sample = goal
        else:
            sample = random_sample(obstacles)
        
        # 找最近节点
        nearest = find_nearest(tree, sample)
        
        # 生长新节点
        new_node = steer(nearest, sample, step_size)
        
        if not collision(nearest, new_node, obstacles):
            # 找附近节点
            near_nodes = find_near(tree, new_node, step_size*2)
            
            # 选最优父节点
            best_parent = nearest
            best_cost = costs[nearest] + distance(nearest, new_node)
            
            for node in near_nodes:
                new_cost = costs[node] + distance(node, new_node)
                if new_cost < best_cost:
                    if not collision(node, new_node, obstacles):
                        best_parent = node
                        best_cost = new_cost
            
            # 加入树
            tree[new_node] = best_parent
            costs[new_node] = best_cost
            
            # 重新布线
            for node in near_nodes:
                new_cost = costs[new_node] + distance(new_node, node)
                if new_cost < costs[node]:
                    if not collision(new_node, node, obstacles):
                        tree[node] = new_node
                        costs[node] = new_cost
    
    return extract_path(tree, start, goal)

这段代码看起来简单,但有几个坑要注意:

  • find_near的搜索半径不能太大,否则计算量爆炸
  • 碰撞检测要高效,我一般用AABB包围盒加速
  • 终点判断别太严格,设个容忍距离,比如0.1米

我的习惯:在实际部署时,我会先跑一次RRT快速找条可行路径,然后用RRT*在路径附近精细优化。这样既快又好,比直接跑RRT*省一半时间。

1.6 知识体系总览

下面这张图,是我自己总结的RRT系列算法知识体系。你看一眼,心里就有谱了:

RRT系列算法 RRT(基础版) RRT*(优化版) RRT变种 快速探索 随机采样 重新选父节点 重新布线 Informed RRT* RRT-Connect 调参核心:步长 | 采样概率 | 搜索半径 | 最大迭代次数 实战技巧:先RRT找路径 → 再RRT*精细优化 典型应用:AGV导航 | 机械臂运动规划 | 无人机航迹规划 复杂环境:狭窄通道 | 动态障碍物 | 高维空间

这张图把RRT系列的核心脉络理清楚了。你从基础版RRT入手,理解它的随机探索机制,然后升级到RRT*,学会如何优化路径质量。最后根据实际场景,选择合适的变种。

好了,这一章的内容就到这儿。记住:RRT算法的精髓不在于它多聪明,而在于它用随机性解决了确定性方法搞不定的复杂问题。下一章我们会深入聊RRT*的数学原理和代码实现细节。

可可精品资料库