算法的故事 / 第一季第二季目录0 / 30 章已读

第二季 · 第 45 章

让机器人在未知世界中长出一棵树

《算法的故事》第二季 · 第三篇:让机器理解空间——史蒂文·拉瓦莱与快速探索随机树 RRT 的诞生1998年,美国爱荷华州,埃姆斯。爱荷华州立大学的计算机科学研究者史蒂文·拉瓦莱(Steven M. LaValle),正在思考一个看似简单、实际上异常困难的问题。怎样让机器人找到一条能够执行的运动路径?如果机器人只是一个能够在平面上自由移动的点,那么答案似乎并不复杂。我们可以先建立地图。将空间划分成网格。再使用上一章的A算法寻找道路。*但如果机器人不是一个点呢?如果它是一辆不能横向移动的汽车?如果它是一台拥有七个关节的机械臂?如果它必须同时考虑速度、转向角、加速度与碰撞?每增加一个需要考虑的变量,搜索空间就可能变得更加庞大。一辆汽车的状态,不只是它的位置。它还包括朝向、速度,以及其他与运动有关的变量。一台机械臂的状态,更不能简单地表示成地图上的一个点。它需要描述每一个关节的角度。如果我们试图把所有可能状态预先列出来,再逐一寻找路线,就会遭遇令人头疼的维度增长。这时候,拉瓦莱提出了另一种思路。不要先建立完整的搜索网格。也不要试图一次性描述所有可行状态之间的连接。从起点开始。在整个空间中随机选择一个位置。找到当前搜索树中距离它最近的节点。沿着随机位置的方向,向外伸出一小段新分支。如果不会碰撞,就保留这段分支。然后,再随机选择一个位置。继续生长。仿佛有一棵看不见的树,正在向未知空间不断伸出枝条。枝条可能撞到障碍物。也可能伸向一片尚未探索的空旷区域。当树的某个分支终于接近目标时,一条从起点到终点的可行路径就出现了。拉瓦莱将这种结构称为:Rapidly-exploring Random Tree,快速探索随机树。简称:

RRT

Rapidly-exploring Random Treeqrand ⟶ qnear ⟶ qnewq_{\mathrm{rand}}\ \longrightarrow\ q_{\mathrm{near}}\ \longrightarrow\ q_{\mathrm{new}}
在他本人后来整理的研究史中,拉瓦莱明确写道:他于1998年6月提出RRT。同年10月,这项研究以爱荷华州立大学计算机科学系技术报告 TR 98-11 的形式发表。 [lavalle.pl][lavalle.pl]这项算法后来成为采样式运动规划的重要基础。它也改变了人们理解“搜索”的方式。在A中,我们面对一张已经定义好的图,寻找应当扩展的下一个节点。*在RRT中,搜索所使用的图本身,也是在探索过程中逐渐生成的。这正是本章要讲述的核心变化。机器不再只是沿着已有的道路寻找答案,而是开始主动生成探索未知空间的道路。

第一幕:当机器人有七个关节,地图突然变得不可想象

具有多个旋转关节的机器人。关节越多,需要描述的配置变量通常也越多,运动规划问题因此进入高维配置空间。我们先从一台机械臂开始。想象一台安装在工厂里的机器人。它的任务,是将桌上的零件移动到指定位置。如果周围没有障碍物,我们可以尝试直接控制机械臂运动。但现实中可能存在:其他机械设备。工作台。安全围栏。正在移动的其他机械臂。甚至机器人自身不同部分之间的碰撞风险。因此,机械臂不能只知道目标在哪里。它还必须保证整个运动过程都安全。

一个关节,就是一个变量

假设机械臂只有一个能够旋转的关节。我们可以用一个角度表示它的状态:q=(θ1)q=(\theta_1)如果有两个关节:q=(θ1,θ2)q=(\theta_1,\theta_2)如果有六个关节:q=(θ1,θ2,…,θ6)\boxed{q=(\theta_1,\theta_2,\ldots,\theta_6)}现在,机器人状态就是六维空间中的一个点。这组变量所构成的空间,称为配置空间(Configuration Space,C-space)。配置空间的维度,通常与描述机器人姿态所需要的独立配置变量数量有关。它不一定等于机器人实际所处物理空间的维数。一台机械臂在三维房间里工作,却可能需要六个、七个甚至更多配置变量,才能描述自己的姿态。

如果我们把每个关节都离散成100种角度呢?

假设每个关节仅允许100个离散状态。对于一个关节:100100种配置。两个关节:1002=10000100^2=10000种配置。六个关节:1006=1012100^6=10^{12}种配置。七个关节:1007=1014100^7=10^{14}种配置。
图45-1:配置空间的组合增长
独立关节变量数每个变量取100个值
1100
210,000
31,000,000
4100,000,000
61,000,000,000,000
7100,000,000,000,000
这是均匀离散网格的配置数量,不是所有运动规划算法实际需要枚举的节点数。它用于说明维度增加带来的潜在计算负担。
我们在前面章节中,已经多次遇到这种现象。旅行商问题随着城市数量增加,路线排列数量迅速增长。动态规划问题随着状态变量增加,状态数量可能变得庞大。而机器人运动规划所遭遇的,是一种直接来自空间维度的困难。它通常被称为维度灾难(Curse of Dimensionality)。

更麻烦的是,并非每种姿态都可以执行

假设一台六关节机械臂。从角度数值上看:q=(20∘,30∘,…)q=(20^\circ,30^\circ,\ldots)可能是一个合法的关节配置。但如果机械臂在这个姿态下撞到了工作台,那么这个状态就不允许使用。我们把全部配置空间记作:C\mathcal C存在碰撞的区域记作:Cobs\mathcal C_{\mathrm{obs}}没有碰撞的配置集合则是:Cfree=C∖Cobs\boxed{ \mathcal C_{\mathrm{free}} = \mathcal C\setminus\mathcal C_{\mathrm{obs}} }机器人需要寻找一条连续路径:q:[0,1]→Cfreeq:[0,1]\rightarrow\mathcal C_{\mathrm{free}}满足:q(0)=qstartq(0)=q_{\mathrm{start}}q(1)=qgoalq(1)=q_{\mathrm{goal}}而且:q(t)∈Cfreeq(t)\in\mathcal C_{\mathrm{free}}对整个运动过程都成立。这比只检查起点和终点更严格。机器人可能在两个姿态下都没有碰撞,却在从第一个姿态运动到第二个姿态的过程中,撞到障碍物。

有时连直接连接两个状态都很困难

如果机器人是一个能够在平面上任意移动的点,两个位置之间的连接可以使用直线。但对于汽车而言,两个状态之间不能随便画一条直线。汽车不能瞬间转向任意角度。它也不能在所有方向上直接移动。如果还要考虑速度和加速度,问题会更复杂。此时,规划器不仅需要知道:从哪里到哪里?还要考虑:用什么控制输入?执行多久?经过哪些中间状态?拉瓦莱在1998年的原始报告中特别强调:RRT针对的一类重要困难,正是**非完整约束(Nonholonomic Constraints)**和涉及运动动力学的规划问题。传统概率路图方法往往需要为两组采样状态构造连接路径;对于复杂的动力系统,这项局部连接本身就可能十分困难。而RRT尝试通过短时间施加控制输入,让树逐渐向外生长。[msl.cs.illinois.edu]这解释了为什么RRT不是单纯给A*增加一点随机数。它是在解决另一种结构的搜索问题。当完整的状态图难以预先建立时,能不能边探索,边生成一张足以找到答案的图?

第二幕:1996年,随机采样已经开始帮助机器人寻找道路

概率路图方法 PRM:先在可行配置空间中采样,再尝试连接附近节点,形成一张可用于寻找路径的路网。它是RRT出现之前的重要采样式运动规划成果。如果我们把RRT的历史讲成:“1998年,拉瓦莱第一个想到利用随机数为机器人规划路径。”那就不准确。在RRT出现之前,研究人员已经探索过随机化运动规划方法。其中一项经典成果,是概率路图算法。英文名称:Probabilistic Roadmap Method。简称:PRM。

1996年,四位研究者发表了一项重要成果

1996年,Lydia Kavraki、Petr Švestka、Jean-Claude Latombe 与 Mark Overmars 在《IEEE Transactions on Robotics and Automation》发表:Probabilistic Roadmaps for Path Planning in High-Dimensional Configuration Spaces《高维配置空间路径规划的概率路图方法》。论文讨论了怎样通过随机采样与局部连接,在复杂配置空间中建立可搜索的图结构。[Kavraki Lab]PRM的基本思想,可以分为两个阶段。第一阶段:学习或预处理。在配置空间中采样许多无碰撞的状态。尝试将空间中相近且能够安全连接的状态连成边。由此获得一张路图。第二阶段:查询。给定起点和目标。将它们与路图中的合适节点连接。然后在路图上寻找一条通路。这项方法尤其适合那些环境比较稳定、需要反复进行多次路径查询的场景。因为最昂贵的一部分路图构建工作,可以在查询之前完成。

如果我们只需要查询一次呢?

假设一台机器人只需要从A移动到B。完成任务后,地图和目标就会改变。这时,先在整个高维空间里建立一张通用路图,未必是最合适的策略。另外,PRM常常依赖一个局部规划器。它需要判断:能否从采样状态A直接连接到采样状态B?对于简单的自由移动点,这可以通过直线碰撞检查完成。但对于具有复杂动力学约束的系统,连接两个任意状态可能十分困难。甚至需要求解一个复杂的控制问题。拉瓦莱注意到了这项局限。

为什么一定要把两个随机状态直接连起来?

这是RRT最重要的思想转变之一。假设我们已经从起点生成了一棵树。现在随机选择状态:qrandq_{\mathrm{rand}}我们并不要求机器人直接到达它。而是先寻找树中距离这个随机状态最近的节点:qnearq_{\mathrm{near}}然后只向随机目标方向前进一小步。得到:qnewq_{\mathrm{new}}如果这一步有效,就将新状态加入树。这样,我们不必一次性求解两个任意状态之间的完整运动连接。我们只需要解决一次比较短的局部扩展。对于复杂的动力系统,这项区别具有重要意义。当然,真正使用RRT处理动力学约束时,“向前一步”不能简单地写成欧几里得直线插值。它可能需要施加一个控制输入,并用运动方程计算短时间后的状态。但原始思想保持不变:不要求一次抵达远方,只要求每一步都能产生一个有效的新状态。

PRM与RRT的真正差别

图45-2:两种采样式规划方法
PRM · 路图在空间中采样并建立多条可能的邻接边
RRT · 搜索树从起点逐步生长,每个新增节点由已有节点扩展而来
这不意味着PRM一定比RRT差。也不意味着RRT能够解决所有PRM无法解决的问题。它们具有不同的设计特点。PRM通常强调建立可以重复查询的路图。基础RRT则更自然地从一个起始状态出发,逐步探索新的区域。在实践中,选择哪一种方法,还取决于运动系统、配置空间、局部连接方法与查询需求。拉瓦莱本人在1998年的论文里,也明确将RRT放在此前随机化规划研究的背景中讨论,而不是宣称所有随机路径规划都由自己发明。[msl.cs.illinois.edu][Robotics and Automation, IEEE Transactions on]接下来,我们终于来到1998年。那棵后来改变机器人运动规划研究的树,将第一次出现在论文里。

第三幕:1998年,爱荷华州立大学的一份技术报告

史蒂文·拉瓦莱(Steven M. LaValle)。1998年提出RRT时,他任职于美国爱荷华州立大学;后来长期在伊利诺伊大学和芬兰奥卢大学从事机器人、规划与虚拟现实研究。[Illinois]对于一项已经成为算法经典的技术,我们很容易只记住它的名字。RRT。快速探索随机树。但如果想真正理解发明者的贡献,就应该看看他当时究竟在研究什么。

拉瓦莱并不是在寻找“最漂亮的随机树”

在1998年的原始报告中,他开篇就提出了一个问题。此前已经存在很多随机运动规划方法。其中一些还取得了很好的实验结果。那么,为什么还需要另一项随机路径规划技术?他的回答并不是:因为随机树画出来很好看。也不是:因为树结构一定比图结构更优秀。他真正关心的是:现有方法在处理复杂非完整约束、动力学约束和高维状态空间时,存在某些不便。因此,需要一种更加适合通过控制输入逐步扩展、而不要求在任意两点之间直接完成连接的规划结构。这才是RRT的原始研究动机。[msl.cs.illinois.edu]

一项成果,有两个值得记住的日期

拉瓦莱在个人研究资料中写道:他于1998年6月引入RRT。而正式列入其出版目录的爱荷华州立大学技术报告编号是:TR 98-11,1998年10月。报告题目:Rapidly-Exploring Random Trees: A New Tool for Path Planning《快速探索随机树:路径规划的一种新工具》。[lavalle.pl]1998年原始研究报告首页。右侧的黑色树状线条是五维汽车运动状态空间中RRT的二维投影,粗线代表其中一条被突出显示的轨迹。[msl.cs.illinois.edu]原始报告只有四页。但第一页上的图,就已经非常接近我们今天看到的RRT可视化。一条粗线从左向右延伸。许多细小枝条不断向各个方向生长。整幅图看起来像一片布满分枝的树冠。不过,这幅图并不是普通的二维平面迷宫。论文特别说明,它是一项五维汽车运动状态空间RRT的二维投影。这意味着,图上的线条只是高维运动数据在二维画布上的表现。如果忘记这一点,就容易误以为原始RRT只研究平面中的几何折线。事实上,拉瓦莱从一开始就试图解决更一般的状态空间运动规划问题。

1998年的核心伪代码

原始报告中的算法流程极其紧凑。将复杂细节简化后,我们可以写成:
text
初始化:树中只有起点

重复:
    随机选择一个状态
    找出树中最近的节点
    选择朝随机状态前进的控制输入
    产生一个新的可行状态
    将新状态和新边加入树

返回生成的树
原文使用的关键词包括:RANDOM_STATENEAREST_NEIGHBORSELECT_INPUTNEW_STATE这些名称体现了RRT最初的设计重点。随机采样负责提出一个探索方向。最近邻查询负责确定从哪里生长。控制输入负责决定怎样扩展。新状态计算负责确认扩展实际抵达了哪里。然后,树中增加一个节点和一条边。[msl.cs.illinois.edu]这里必须强调:原始RRT不只是寻找一张二维地图上的终点。它首先是一项逐渐生成探索树的随机化数据结构。真正用于具体路径规划时,还需要加入目标区域判断、碰撞检测,以及从树中恢复路径的机制。这些内容后来形成了我们今天在教材和机器人软件库中最熟悉的RRT流程。

作者后来怎样总结这项算法?

拉瓦莱在个人研究主页上,使用了一个很有意思的描述。RRT通过随机采样,不断将树扩展到新的空间。这种机制会产生一种沃罗诺伊偏置(Voronoi Bias),使早期探索倾向于快速伸向尚未覆盖的区域。随着树不断增长,探索分布会逐渐变得更加广泛。[lavalle.pl]注意这个词:沃罗诺伊。上一章我们刚刚学过它。这不是巧合。因为RRT真正令人惊讶的数学秘密,恰好与沃罗诺伊区域有关。而要理解这一点,我们必须先把一棵树真正生长出来。

第四幕:一棵树究竟怎样长出来?只需要五个动作

RRT的一次典型迭代:采样随机目标、寻找最近的树节点、向目标方向扩展、检测碰撞,最后把有效节点加入树。现在,让我们把高维机械臂暂时放在一边。先研究二维平面。假设有一片大小为:100×100100\times100的正方形区域。机器人位于:S=(10,10)S=(10,10)目标位于:G=(90,90)G=(90,90)中间还有一些障碍物。我们想找出一条不会撞到障碍物的路径。

第一步:在空间中随机选一个点

假设第一次随机采样得到:qrand=(72,65)q_{\mathrm{rand}}=(72,65)这个点可能距离机器人很远。但RRT不会要求机器人立即抵达它。这个随机点只负责提供一个探索方向。我们可以把它想象成:一棵树收到一条指令——向这个方向长一点。

第二步:找到树上距离采样点最近的节点

在第一次迭代中,树里只有起点:qstart=(10,10)q_{\mathrm{start}}=(10,10)所以最近节点自然是起点。qnear=(10,10)q_{\mathrm{near}}=(10,10)等树逐渐生长以后,就需要在全部已有节点中寻找距离采样点最近的那个。数学上可以写成:qnear=argmin⁡q∈VT d(q,qrand)\boxed{ q_{\mathrm{near}} = \underset{q\in V_T}{\operatorname{argmin}} \ d(q,q_{\mathrm{rand}}) }其中:VTV_T是当前树中所有节点的集合。

第三步:只向随机点前进一步

假设每次树枝最多增长:η=10\eta=10个单位。从:qnear=(10,10)q_{\mathrm{near}}=(10,10)指向:qrand=(72,65)q_{\mathrm{rand}}=(72,65)的向量是:(62,55)(62,55)向量长度为:622+552=6869≈82.88\sqrt{62^2+55^2} =\sqrt{6869} \approx82.88我们不希望一次跨越82.88个单位。只想前进10个单位。因此,将向量归一化,再乘以步长:qnew=qnear+ηqrand−qnear∥qrand−qnear∥\boxed{ q_{\mathrm{new}} = q_{\mathrm{near}} + \eta \frac{q_{\mathrm{rand}}-q_{\mathrm{near}}} {\|q_{\mathrm{rand}}-q_{\mathrm{near}}\|} }在这个例子中:qnew≈(17.48,16.64)q_{\mathrm{new}} \approx(17.48,16.64)这就是新树枝的终点。更一般地,如果随机点距离最近节点不足 η\eta,则可以直接将新节点设为随机点:qnew=qnear+min⁡(1,ηd)(qrand−qnear)q_{\mathrm{new}} = q_{\mathrm{near}} + \min\left(1,\frac{\eta}{d}\right) (q_{\mathrm{rand}}-q_{\mathrm{near}})其中:d=∥qrand−qnear∥d=\|q_{\mathrm{rand}}-q_{\mathrm{near}}\|这种操作通常被称作:Steer——朝目标状态推进。

第四步:检查新树枝是否碰到障碍物

这是整项算法不可省略的部分。假设:qnearq_{\mathrm{near}}没有碰撞。而:qnewq_{\mathrm{new}}也没有碰撞。是否意味着两者之间的线段一定安全?不意味着。
图45-3:两个端点都安全,连接它们的树枝仍然可能碰撞
AB障碍物连接线段穿过障碍物,因此不能直接加入树
查看 SVG 配图原件
示意图原件 45-009-50c139d436a0.svg
45-009-50c139d436a0.svg · 点击查看大图
所以,我们必须检查:从最近节点到新节点的整段运动是否有效。如果这条树枝穿过障碍物,就拒绝扩展。如果整个过程都安全,则可以继续。对于二维点机器人与矩形障碍物,这可以通过线段—矩形相交测试实现。对于机械臂,可能需要沿运动过程检查机器人各部分与环境之间的碰撞。对于具有动力学约束的机器人,还需要同时验证控制输入产生的轨迹是否合法。

第五步:将新节点加入树

如果新状态有效,就添加:VT←VT∪{qnew}V_T\leftarrow V_T\cup\{q_{\mathrm{new}}\}并添加新边:ET←ET∪{(qnear,qnew)}E_T\leftarrow E_T\cup \{(q_{\mathrm{near}},q_{\mathrm{new}})\}同时记录:parent⁡(qnew)=qnear\operatorname{parent}(q_{\mathrm{new}}) = q_{\mathrm{near}}这意味着,每个新加入的节点都有一个父节点。整张图保持为一棵树。如果最终某个节点能够安全连接到目标,我们就可以沿着父节点不断回溯,恢复整条路径。

为什么它不是普通的随机漫步?

这里有一个容易误解的地方。RRT虽然使用随机采样,但它不是一个机器人在随机地乱走。随机漫步通常只依赖当前位置,随机选择下一步。RRT则不同。它会保留已经探索出的整棵树。每次随机采样,都可能选择树中的不同节点作为生长起点。因此,新的分支可以从旧树上任何合适的位置伸出。某些枝条可能绕开障碍物。另一些枝条则可能深入以前没有探索过的区域。这种结构使算法能够在多个方向上持续扩展,而不必一次次回到原始起点重新搜索。
图45-4:一轮RRT迭代
1
Sample生成随机目标 q_rand

2
Nearest在现有树中找到最近节点 q_near

3
Steer从最近节点向随机目标迈出有限的一步

4
Collision Check验证整段新运动是否无碰撞

5
Insert若有效,加入新节点与父子边
这五项操作,看起来比上一章A*的图搜索还要简单。但它们能够在复杂空间里产生十分丰富的结构。那么问题来了:如果每次都随机选择目标,为什么树不会永远在起点附近打转?为什么它会倾向于向没有探索过的区域伸展?答案竟然来自我们在第42章学习过的一项几何结构。

第五幕:沃罗诺伊图回来了——为什么树会主动探索未知区域?

RRT的沃罗诺伊偏置:若均匀采样整个空间,拥有较大最近邻区域的树节点,更容易被选为扩展起点。还记得第42章吗?我们曾经研究过:给定一组站点,平面上的每个位置属于哪个最近站点?我们用垂直平分线,将空间划分成许多沃罗诺伊区域。现在,把沃罗诺伊图中的站点换成RRT树节点。问题就变成:随机点会将哪一个树节点选为最近邻?

每个树节点,都拥有一块自己的领地

假设当前树里有三个节点:A,B,CA,\quad B,\quad C它们对应的沃罗诺伊区域分别是:VA,VB,VCV_A,\quad V_B,\quad V_C如果随机采样得到的点位于 VAV_A,那么最近节点就是A。如果落在 VBV_B,最近节点就是B。如果落在 VCV_C,最近节点就是C。假设我们在面积为10000的区域中均匀随机采样。A的沃罗诺伊区域面积为:20002000B的区域面积为:30003000C的区域面积为:50005000那么,下一次采样选择它们作为最近节点的概率分别为:P(A)=0.2P(A)=0.2P(B)=0.3P(B)=0.3P(C)=0.5P(C)=0.5因此,C被选为扩展起点的机会最大。
图45-5:沃罗诺伊区域越大,被随机选中的机会越高
A20%B30%C50%区域面积与被选中概率的比例示意
查看 SVG 配图原件
示意图原件 45-011-379bef490761.svg
45-011-379bef490761.svg · 点击查看大图
这是面积比例的概念示意,不是三点实际沃罗诺伊图。对于均匀采样,最近邻被选中的概率等于其沃罗诺伊单元在采样域内所占的面积比例。
一般地,若随机点在区域 XX 中均匀采样,那么某个树节点 vv 被选为最近节点的概率为:P(v被选中)=μ(Vv∩X)μ(X)\boxed{ P(v\text{被选中}) = \frac{\mu(V_v\cap X)}{\mu(X)} }其中:μ\mu表示面积,或者更高维空间中的体积。在存在障碍物且只在自由空间采样时,我们需要将对应区域改为自由采样域,并使用相应的交集测度。这项公式说明了RRT一项非常重要的特征。那些朝向广阔、尚未探索区域的边缘节点,往往拥有较大的沃罗诺伊区域。因此,它们被随机样本选为最近邻的机会,可能比内部节点更高。结果就是:树会倾向于向新的空间伸展。

为什么称为“探索偏置”?

假设有一棵树已经在左下角密密麻麻地生长了许多分支。这些内部节点彼此接近。它们各自对应的沃罗诺伊区域可能很小。而树最右端有一根枝条,伸向一大片还没有节点的空旷区域。它的最近邻领地可能很大。当我们在整个平面中均匀采样时,许多随机点会落在这片广阔区域。于是,最右端的枝条就有更大机会继续向外生长。这种性质叫作:Voronoi Bias——沃罗诺伊偏置。拉瓦莱在1998年的研究中强调,RRT对尚未探索区域的扩展偏置,是其重要优势之一。他在后来的研究主页上,也明确将这种偏置与树快速扩展和空间覆盖联系起来。[lavalle.pl][msl.cs.illinois.edu]

为什么这比随机漫步更有价值?

普通随机漫步可能反复在已经访问过的区域附近移动。而RRT保存整个搜索树。当随机样本落在某片尚未覆盖的区域时,它能够从距离该区域最近的树节点继续伸出分支。这使探索能够同时从多个前沿推进。不过,也不能把沃罗诺伊偏置误解成:RRT一定优先选择正确方向。或者:所有边缘节点一定比内部节点更容易被选中。实际概率取决于:树节点的空间分布。采样区域。距离度量。障碍物。以及是否采用目标偏置等策略。如果障碍物挡住了某个方向,即使那个方向经常被采样,新的树枝仍然可能被碰撞检测拒绝。因此,沃罗诺伊偏置提供的是一种空间探索倾向,而不是保证每次扩展都有进展的魔法。

从第42章到第45章,一项几何概念有了新生命

在第42章,沃罗诺伊图用来回答:哪个医院距离居民最近?现在,它用来解释:为什么某个树节点更容易成为随机探索的起点?两个问题的形式完全不同。但它们使用的是同一个最近邻结构。这正是算法研究最有趣的地方之一。一项原本用于划分空间的数学结构,也可以用来解释随机搜索过程中的概率偏置。RRT不需要真的构造完整的沃罗诺伊图才能运行。它只需要进行最近邻查询。但沃罗诺伊区域的存在,让我们能够理解算法为什么会产生那样的生长行为。

第六幕:随机并不等于一定成功——概率完备性究竟保证什么?

狭窄通道是采样式运动规划的典型挑战。通道在整个配置空间中占比很小时,随机采样可能需要较多尝试才能找到通过它的有效分支。现在,我们已经知道RRT会向新的区域探索。但一个更严肃的问题出现了。如果路径确实存在,RRT是否一定能够找到?假设从起点到目标,只有一条非常狭窄的通道。通道很短,却只有很小的宽度。对于人类,我们也许能够一眼看出它的位置。但对于均匀随机采样的RRT而言,通道占据的空间比例可能极小。因此,大量随机样本都可能落在通道之外。树枝一次又一次撞到墙壁。搜索迟迟无法穿过去。这似乎说明:RRT并不可靠?答案需要从一种特殊的数学保证说起。

确定性完备与概率完备不是同一回事

我们先定义什么叫作完备性。一种搜索算法如果能够保证:只要存在符合问题定义的可行路径,算法最终就会找到它;在适当的完整判定条件下,如果不存在路径,也能够给出对应结论;那么我们可以讨论它的完备性。但采样式运动规划面对的往往是连续而复杂的配置空间。有限次随机采样,不能穷尽所有可能位置。RRT所拥有的一项重要理论性质,是在相应假设下的:概率完备性(Probabilistic Completeness)。它的含义是:如果存在具有足够稳健性的可行路径,那么随着有效随机采样次数不断增加,算法未能找到路径的概率趋向于零。可以写成:lim⁡N→∞P(前N次采样仍未找到路径)=0\boxed{ \lim_{N\rightarrow\infty} P(\text{前N次采样仍未找到路径}) =0 }注意,这不是说:采样100次一定找到。也不是说:采样1000次一定找到。甚至不是说:采样一百万次时,失败概率严格等于零。它说明的是一个随着采样次数增大而成立的极限性质。

为什么理论上可以越来越接近成功?

想象某条可行路径能够被一串具有正体积的小区域覆盖。只要树依次扩展进入这些区域,就可以逐步沿着路径向目标推进。如果每个区域都拥有非零的采样概率,而且局部扩展与碰撞检测能够正确完成需要的连接,那么随着独立采样次数增长,最终依次命中这些区域的机会会越来越大。这种证明思想涉及:采样分布。正测度区域。路径的正间隙(Clearance)。局部连接机制。以及搜索算法的具体假设。因此,概率完备性并不是一句对任何随机树程序都自动成立的口号。它需要特定的数学条件。拉瓦莱在1998年的原始报告中,就将概率完备性列为RRT的重要性质之一,同时强调其分析需要相应条件。[msl.cs.illinois.edu]

一个简单的概率例子

假设某个关键区域,只占随机采样范围的:p=0.01p=0.01即1%。每次均匀独立采样时,落入这片区域的概率是1%。那么,一次没有采到它的概率是:1−p=0.991-p=0.99连续 NN 次都没有采到的概率是:(1−p)N\boxed{(1-p)^N}例如:P(100次都没有采到)=0.99100≈0.366P(\text{100次都没有采到}) =0.99^{100} \approx0.3661000次:P(1000次都没有采到)=0.991000≈4.32×10−5P(\text{1000次都没有采到}) =0.99^{1000} \approx4.32\times10^{-5}这说明,只要区域拥有正采样概率,多次独立随机尝试就能不断降低完全错过它的概率。但这里有一个非常重要的限定:这只是独立采样命中某个固定区域的概率模型,并不是RRT完整求解成功率的公式。因为真正的RRT还必须考虑:是否已经有合适的树节点靠近区域?新枝条能否通过碰撞检测?是否存在动力学限制?需要连续通过多少个关键区域?这些因素决定了最终的路径发现概率。

如果通道宽度恰好为零呢?

假设一条路径只有精确满足某个等式才能通过。可行区域在采样空间中没有正体积。那么,单纯进行连续空间里的均匀随机采样,就可能几乎不可能恰好命中所需的状态。此时,常规概率完备性证明使用的正间隙条件不再成立。不能简单宣称:“只要给RRT无限时间,它一定能够解决所有可能存在路径的问题。”对于非常狭窄的通道、低维约束流形、复杂闭链机构等问题,可能需要专门的采样策略、约束投影或其他规划技术。

还有一个非常重要的区别:找到路径不等于找到最短路径

现在假设RRT已经找到一条合法路径。它绕过全部障碍。从起点通向目标。这意味着规划成功了吗?如果目标只是:找到任意一条可行路径。那么,是的。但如果目标是:找到所有可行路径中最短的一条。情况就完全不同了。基础RRT并不保证第一次找到的路径是最短的。更进一步,即使我们让普通RRT持续增加采样,它也不具备一般情况下收敛到最优路径的渐近最优性保证。后来,Karaman与Frazzoli对这项问题进行了严格研究,并提出RRT*等具有不同最优性性质的算法。[Sertac Karaman, Emilio Frazzoli, 2011]这将成为本章后半部分最重要的区别:概率完备,保证的是在条件成立时找到可行解的概率趋于1;渐近最优,研究的是随着计算持续进行,解的成本是否趋于真正的最优值。两者不是一回事。

第七幕:互动实验室——亲眼看着一棵随机搜索树生长

随机树会从起点不断向空间伸展,遇到障碍时拒绝相应扩展,并通过不同的分支寻找可行通路。下面可以逐次观察整个过程。现在,我们终于要让RRT真正运行起来。这一次不再使用抽象的六维机械臂。我们先建立一张二维地图。蓝色S是机器人的起点。绿色G是目标。灰色矩形代表障碍物。细蓝线是已经生成的搜索树。橙色标记代表当前随机采样的位置。每次点击,算法会:随机选择一个目标点。在树中寻找最近节点。向采样方向生长最多6个单位。检查新线段是否与障碍物相交。如果安全,就把新节点加入树。如果碰撞,则拒绝这一轮扩展。

实验一:调整随机种子和目标偏置

为了方便重复观察,我们不使用每次都会变化的随机序列。而是提供三个可选的随机种子。同样的地图、随机种子和参数,会产生相同的实验过程。此外,你还可以修改目标偏置(Goal Bias)。例如:目标偏置为0%,说明每次都从地图中随机选择采样点。目标偏置为10%,说明约10%的迭代会直接将目标位置作为采样方向,其余时候仍然随机采样。目标偏置为25%,说明算法更加频繁地尝试朝目标伸展。但目标偏置越大,并不一定越好。如果目标和机器人之间存在障碍,大量直接朝目标的尝试可能反复撞墙。适当保留对其他方向的随机探索,才能帮助搜索绕行道路。
图45-6:RRT随机树生长实验室本实验采用二维点机器人、固定步长、轴对齐矩形障碍和确定性的随机数序列。树枝与障碍物使用连续线段相交检查。
0 / 102
SG
探索树
当前采样
已找到路径
障碍物
当前扩展情况初始状态
树中目前只有起点S。下一轮将开始随机采样。
当前树中节点

1

当前采样轮次

0

先做三项对比实验

第一项:选择“绕开中央障碍”,使用随机种子42,将目标偏置设为10%。请先推进前20轮。你会看到,树一开始只有起点。随后逐渐向多个方向伸展。当某条树枝尝试穿过中央障碍物时,就会被拒绝。而其他枝条继续沿着障碍物的上方或下方生长。最终,某个分支可能绕过障碍,抵达目标。第二项:切换到“穿越狭窄通道”。这张地图中,中间的障碍物只留下很小的水平通路。树必须找到能够进入通道的分支,并沿着正确方向逐渐通过。请分别选择0%、10%、25%的目标偏置,观察成功轮次有什么不同。第三项:使用随机种子2026,将目标偏置设为0%,选择“穿越狭窄通道”。在这组实验参数和450轮预算下,搜索可能没有得到一条完整路径。这并不违反概率完备性。概率完备性从来没有保证:某个固定随机序列,在450次以内必定成功。如果换一个随机种子,或者提高采样预算、调整步长与目标偏置,搜索结果就可能改变。

这个实验使用的是什么模型?

为了让树的生成过程可见,我们有意进行了几项简化。机器人是二维平面中的点。障碍物是轴对齐矩形。边只能沿直线连接。步长固定为6。碰撞检查为线段与带有安全裕量的矩形相交判定。这些都是二维几何规划的合理教学约定。但它们并不意味着:真实汽车能够沿着图中任意一条直线突然改变朝向。真正的汽车、无人机或机械臂,需要使用符合其运动学和动力学的扩展函数。因此,本实验展示的是基础几何RRT,而不是完整的高维动力学RRT。

第八幕:从一棵树到两棵树——1999年至2001年的新突破

双向搜索树示意:一棵树从起点生长,另一棵从目标生长;如果两棵树能够通过合法运动连接,就获得一条完整路径。RRT提出之后,研究者很快开始尝试扩展它的能力。其中有一项非常自然的想法。既然一棵树可以从起点出发,向目标探索……为什么不能同时从目标出发,也生成另一棵树?这样,两棵树就有机会在空间中间相遇。

2000年,RRT-Connect

1999年前后,James J. Kuffner 与 Steven M. LaValle 继续合作研究RRT。2000年4月,他们在旧金山举行的 IEEE 国际机器人与自动化会议(ICRA)上发表:RRT-Connect: An Efficient Approach to Single-Query Path Planning《RRT-Connect:一种高效的单次查询路径规划方法》。论文发表于会议论文集第995—1001页。[数字对象标识符][Kuffner]RRT-Connect 使用两棵树。一棵从起始配置出发。一棵从目标配置出发。在每轮迭代中,一棵树向随机样本生长。然后,另一棵树尝试向刚刚生成的新节点推进。与普通基础RRT相比,它不仅使用随机探索,也更加积极地尝试连接两棵已有的树。这种策略在许多单次路径查询问题中非常有效。

为什么两棵树可能更快?

想象一条很长的走廊。起点在最左边。目标在最右边。如果只有一棵树,它必须不断伸出分支,直到接近目标。如果有两棵树:左边的树向右探索。右边的树向左探索。那么,两棵树可能在走廊中间附近相遇。这并不意味着任何地图上都能获得两倍速度提升。实际性能取决于障碍布局、采样方式、局部连接能力和其他参数。但它提供了一种非常有价值的设计方式:将单向探索变成相向探索,让两组已经发现的可行状态有机会彼此连接。

但是,不能把RRT-Connect直接用于所有动力学问题

这里有一个容易忽略的数学前提。对于能够自由前后移动的平面点机器人,反向生成一条几何路径通常没有问题。如果一条线段从A到B无碰撞,那么它反过来从B到A,同样无碰撞。但对于某些具有动力学约束的系统,运动并不一定可以随意倒转。例如:一架具有特定飞行动力学的无人机,不一定能够按时间倒序执行任何一段原来的运动。一辆只能前进的汽车,也不能把一条前进轨迹简单反转成另一条仍然合法的前进轨迹。因此,双向树连接是否有效,还需要考虑状态转移是否可逆、能否逆向积分,以及连接时能否满足动力学约束。Kuffner与LaValle在2000年的RRT-Connect论文中,主要研究适合单次查询的几何配置空间规划,并展示了机械臂等应用。不能把其简单的双向连接思想,未经修改直接推广到任意动态系统。

1999年:动力学规划也在推进

事实上,在RRT-Connect之前,两位研究者已经于1999年的ICRA会议上发表:Randomized Kinodynamic Planning《随机化运动动力学规划》。后来,扩展研究发表于2001年5月《The International Journal of Robotics Research》第20卷第5期,第378—400页。[Sage Journals][OpenAIRE - Explore]这项研究探讨的不是普通点机器人如何在平面上走直线。而是:怎样为具有速度、加速度与动力学约束的系统,寻找满足物理运动规律的轨迹?论文报告了悬浮器和卫星等具有较高维状态空间的实验模型,状态维数可达到12维。这项结果说明,RRT的发展方向并不是局限于二维迷宫。它从诞生之初,就与复杂运动控制问题有紧密联系。

同一个扩展方向,也可以用不同控制输入

对于简单的二维点机器人,扩展可以写成:qnew=qnear+Δqq_{\mathrm{new}} = q_{\mathrm{near}}+\Delta q但对于动力学系统,我们更可能使用:x˙=f(x,u)\dot x=f(x,u)其中:xx表示系统状态。uu表示控制输入。从:xnearx_{\mathrm{near}}出发,施加控制:uu持续时间:Δt\Delta t得到:xnew=Integrate⁡(f,xnear,u,Δt)x_{\mathrm{new}} = \operatorname{Integrate} \left(f,x_{\mathrm{near}},u,\Delta t\right)这里的 Integrate 可以代表数值积分等状态传播过程。因此,高维动力学RRT的关键操作,不是简单地朝随机坐标画一条线。而是:选择控制输入。预测系统运动。检查整段轨迹是否满足约束。然后保留安全的新状态。
图45-7:RRT在不同运动模型中的扩展
几何RRT使用几何插值生成新状态
动力学RRT使用控制输入传播出合法轨迹
至此,RRT已经形成了一个更加丰富的研究方向。基础RRT强调从一个起始状态迅速探索空间。RRT-Connect强调两棵树之间的高效连接。动力学RRT则进一步考虑受运动方程限制的状态扩展。但它们仍然拥有一个共同的问题。如果第一次找到的路径并不是最好的,我们应该怎样继续改进?答案将我们带到2011年。

第九幕:2011年,RRT*让树不仅能生长,还能重新整理自己

RRT*在普通RRT生长机制上增加了代价比较和重新连接(rewiring)。新节点有时能够为附近的旧节点提供更短路径,从而逐渐改善整棵搜索树的成本结构。现在,我们面对一个非常现实的场景。机器人已经找到了一条安全路径。它确实能够到达目标。但这条路径绕了很多弯。假设直线距离只有:100100个单位。普通RRT找到的可行路径,可能长达:170170个单位。当然,它完成了“找到一条路”的任务。但如果机器人需要每天重复这条路线数千次,那么额外的路程可能意味着大量时间与能源消耗。因此,我们希望算法不要在找到第一条路径之后就满足。如果继续计算,它应该逐渐寻找成本更低的路线。这项要求叫作:最优运动规划(Optimal Motion Planning)。

普通RRT的问题在哪里?

基础RRT中,每次新增节点都会选择当前最近的树节点作为父节点。假设新节点C来自B。那么:parent⁡(C)=B\operatorname{parent}(C)=B之后,即使树长到了另一个位置A,而A可以以更短成本连接C,基础RRT也不一定重新修改C的父节点。也就是说,树的结构在构造过程中很大程度上被早期随机选择决定。如果早期选择了较绕的路径,后来的新节点并不必然能够自动纠正整条旧分支的成本。因此,虽然树会越来越大,基础RRT的最佳路径成本,并没有相应的通用渐近最优保证。

2011年的理论突破

2011年,Sertac Karaman 与 Emilio Frazzoli 在《The International Journal of Robotics Research》发表:Sampling-based Algorithms for Optimal Motion Planning《最优运动规划的采样式算法》。论文分析了PRM与RRT等随机规划方法的渐近性质。他们指出,在相应条件下,普通RRT的路径成本可能收敛到一个非最优值。随后,他们提出了具有渐近最优性保证的新算法:PRM*。以及:RRT*。[Sertac Karaman, Emilio Frazzoli, 2011]星号不只是名称装饰。RRT*与普通RRT之间,存在很重要的算法差别。

第一个改进:不一定选择最近节点作为父节点

普通RRT会选择:qnearq_{\mathrm{near}}作为新增节点的父节点。但RRT*还会检查新节点附近的其他节点。假设新节点是C。附近有A、B、D三个候选父节点。虽然B在几何上距离C最近,但从起点到B可能已经走了一条很长的绕路。这时,经过A连接C,总路径成本可能反而更低。所以,RRT*不仅考虑:d(B,C)d(B,C)还考虑:g(B)+c(B,C)\boxed{ g(B)+c(B,C) }其中:g(B)g(B)表示从起点沿当前树抵达B的成本。c(B,C)c(B,C)表示从B到C的局部连接成本。算法在附近可行候选中,选择能够提供最低总成本的父节点。

第二个改进:重新连接附近的旧节点

更有意思的是,RRT*还会反过来检查:新节点C,是否能够帮助附近的其他旧节点降低成本?如果某个旧节点D原来走一条较长路线:S→⋯→DS\rightarrow\cdots\rightarrow D现在却能够通过:S→⋯→C→DS\rightarrow\cdots\rightarrow C\rightarrow D获得更低成本,并且这段新连接合法,那么算法就可以修改D的父节点。这项操作叫作:Rewiring——重新布线,或重新连接。
**图45-8:普通RRT与RRT*的成本改进**
普通RRT可能长期保留早期的绕行父节点连接
RRT*发现更便宜的有效连接时,可以修改父节点

什么叫作渐近最优性?

设:CNC_N是算法使用 NN 个采样后,得到的最佳可行路径成本。真正的最优成本记为:C∗C^*渐近最优性的核心目标是:随着采样数量无限增加,最佳解的成本趋近于真正的最优值。在RRT*的相关数学条件下,可以得到几乎必然收敛的结论:P(lim⁡N→∞CN=C∗)=1\boxed{ P\left( \lim_{N\to\infty}C_N=C^* \right)=1 }其中,概率1表示“几乎必然”(Almost Surely)。这不等于每一个有限时刻都已经得到最优解。也不保证给定1000次采样之后,路径长度就一定在最优值的1%以内。它是一项极限性质。并且依赖采样、连接半径、成本函数、空间结构等相关技术条件。

2011年的结果意味着什么?

它让我们能够把两种目标明确区分。
**图45-9:RRT与RRT*的核心差异**
性质基础RRTRRT*
随机采样扩展是是
寻找可行路径是是
比较邻近父节点成本通常不做是
重新连接旧节点通常不做是
通用渐近最优性不保证相应条件下保证
这一变化很有象征意义。1998年,RRT解决的是:怎样在复杂空间中高效探索并发现可行路径?2011年,RRT*进一步研究:怎样让一棵持续增长的树,不断改善自己已经找到的路线?从找到可行解,到逐渐逼近最优解。这正是机器人运动规划研究的重要发展方向之一。但在实际项目中,也不能直接认为RRT*永远优于RRT。如果任务强调尽快获得一条可行路径,基础RRT或RRT-Connect可能更适合某些场景。如果任务允许持续规划、更加重视路径质量,RRT*及其改进方法则可能具有优势。算法选择始终取决于真正需要优化的目标。

第十幕:真正写出RRT——让Python在障碍物之间寻找道路

基础几何RRT的程序输出:搜索树包含大量探索分支,最终路径则由目标节点沿父节点关系回溯得到。现在,我们来完成真正的编程练习。在前面的互动实验里,你已经观察了RRT的五项基本动作。现在,我们将它们写成一段可以独立运行的 Python 程序。不使用专业机器人规划库。只使用标准库来完成RRT计算。使用 Matplotlib 将搜索过程画出来。

第一步:定义地图和障碍物

我们使用一个:100×100100\times100的二维空间。起点:S=(9,50)S=(9,50)目标:G=(92,50)G=(92,50)中央放置一个矩形障碍物:36≤x≤6536\le x\le6526≤y≤7326\le y\le73机器人需要从障碍物的上方或者下方绕行。为了避免机器人过分贴近障碍物,我们还引入1个单位的保守安全裕量。这相当于让程序把矩形障碍物适当向外扩展,再进行线段碰撞检查。

第二步:完整程序

下面是一份可以直接运行的基础RRT教学实现。
python

import math
import random

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle


START = (9.0, 50.0)
GOAL = (92.0, 50.0)

WORLD = (0.0, 0.0, 100.0, 100.0)

# 障碍物:(xmin, ymin, xmax, ymax)
OBSTACLES = [
    (36.0, 26.0, 65.0, 73.0)
]

ROBOT_RADIUS = 1.0
STEP_SIZE = 6.0
GOAL_BIAS = 0.10
MAX_ITERATIONS = 5000


def distance(a, b):
    return math.hypot(
        a[0] - b[0],
        a[1] - b[1]
    )


def steer(start, target, step_size):
    """从start向target推进不超过step_size的距离。"""

    d = distance(start, target)

    if d == 0:
        return start

    scale = min(1.0, step_size / d)

    return (
        start[0] + scale * (target[0] - start[0]),
        start[1] + scale * (target[1] - start[1])
    )


def segment_intersects_box(a, b, rect, margin=0.0):
    """
    用参数区间裁剪法判断线段是否与矩形相交。

    将矩形向四周扩展margin,提供保守安全裕量。
    此法以轴对齐外扩矩形近似机器人圆形占据区域。
    """

    xmin, ymin, xmax, ymax = rect

    xmin -= margin
    ymin -= margin
    xmax += margin
    ymax += margin

    dx = b[0] - a[0]
    dy = b[1] - a[1]

    tmin, tmax = 0.0, 1.0

    for pos, delta, lower, upper in [
        (a[0], dx, xmin, xmax),
        (a[1], dy, ymin, ymax)
    ]:
        if abs(delta) < 1e-12:
            if pos < lower or pos > upper:
                return False
            continue

        t1 = (lower - pos) / delta
        t2 = (upper - pos) / delta

        if t1 > t2:
            t1, t2 = t2, t1

        tmin = max(tmin, t1)
        tmax = min(tmax, t2)

        if tmin > tmax:
            return False

    return True


def collision_free(a, b):
    """检查整条候选线段是否处于允许的空间。"""

    xmin, ymin, xmax, ymax = WORLD

    for p in (a, b):
        if not (
            xmin + ROBOT_RADIUS <= p[0] <= xmax - ROBOT_RADIUS
        ):
            return False

        if not (
            ymin + ROBOT_RADIUS <= p[1] <= ymax - ROBOT_RADIUS
        ):
            return False

    for rect in OBSTACLES:
        if segment_intersects_box(
            a, b, rect, ROBOT_RADIUS
        ):
            return False

    return True


def rrt(seed=42):
    rng = random.Random(seed)

    # 每个节点:(位置, 父节点索引)
    nodes = [(START, -1)]

    for iteration in range(1, MAX_ITERATIONS + 1):

        # 1. 随机采样,偶尔直接朝目标采样
        if rng.random() < GOAL_BIAS:
            sample = GOAL
        else:
            sample = (
                rng.uniform(ROBOT_RADIUS, 100 - ROBOT_RADIUS),
                rng.uniform(ROBOT_RADIUS, 100 - ROBOT_RADIUS)
            )

        # 2. 找到最近的树节点
        nearest_idx = min(
            range(len(nodes)),
            key=lambda i: distance(nodes[i][0], sample)
        )

        nearest = nodes[nearest_idx][0]

        # 3. 向样本方向前进一小步
        new_state = steer(
            nearest, sample, STEP_SIZE
        )

        if distance(nearest, new_state) < 1e-10:
            continue

        # 4. 检查整段运动是否安全
        if not collision_free(nearest, new_state):
            continue

        # 5. 加入新节点及其父节点
        nodes.append((new_state, nearest_idx))

        new_idx = len(nodes) - 1

        # 尝试连接目标
        if (
            distance(new_state, GOAL) <= STEP_SIZE
            and collision_free(new_state, GOAL)
        ):
            nodes.append((GOAL, new_idx))
            goal_idx = len(nodes) - 1

            # 沿父节点回溯整条路径
            path = []
            idx = goal_idx

            while idx != -1:
                state, parent_idx = nodes[idx]
                path.append(state)
                idx = parent_idx

            path.reverse()

            return nodes, path, iteration

    return nodes, None, MAX_ITERATIONS


nodes, path, iterations = rrt(seed=42)

print("采样轮次:", iterations)
print("树节点数量:", len(nodes))

if path is None:
    print("本次预算内没有找到路径")
else:
    length = sum(
        distance(path[i], path[i + 1])
        for i in range(len(path) - 1)
    )

    print("可行路径长度:", round(length, 2))


# 绘制结果
fig, ax = plt.subplots(figsize=(8, 8))

for xmin, ymin, xmax, ymax in OBSTACLES:
    ax.add_patch(
        Rectangle(
            (xmin, ymin),
            xmax - xmin,
            ymax - ymin,
            alpha=0.55
        )
    )

for i, (state, parent) in enumerate(nodes):
    if parent == -1:
        continue

    p = nodes[parent][0]

    ax.plot(
        [p[0], state[0]],
        [p[1], state[1]],
        linewidth=0.7,
        alpha=0.45
    )

if path is not None:
    xs, ys = zip(*path)
    ax.plot(xs, ys, linewidth=3, label="RRT Path")

ax.scatter(*START, s=90, label="Start")
ax.scatter(*GOAL, s=90, label="Goal")

ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
ax.set_aspect("equal")
ax.set_title("Rapidly-exploring Random Tree")
ax.legend()

plt.show()

第三步:观察搜索树与最终路径之间的区别

运行之后,你会看到两种不同的线条。一种是大量细线。它们是RRT探索过程中生成的分支。另一种是加粗的路径。它由目标节点沿父节点不断回溯得到。请注意:机器人真正执行的路线,不需要走遍整棵树。树的其他分支只是探索过程中的计算结果。这是RRT与普通树遍历之间一个非常重要的区别。我们生成一棵树,并不是为了把每条树枝都走一遍。而是为了让某个分支最终能够安全连接到目标。

第四步:为什么每次扩展都需要寻找最近节点?

程序中有这样一段代码:
python
nearest_idx = min(
    range(len(nodes)),
    key=lambda i: distance(nodes[i][0], sample)
)
这是一种最直观的实现。它遍历整棵树,找到距离当前随机样本最近的节点。如果树中已有:NN个节点,那么一次最近邻查询需要:O(N)O(N)时间。如果连续生成 NN 个节点,而每次都遍历当前树,那么仅最近邻查询的累计成本就可能达到:O(N2)O(N^2)这说明:RRT使用随机采样,并不自动意味着它在计算复杂度上始终高效。它还依赖最近邻查询、碰撞检测以及状态传播等基础操作。对于低维几何空间,可以考虑使用:KD-tree。其他空间索引。或者适合具体距离度量的最近邻结构。但在很高维的空间里,许多空间索引的数据结构效率可能显著下降。而复杂的机器人状态距离,也未必能用普通欧几里得距离准确描述。因此,最近邻查询是RRT工程实现的重要研究课题之一。拉瓦莱在其教材《Planning Algorithms》中,就专门讨论了最近邻搜索对RRT性能的影响。

第五步:使用路径捷径化进一步缩短路线

我们在第40章已经学习过:在满足适当条件时,可以通过跳过不必要的中间点,缩短路径。对于基础二维RRT,我们也可以使用类似的思路。假设最终路径为:P0→P1→P2→⋯→PmP_0\rightarrow P_1\rightarrow P_2\rightarrow\cdots\rightarrow P_m如果:Pi→PjP_i\rightarrow P_j之间的直接线段无碰撞,而且:j>i+1j>i+1那么,我们就可以跳过中间若干节点。只要连接是合法的,欧几里得直线距离不会超过原有折线路径长度。因此,能够在不增加路径长度的情况下简化路线。
python

def shortcut_path(path):
    """
    针对二维点机器人进行贪心路径捷径化。

    只要能够直接连接更远的路径节点,
    就跳过中间不必要的节点。
    """

    if path is None or len(path) <= 2:
        return path

    optimized = [path[0]]
    i = 0

    while i < len(path) - 1:
        j = len(path) - 1

        while j > i + 1:
            if collision_free(path[i], path[j]):
                break
            j -= 1

        optimized.append(path[j])
        i = j

    return optimized


if path is not None:
    improved = shortcut_path(path)

    original_cost = sum(
        distance(path[i], path[i + 1])
        for i in range(len(path) - 1)
    )

    improved_cost = sum(
        distance(improved[i], improved[i + 1])
        for i in range(len(improved) - 1)
    )

    print("原始路径长度:", round(original_cost, 2))
    print("捷径化后长度:", round(improved_cost, 2))
    print("原始路径节点:", len(path))
    print("改进后节点:", len(improved))

    assert improved_cost <= original_cost + 1e-9
这段优化很有价值。但不能将它误认为RRT*。RRT*是在树的生成过程中,不断优化附近节点的连接成本。而路径捷径化,是在找到完整路径之后,对结果进行几何后处理。两种方法的工作机制不同。此外,这项捷径化只适用于当前二维自由移动点模型。如果机器人必须遵守特定转弯半径或动力学约束,那么不能直接用一条直线替代任意两段合法轨迹。还必须验证替换后的运动是否满足实际系统的控制约束。

第十一幕:从机器人手臂到虚拟现实——RRT真正改变了什么?

机械臂路径规划是采样式运动规划的重要应用领域。真实系统需要同时考虑关节限制、碰撞几何和机器人运动模型。现在,我们已经完成了从数学思想到程序实现的全部过程。但还有一个问题值得讨论。RRT为什么能够成为机器人研究中如此有影响力的一项算法?答案并不只是:它能够在二维迷宫里找到路。毕竟,二维迷宫早已有大量成熟算法。RRT真正具有长期价值的地方,是它为复杂连续状态空间提供了一种比较自然的探索方式。

应用一:多关节机械臂

对于一台七关节机械臂,配置空间可能拥有七个维度。如果我们希望用均匀网格覆盖整个空间,状态数量会迅速增长。而采样式规划方法不必提前枚举全部格点。它可以随机产生一些关节角配置。然后尝试用合法的关节运动将已有树节点扩展到新的配置。只要机器人模型提供了碰撞检测能力,就可以拒绝那些会使机械臂撞到环境的扩展。当然,在实际系统里,采样和连接还需要考虑:各关节的角度范围。自碰撞。关节速度限制。运动学约束。以及可能存在的动力学条件。因此,简单的欧几里得距离不一定能描述不同机械臂姿态之间真正的连接难度。这种问题也说明:RRT虽然结构简单,但能否真正发挥作用,很大程度上取决于其底层几何与运动学模块。

应用二:虚拟人物与动画

计算机动画中的虚拟人物同样拥有大量关节变量。采样式规划可用于寻找满足姿态和碰撞约束的动作路径。想象一个三维游戏角色。它需要伸手拿起桌上的物体。如果房间里还有其他障碍物,手臂可能需要绕行。角色的状态可以包含:肩关节。肘关节。手腕。躯干姿态。以及其他控制变量。这与真实机械臂的路径规划,具有某些相似的数学结构。RRT-Connect 的早期研究,就曾涉及面向动画的七自由度人类手臂模型,并展示复杂机械臂运动的可行路径生成。[数字对象标识符]因此,RRT并不只属于装有电机的实体机器人。它也能用于虚拟对象和运动规划相关的图形学任务。

应用三:无人车与飞行器

对于汽车或飞行器,规划问题往往需要考虑:位置。速度。朝向。转向或控制输入。这些变量共同构成更高维的状态空间。基础几何RRT当然不能自动解决全部控制问题。但将短时间的控制传播作为树扩展操作之后,就可以形成面向动力学模型的RRT变体。早期 LaValle–Kuffner 研究中的悬浮器和卫星轨迹规划,正是这一方向的典型例子。[Sage Journals]

应用四:专业运动规划软件库

今天,如果想研究这些算法,可以使用成熟的软件库。例如:Open Motion Planning Library(OMPL)。OMPL 提供了一系列采样式运动规划算法,其中包括:RRT。RRT-Connect。RRT*。以及其他几何与控制规划器。它还提供规划器选择、状态空间、碰撞有效性检查等基础设施。[ompl.kavrakilab.org]这类软件库的价值,在于它将复杂的算法工程实现封装为可复用组件。开发者可以更加专注于:机器人状态怎样表示?哪些状态有效?什么样的运动合法?应该采用哪一种距离与目标函数?不过,成熟的算法库也不能替用户自动定义正确的物理模型。如果碰撞模型漏掉了机器人某个部分,那么规划器找到的路径仍然可能在现实世界中碰撞。如果控制约束定义不准确,计算出来的运动也可能无法执行。因此,算法库可以提供计算能力,却不能代替正确的建模。

一个必须澄清的词:“未知世界”

本章标题叫作:《让机器人在未知世界中长出一棵树》。这里的“未知”,需要作一次严格区分。在最经典的RRT运动规划问题里,算法通常拥有一个已经定义的状态空间,以及判断状态或运动是否有效的机制。它不要求提前枚举全部可行状态。但这不意味着机器人可以在完全没有环境信息、没有传感器、也没有碰撞判断的情况下,自行安全穿越任何未知场景。RRT主要探索的是尚未被搜索树覆盖的配置空间,并不天然解决真实环境中的感知、建图和未知障碍发现问题。如果机器人需要在真正未知的房间里移动,就还需要:传感器感知。环境建图。定位。在线碰撞检测。以及遇到新障碍后的重新规划。RRT可以作为这些系统中的一项规划组件。但不能将它误解为一套完整的自主机器人智能系统。

与上一章A*相比,究竟应该怎样选择?

图45-10:A、PRM、RRT与RRT*的研究侧重点*
算法主要思想典型关注点
A*按启发式优先级扩展图节点在给定搜索图上寻找低成本路径
PRM采样并建立可复用的路图复杂配置空间中的路径查询
RRT从起点随机采样并扩展树尽快探索并寻找可行路径
RRT-Connect从两端生长并尝试连接适合双向连接的单次路径规划
RRT*采样生长、优化父节点与重连在条件满足时逐渐逼近最优路径
这张表不是一份简单的速度排行榜。不能说:RRT一定比A*快。RRT*一定比RRT好。或者PRM一定比RRT更适合高维空间。它们面对的输入模型、使用方式和数学保证可能并不相同。A*的优势往往体现在能够利用有效启发式的图搜索问题。RRT系列的优势则体现在某些连续、高维、具有复杂运动约束的规划问题。当问题结构发生变化时,最适合的算法也可能随之改变。这正是本系列不断强调的原则:理解一种算法,不只是记住它能够做什么,还要知道它为什么有效、依赖什么条件,以及什么时候不应该直接使用它。

尾声:有时,不必知道整张地图,也能开始寻找道路

让我们再次回到1998年。爱荷华州立大学的史蒂文·拉瓦莱,正在研究复杂系统的运动规划。在他之前,研究人员已经拥有许多重要成果。迪杰斯特拉算法能够在图上寻找最短路径。A*能够通过启发式估计,引导搜索朝着目标前进。概率路图方法,则展示了如何在连续配置空间中进行随机采样,并建立可用于路径查询的图结构。但是,一类特别困难的问题仍然存在。如果机器人的状态空间拥有很多维度。如果运动受到复杂的非完整约束。如果我们不能轻易连接任意两个状态。那么,是否还有一种更加自然的探索方法?拉瓦莱给出的答案,不是一张更加密集的网格。也不是一份更加复杂的完整路图。而是一棵树。它从起点开始。每次随机选择一个方向。从现有树中寻找合适的节点。向前伸出一段新的合法运动。成功就保留。碰撞就拒绝。然后继续。这个过程没有复杂的全局控制中心。它也不需要预先知道整个可行空间的全部连接关系。每一次新分支,都只是一项局部决定。但大量局部决定组合起来,却可能逐渐探索相当复杂的高维空间。这就是RRT最令人惊讶的地方。它不必预先构建完整的搜索图,却能够在搜索过程中生成一张足以找到可行路径的结构。

一项来自1998年的思想,怎样成为长期使用的工具?

1998年,拉瓦莱提出RRT。1999年,他与Kuffner研究随机化动力学规划。2000年,RRT-Connect展示了双向树连接的效率优势。2001年,相关研究继续扩展到更加复杂的运动模型。2011年,Karaman与Frazzoli提出RRT及PRM等方法,进一步研究采样规划的渐近最优性。这条研究道路并没有在2011年结束。后来还有大量RRT变体,面向不同的机器人、约束与成本函数。拉瓦莱本人在其研究主页上指出,RRT已经进入自主车辆、无人机、人形机器人、虚拟环境等多个领域。1999年由他与Kuffner合作发表的早期会议研究,还在2019年获得了首届 IEEE ICRA Milestone Award,用来表彰它的长期学术影响。[lavalle.pl]这是一项很有意义的历史回望。因为它说明,一项研究的价值,有时并不在发表当年就能被完全衡量。一份只有几页的技术报告,可能为后来几十年的研究建立新的基础。

从最短路,到随机树

回顾第三篇,我们已经讲述了五种看起来十分不同的几何算法。
《让机器理解空间》· 第41—45章回顾
第41章
Graham Scan:理解空间的外边界用叉积、排序和栈,从点云中寻找决定凸包的极端点。

第42章
Voronoi Diagram:理解空间的内部分割根据最近邻距离,将空间划分为许多相邻区域。

第43章
Bentley–Ottmann:发现空间中的相交关系利用事件队列与扫描线,避免检查大量无关的线段组合。

第44章
A:在已有搜索图上寻找目标*结合已经发生的路径成本与对剩余成本的估计,让搜索更有方向。

第45章
RRT:在连续空间中生成探索树通过随机采样、最近邻与合法扩展,逐步建立可行路径所需的连接。
这五章,其实构成了一条清晰的思想发展路线。首先,我们让计算机知道哪里是外部边界。接着,教会它判断空间里的最近关系。然后,让它识别几何对象之间的交点。有了这些基础,我们开始研究怎样在空间中行动。A*告诉我们:如果已经拥有一张图,怎样利用启发式信息寻找目标?RRT则进一步告诉我们:如果状态空间连续而复杂,怎样通过采样逐渐生成可以使用的搜索结构?这两种方法并非互相取代。它们只是从不同角度,解决机器在复杂世界中行动的问题。

随机性不是缺乏方法

在普通语言里,我们经常把“随机”理解成毫无计划。但是,在RRT中,随机性恰恰是一项精心设计的探索机制。它帮助算法发现原本没有访问过的空间。最近邻查询提供结构。局部扩展提供连续性。碰撞检测提供安全约束。父节点关系提供路径恢复能力。而概率完备性,则在相应条件下,为持续采样能够找到可行路径提供理论依据。因此,RRT并不是随便乱走。它是一种有结构的随机探索。这也与第二季前面关于蒙特卡洛方法的章节形成了呼应。随机性并不意味着没有数学保证。它意味着,我们可以使用概率工具,在难以穷尽的巨大空间里组织计算。但同时也要记住:概率完备,不代表有限时间内必然成功。找到可行路径,不代表找到最短路径。而一条在数学模型中无碰撞的轨迹,也不意味着它自动满足真实机器人的全部物理约束。真正可靠的算法研究,不会隐瞒这些边界。它会努力说明:在什么条件下,能够提供怎样的保证。

第三篇的最后一个问题

让我们回顾一开始的那台机器人。它站在一片复杂环境中。没有办法预先列出所有可能的运动状态。也无法直接看出哪条路径能够通向目标。但现在,它拥有了一种新的探索方式。从起点开始。伸出一根枝条。遇到障碍,就尝试另一个方向。找到空旷区域,就继续生长。随着枝条越来越多,可行空间中的连接结构也逐渐出现。最后,某一根分支抵达了目标。这棵树,也许并不优美。它可能存在许多没有用到的侧枝。最初找到的路径,也可能弯弯曲曲,并非最短。但它完成了一件非常重要的事。在没有提前枚举全部状态的情况下,它通过有限的探索,构造出了一条可以执行的道路。这是从理解空间走向在空间中行动的重要一步。而我们这五章的研究,也将告一段落。下一篇,我们将离开几何世界。进入另一个同样充满数学结构的领域。在那里,计算机所面对的不再只是点、线、区域与道路。而是信息本身。信息怎样穿越噪声?为什么一个消息即使受到干扰,仍然可能被正确恢复?一个被划伤的光盘,为什么还能读出其中的数据?我们将认识香农、汉明、里德、所罗门与维特比。研究算法怎样在不可靠的通信世界里,寻找可靠的答案。空间可能阻挡机器的道路,而噪声可能破坏机器的信息。下一段故事,将从噪声开始。

历史档案:RRT的原始研究与理论演化

本章以发明者本人保存的研究资料、原始技术报告及后续期刊论文为主要依据。
时间研究成果重要意义
1996年Kavraki 等:PRM使用随机采样建立配置空间路图
1998年6月LaValle 提出RRT按发明者本人回顾记录
1998年10月RRT技术报告 TR 98-11系统介绍快速探索随机树及其设计动机
1999年LaValle、Kuffner:随机动力学规划将RRT用于具有复杂动力学约束的轨迹规划
2000年Kuffner、LaValle:RRT-Connect使用两棵树与积极连接策略进行单次路径规划
2001年随机动力学规划扩展论文总结高维动力系统中的理论与实验
2011年Karaman、Frazzoli:RRT*研究采样式规划的渐近最优性
1998年 · RRT原始技术报告Steven M. LaValle — Rapidly-Exploring Random Trees: A New Tool for Path Planning本章最重要的一手文献。四页报告包含原始RRT算法、控制输入、最近邻、生长偏置与高维运动规划实验。

发明者本人整理的RRT研究史Steven M. LaValle — Rapidly Exploring Random Trees明确记录1998年6月提出RRT及1998年10月技术报告的时间信息,并收录后续相关研究与长期应用。

1996年 · 概率路图PRMKavraki、Švestka、Latombe、Overmars — Probabilistic Roadmaps for Path Planning in High-Dimensional Configuration Spaces用于核查RRT之前随机采样式运动规划的研究基础,避免将全部采样规划思想误归于1998年的RRT。

2000年 · RRT-ConnectKuffner & LaValle — RRT-Connect: An Efficient Approach to Single-Query Path Planning用于核查双向树连接、单次路径查询以及机械臂与虚拟人物运动规划方面的实验。

2001年 · 随机动力学规划LaValle & Kuffner — Randomized Kinodynamic Planning介绍RRT如何用于状态包含速度和动力学约束的系统,包括高维轨迹规划的理论与实验。

2011年 · RRT*与渐近最优性Karaman & Frazzoli — Sampling-based Algorithms for Optimal Motion Planning用于核查基础RRT与RRT*之间的理论差异,以及概率完备性和渐近最优性的不同含义。

原作者开放教材Steven M. LaValle — Planning Algorithms第4章讨论配置空间,第5章讨论采样式运动规划,包括RRT、PRM、最近邻、采样和碰撞检测等理论与算法细节。
**史实与教学重构说明:**本章1998年的爱荷华州研究背景、RRT原始设计目标、论文时间、后续RRT-Connect与RRT*研究,均基于相应资料。开篇的研究者内心提问属于叙事性的教学组织,不是有逐字记录的现场对话。二维地图、可操作的RRT实验室、树枝示意以及Python程序均为现代教学重构。它们演示几何RRT,不等同于1998年原始论文针对更一般动力系统提出的完整状态传播模型。所有概率完备性、最优性和算法性能结论,都应在各自的数学前提下理解。
第二季 · 第三篇正式完结

《让机器理解空间》 · 第41—45章

从凸包、沃罗诺伊图、平面扫描,到A*与RRT,我们完成了由静态几何结构走向机器人运动规划的五章历程。
下一篇 · 穿越噪声的信息

第46章:《噪声中的希望:香农如何重新定义通信》

1948年,美国贝尔实验室。一位数学家发表了一篇将改变通信科学的论文。他的名字叫克劳德·香农(Claude Shannon)。当时的工程师一直在面对一个令人头疼的问题。电信号经过漫长线路传播,会受到噪声干扰。信号可能衰减。数据可能发生错误。距离越长、传输速率越高,可靠通信可能就越困难。那么,噪声是否意味着信息必然丢失?是否存在某种数学极限,决定一条通信线路最多能够可靠传递多少信息?香农提出了一套全新的理论。他不再只研究某种特定的电报符号。而是从概率、信息与通信容量出发,重新定义了整个问题。比特。信息熵。信道容量。可靠通信的极限。这些概念,后来成为数字通信、数据压缩和纠错编码的重要基础。下一章,我们将追踪香农1948年发表的:A Mathematical Theory of Communication。从一条受到噪声干扰的通信线路开始,进入现代信息论的世界。如果机器人需要学会在复杂空间中寻找道路,那么通信系统就需要学会在噪声之中,寻找仍然能够被可靠传递的信息。
——《算法的故事》第二季 · 第45章 完

本章配图