深夜的夜市,阿明端着一碗滚烫的酸辣汤在窄巷里赶路,迎面来了同样端着汤的阿花。没有人喊“你往左我往右”,两人却像排练过一样,各自往右挪了半步,擦肩而过,汤一滴没洒。ORCA(Optimal Reciprocal Collision Avoidance,最优互惠碰撞避免)算法,就是想让机器人学会这套“夜市默契”:不靠通信、不靠中央指挥,每个智能体自己扫一眼周围人的位置和速度,判断“继续这么走会不会撞”,然后每个人都只改一点点方向——你让一半,我让一半——最后选一个“既能最快到达目标、又不会撞上任何人”的速度。这个判断在每一瞬间重复:看一眼,算一下,迈一步。整个过程决定了人群仿真里成千上万虚拟行人的流动、仓库里几百台机器人的穿梭,是“规划与控制”中最贴近日常直觉的一课。

岗位关联

对 AI 从业者而言,ORCA 是“感知—决策—执行”闭环中决策层的经典范式。理解它,你就能读懂多智能体系统、机器人导航项目的核心模块,也是从仿真走向实际部署的必经之路。

目录
  1. 可视化演示
  2. 动态避障难题
  3. 速度障碍构造
  4. 互惠避让策略
  5. 最优速度求解
  6. 参数调节方法
  7. 工程实现要点
  8. 应用与局限
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

可视化展示ORCA算法速度障碍几何构造、多智能体互惠避障策略及参数调节效果,交互调节参数体验动态障碍物避障核心原理。

本节配有交互动效(进入视口后自动加载,约 3KB)。

速度障碍几何构造 速度障碍VO为禁止速度集合的锥形区域 智能体需选择VO外速度避免碰撞 VO = {v : ∃t>0, λ(v)∈D} 速度障碍是ORCA算法的理论基础

动态避障难题

阿明第一次去夜市摆摊,打开导航 App,里面画着一条从家到街口的蓝色路线。可真的走起来,他发现地图是死的:昨天的糖葫芦摊今天换成了套圈摊;前面的大爷忽然停下拍照;一个小孩从摊位间蹿出来。如果每遇到一次变化就掏出手机重新规划路线,等他走到摊位,酸辣汤早就凉透了。

机器人在真实世界也面临同样的尴尬。传统路径规划算法(如 A*、RRT)会先在地图上算出一条“从起点到终点”的几何路线,它默认世界是静止的。可一旦出现动态障碍——别的机器人、行人、车辆——这条预规划路线立刻失效。于是研究者把决策的粒度从“走哪条路”细化到“下一步用哪个速度”:不必预演整段路,只需在每个瞬间回答“此刻往哪走、走多快”。这个思路把问题搬进了“速度空间”:它像一张以速度分量为坐标的图纸,横轴是东西方向的速度,纵轴是南北方向的速度,图纸上的每个点都代表一种走法。再叠加“不能超速”的限制,就得到一个圆形的可达速度集合。

阿明恍然大悟:导航 App 管“长远方向”,而夜市里他真正需要的是“脚下这一步的速度”——目标由导航定,步伐由“速度空间里的安全集合”定。动态世界不欢迎“背地图”的人,只奖励“边走边看”的人。

V={v∈R2  |  ∥v∥≤vmax⁡}\displaystyle \mathcal{V} = \left\{ \mathbf{v} \in \mathbb{R}^{2} \;\middle|\; \lVert \mathbf{v} \rVert \le v^{\max} \right\}
这句话在说:所有可行的速度组成一个“以原点为圆心、最大速度为半径”的圆盘,避障求解都在这张速度地图上进行。
  1. 速度平面把机器人的每个可能速度都当作平面上的一个坐标点
    R2\mathbb{R}^{2}
  2. 速度大小点到原点的距离就是“走多快”
    ∥v∥\lVert \mathbf{v} \rVert
  3. 上限约束物理极限画出一个圆,圆内才是可达速度
    ∥v∥≤vmax⁡\lVert \mathbf{v} \rVert \le v^{\max}
  4. 决策画布后面所有“找安全速度”的运算,都在这张圆盘上完成
    V\mathcal{V}

速度障碍构造

阿明在窄巷里终于迎上了阿花。要判断会不会撞,他不需要预测阿花接下来十秒的完整轨迹——那太难了,夜市里也没人按轨迹走。他只需要盯住“相对速度”:把阿花的运动“叠加”到自己身上,假设自己静止、阿花以两人的速度差朝自己冲来。这道合成速度像一支箭,箭头的方向决定了会不会相撞。

更精确地说,从阿明的位置看过去,阿花不是一个小点,而是“阿明半径 + 阿花半径”这么粗的一根柱子。从自己脚下向柱子两侧各画一条切线,两条切线之间就形成一个锥形区域——如果“相对速度箭头”落在这个锥里,继续走就会撞上。这个锥形区域在数学上叫速度障碍(Velocity Obstacle,VO),它把所有“会撞的速度”集合成了一个几何对象。

速度障碍的胖瘦完全由几何决定:两人距离越近,锥越胖;半径和越大,锥越胖。距离无限远时,锥会缩成一条线,等于没有威胁。注意,这片锥形不是“地上不能走的路”,而是“不能用这些速度走路”——同样的位置,换个速度方向照样过得去。

阿明发现这招很省脑力:不用猜阿花要去哪,只看“相对箭头”在不在锥里。箭头在锥外,放心走;箭头进了锥,就要想办法了。

VOAB={vA  |  ∠ ⁣(vA−vB,  pB−pA)≤arcsin⁡ ⁣(rA+rB∥pB−pA∥)}\displaystyle \mathcal{VO}_{A}^{B} = \left\{ \mathbf{v}_{A} \;\middle|\; \angle\!\left( \mathbf{v}_{A} - \mathbf{v}_{B},\; \mathbf{p}_{B} - \mathbf{p}_{A} \right) \le \arcsin\!\left( \frac{r_{A} + r_{B}}{\lVert \mathbf{p}_{B} - \mathbf{p}_{A} \rVert} \right) \right\}
这句话在说:把对方的速度“扣掉”后,如果相对速度的方向与“看向对方”的方向夹角很小,小到落在锥内,那么继续用当前速度走就会撞上。
  1. 相对速度把 B 的运动叠到 A 头上,两个人都在动就变成“一个人动、一个人静”
    vA−vB\mathbf{v}_{A} - \mathbf{v}_{B}
  2. 视线向量从 A 指向 B 的方向,是判断“有没有迎面而来”的基准线
    pB−pA\mathbf{p}_{B} - \mathbf{p}_{A}
  3. 夹角判定相对速度方向与视线的夹角小于半锥角,就判定为危险速度
    ∠ ⁣(vA−vB,  pB−pA)≤α\angle\!\left( \mathbf{v}_{A} - \mathbf{v}_{B},\; \mathbf{p}_{B} - \mathbf{p}_{A} \right) \le \alpha
  4. 半锥角由两人距离和安全半径之和反推,距离越近、半径越大,锥越胖
    α=arcsin⁡ ⁣(rA+rB∥pB−pA∥)\alpha = \arcsin\!\left( \frac{r_{A} + r_{B}}{\lVert \mathbf{p}_{B} - \mathbf{p}_{A} \rVert} \right)

互惠避让策略

箭头进了锥,怎么办?最简单的想法是“我全让”:阿明猛往右闪。可如果阿花照直走,两人还是可能撞成一团——而且全让的效率极低,相当于两个人里只有一个人长眼睛。ORCA 的创始人想得更妙:既然双方都长着眼睛,为什么不让每个人都承担一半?

数学上,“全让”等于把相对速度箭头完全掰出锥;“各让一半”等于只做让箭头转出危险区所需的最小改动,记为向量 u,然后阿明承担 u 的一半,阿花承担另一半。这样两人的实际轨迹都只偏了一点点,擦肩而过的动作小到几乎看不出来。

这半步妙在何处?妙在约束的形态:把一个弯曲的“危险锥”变成一条直线。速度平面被一条直线切成两半,阿明只需要保证自己的新速度落在“安全的那一半”,这一半就叫半平面(half-plane)。约束从弯曲变直线,后续求解一下子就简单了。这也是 ORCA 里 "Optimal" 的含义——避让代价对双方都最优,而不是某一方独自吃下全部成本。

夜市里两人没有喊话、没有眼神确认,却同时右移半步——因为双方都默认对方“懂规矩”。ORCA 把这种默契写进了公式:我让一半,你也让一半,谁也不吃亏。

ORCAAB={v  |  (v−(vApref+12 u))⋅n≥0}\displaystyle \mathrm{ORCA}_{A}^{B} = \left\{ \mathbf{v} \;\middle|\; \left( \mathbf{v} - \left( \mathbf{v}_{A}^{\mathrm{pref}} + \tfrac{1}{2}\,\mathbf{u} \right) \right) \cdot \mathbf{n} \ge 0 \right\}
这句话在说:把让相对速度脱险的最小改动 u 分出一半给 A,得到一个新的参考速度,A 的新速度只要落在以它为边界的安全一侧,就一定能与 B 错开。
  1. 脱险向量把相对速度“掰”出碰撞锥的最小改动,像把弓弦掰回安全方向的最短一推
    u\mathbf{u}
  2. 责任对半互惠的核心,阿明只承担一半改动,另一半留给阿花
    12 u\tfrac{1}{2}\,\mathbf{u}
  3. 新基准速度A 被期望偏转后的参考点,半平面的边界从它出发
    vApref+12 u\mathbf{v}_{A}^{\mathrm{pref}} + \tfrac{1}{2}\,\mathbf{u}
  4. 安全侧判定与法向量 n 的点积非负,意味着新速度待在“安全这一半”里
    (v−(vApref+12 u))⋅n≥0\left( \mathbf{v} - \left( \mathbf{v}_{A}^{\mathrm{pref}} + \tfrac{1}{2}\,\mathbf{u} \right) \right) \cdot \mathbf{n} \ge 0

最优速度求解

巷子不止有阿花。阿明左侧是烤串摊的油锅,右侧一个蹲着玩手机的小孩,后面还有一只横穿的小狗。每个“威胁对象”都给阿明贡献一条安全线(半平面的边界),这些线一围,就在速度图纸上圈出一块可以安全使用的区域。

问题变成:在这块可行区域里选哪个速度?选得太慢,到摊位时汤都凉了;选得离原本路线太远,可能撞进另一个摊。最自然的答案是:选“离自己想用的速度最近”的那个点。

那个“想用的速度”叫期望速度(preferred velocity),就是“没有障碍时我朝目标全速冲”的速度。找一个点,满足“所有安全线都不能越界 + 不超速”,同时离期望速度最近——这是一个典型的二维线性规划问题:约束全部是直线,目标是最短距离,现代实现里可以在毫秒级完成。

阿明把这套“圈禁区、找最近”演练了几次,发现它像夜市里走路的本能:眼睛扫一圈,心里把每个人“应该让开的方向”画成线,然后脚自动踩在“最顺又不撞”的那个速度上。

vAnew=arg⁡min⁡v∈Vfree∥v−vApref∥2,Vfree=⋂B≠AORCAAB∩D‾ ⁣(0,  vmax⁡)\displaystyle \mathbf{v}_{A}^{\mathrm{new}} = \mathop{\arg\min}_{\mathbf{v} \in \mathcal{V}_{\mathrm{free}}} \left\lVert \mathbf{v} - \mathbf{v}_{A}^{\mathrm{pref}} \right\rVert^{2}, \qquad \mathcal{V}_{\mathrm{free}} = \bigcap_{B \neq A} \mathrm{ORCA}_{A}^{B} \cap \overline{D}\!\left( \mathbf{0},\; v^{\max} \right)
这句话在说:把所有邻居的“安全一半”与速度圆盘取交集,得到可行域,然后在可行域里找离期望速度最近的那个速度,作为这一瞬间真正执行的速度。
  1. 期望速度没有障碍时,朝目标全速走的“理想步伐”
    vApref\mathbf{v}_{A}^{\mathrm{pref}}
  2. 可行域所有邻居半平面与速度圆盘的交集,通常是一个凸多边形
    Vfree=⋂B≠AORCAAB∩D‾(0,vmax⁡)\mathcal{V}_{\mathrm{free}} = \bigcap\nolimits_{B \neq A} \mathrm{ORCA}_{A}^{B} \cap \overline{D}(\mathbf{0}, v^{\max})
  3. 最近点目标在可行域里找离理想步伐最近的速度,安全与效率的平衡点
    min⁡v∈Vfree∥v−vApref∥2\min_{\mathbf{v} \in \mathcal{V}_{\mathrm{free}}} \left\lVert \mathbf{v} - \mathbf{v}_{A}^{\mathrm{pref}} \right\rVert^{2}
  4. 线性规划约束全是直线、目标是最短距离,这类 2D 问题求解极快
    arg⁡min⁡\arg\min

参数调节方法

阿明后来把另一个摊主也教会了这套避让法,却发现各人“性格”不同:有人远远看到人就绕开,走得四平八稳;有人贴着别人肩膀滑过去,惊险但快。差别在哪?在于三个旋钮。

第一个旋钮是时间视界 τ:只看未来几秒内会撞上的威胁。τ 大,30 米外的人也算进去,走得像过雷区,保守但绕路;τ 小,临到面前才躲,反应快但风险高。第二个旋钮是安全半径:在两人半径和之上额外加的余量 δ,相当于“礼貌距离”,留得越宽,走得越稳,路也越绕。第三个是最大速度,管体能上限。

工程上 τ 还直接决定了邻居搜索范围:只有“双方以最大速度相向而行、在 τ 秒内能碰面”的对象,才值得为它建约束。超出这个范围的智能体现在根本撞不上,理它纯属浪费算力。

阿明总结出一句口诀:敢闯夜市不是眼睛多尖,而是“看多远、留多宽、走多快”三个旋钮调得合适。调得太怂,汤凉了;调得太野,汤洒了。

α=arcsin⁡ ⁣(rA+rB+δ∥pB−pA∥),∥pB−pA∥≤(vAmax⁡+vBmax⁡)τ\displaystyle \alpha = \arcsin\!\left( \frac{r_{A} + r_{B} + \delta}{\lVert \mathbf{p}_{B} - \mathbf{p}_{A} \rVert} \right), \qquad \lVert \mathbf{p}_{B} - \mathbf{p}_{A} \rVert \le \left( v_{A}^{\max} + v_{B}^{\max} \right) \tau
这两式连起来看:先由“距离和安全间距”算碰撞锥的半角;再由时间视界和双方最大速度算出“真正需要警惕的邻居范围”。
  1. 安全间距把两人身体半径与额外余量加起来,等于把“礼貌距离”编码进了几何
    rA+rB+δr_{A} + r_{B} + \delta
  2. 锥角反推间距在总距离中占比越大,锥角越大,避让越早越保守
    arcsin⁡ ⁣(rA+rB+δ∥pB−pA∥)\arcsin\!\left( \frac{r_{A} + r_{B} + \delta}{\lVert \mathbf{p}_{B} - \mathbf{p}_{A} \rVert} \right)
  3. 搜索半径时间视界乘上双方最大速度,圈出“眼下真有威胁”的距离范围
    (vAmax⁡+vBmax⁡)τ\left( v_{A}^{\max} + v_{B}^{\max} \right) \tau
  4. 调参旋钮τ 管“看多远”,δ 管“留多宽”,v^max 管“能多快”
    τ, δ, vmax⁡\tau,\ \delta,\ v^{\max}

工程实现要点

避让规则想清楚了,还要能跑起来。阿明在夜市里走十米,眼睛扫了不下几十圈:看看谁靠近了,心里给每个人画个锥,把所有人的安全线叠在一起,选个速度,迈步。下一秒,世界变了,重来。

工程上这就是逐帧循环:感知(找邻居)→ 建锥(算 VO)→ 合并约束(所有半平面取交集)→ 解 LP(选最优速度)→ 更新位置(积分)。为了不卡顿,优化的重中之重是“邻居搜索”:用空间网格或哈希把世界分块,只查身边一小圈,而不是遍历全图所有对象。RVO2 这类成熟库就是靠这个技巧,把上千个智能体的仿真跑进了实时帧率。

每一步的数学非常简单:先用最优速度公式算出本帧速度,再用它推进位置。所谓“滚动时域”,就是永远只规划眼前一小步,世界变了就重算,绝不闭眼按预演走。

这套循环的本质是“滚动时域”思维:夜市的路况每秒钟都在变,但阿明只要保证“这一秒的速度是安全的”,走完全程自然安全。

vt+1=vtnew,pt+1=pt+vt+1 Δt\displaystyle \mathbf{v}_{t+1} = \mathbf{v}_{t}^{\mathrm{new}}, \qquad \mathbf{p}_{t+1} = \mathbf{p}_{t} + \mathbf{v}_{t+1}\,\Delta t
这句话在说:把每次解出的最优速度作为这一帧的执行速度,位置按速度乘以步长向前推进,然后进入下一帧,循环往复。
  1. 每帧求解每个控制周期都重新解一次“最优速度”,而不是用上一次的结果
    vt+1=vtnew\mathbf{v}_{t+1} = \mathbf{v}_{t}^{\mathrm{new}}
  2. 位置积分用当前速度把位置往前推一小步,速度是“怎么走”,位置是“走到哪”
    pt+1=pt+vt+1 Δt\mathbf{p}_{t+1} = \mathbf{p}_{t} + \mathbf{v}_{t+1}\,\Delta t
  3. 步长与帧率或控制频率配合,一般取 10~100 毫秒一个周期
    Δt\Delta t
  4. 滚动时域永远只规划眼前一小步,路况变了就重算,这就是“边走边看”
    t→t+1t \to t+1

应用与局限

阿明的默契法则,从两个人扩展到了五百人的庙会:所有人都不说话,人群却像鱼群一样自动分流、绕行——这就是人群仿真。把“人”换成仓库里的几百台送餐机器人,同一套“各让一半”让它们在货架间互不撞车,不需要中央调度逐帧指挥。

但默契也有失灵时刻。如果有个醉汉横冲直撞(不合作障碍),你让一半他补过来,照样撞上;如果巷口只容一人通过,两人都各让一半,可能僵在门前谁也没进去——这叫死锁。ORCA 是局部算法,它看不见全局,在 U 形墙、迷宫里会原地打转。它能保证“你让、我也让”时双方不错碰,却不能保证“你无论怎样都会到达目的地”。

这个“默契假设”在公式里藏得很深:B 的新速度也按“减去一半改动”来计算。只有当双方都遵守同一协议时,等式才成立。遇到墙、车、不看路的行人,这一假设就破裂了。

所以工程上 ORCA 从来不是孤军奋战:全局规划负责“去哪”,ORCA 负责“别撞”,再加上急停逻辑兜底。正如阿明的智慧是“看路”而不是“背地图”——看路的本事再大,也得先知道要去哪。

vBnew=vBpref−12 u\displaystyle \mathbf{v}_{B}^{\mathrm{new}} = \mathbf{v}_{B}^{\mathrm{pref}} - \tfrac{1}{2}\,\mathbf{u}
这句话在说:B 也按同样的规则让出另一半,这是 ORCA 互惠性的完整表达;一旦 B 不配合,整个避让保证就不再成立。
  1. 另一边的让步B 也承担一半责任,且方向与 A 相反
    −12 u-\tfrac{1}{2}\,\mathbf{u}
  2. 隐含协议公式成立的前提,是双方都按 ORCA 规则行动
    vBpref, u\mathbf{v}_{B}^{\mathrm{pref}},\ \mathbf{u}
  3. 假设破裂遇到墙、车辆等不合作物体,等式不再成立,需要额外兜底
    vBnew≠vBpref−12 u\mathbf{v}_{B}^{\mathrm{new}} \neq \mathbf{v}_{B}^{\mathrm{pref}} - \tfrac{1}{2}\,\mathbf{u}

AI 应用场景

游戏与 VR 人群仿真

阶段:决策与导航(局部避障层)

解决的问题:传统人群仿真让每个 NPC“照剧本走”,遇到动态行人只能排队等待或瞬移穿模,真实感差。用 ORCA 后,每个 NPC 实时看邻居、算速度障碍、各让一半,无需集中调度就能呈现自然流动的人群。

具体案例:某 VR 消防疏散演练系统用 RVO2 库驱动 500 名虚拟行人。疏散命令下达后,人群在烟雾与障碍物之间自动避让、分流、绕行,全程帧率稳定在 60 fps,训练者看到的拥堵与绕行模式与真实疏散高度一致。

仓储机器人集群导航

阶段:规划与控制(局部运动层)

解决的问题:传统 AGV 靠磁条或统一调度,增加车辆就要重铺场地,调度系统一旦宕机整场停摆。采用“全局任务规划器 + ORCA 局部避让”的分层架构后,机器人彼此不通信也能互让,系统鲁棒性大幅提升。

具体案例:某电商仓库部署 300 台潜伏式 AGV。顶层系统只下发“去哪个货架取哪个料箱”的目标,底层 ORCA 在交叉路口实时避让,车辆不再需要“红绿灯式”的排队等待,吞吐量提升约 35%。

无人机集群编队避障

阶段:决策与导航(机间安全层)

解决的问题:无人机集群执行灯光秀或巡检时,位置偏差、阵风扰动会造成机间碰撞;集中调度在大机群下延迟高、风险大。ORCA 作为机间安全层,让每架无人机用自己的感知估计邻居速度并实时微调。

具体案例:某 500 架无人机编队项目,在预设航点之外叠加 ORCA 避让层。当一架无人机因气流偏离预定位置时,相邻几架自动各让一半,编队整体在 0.3 秒内恢复队形,全程零碰撞。

Python 演示

python84 行
import math  class Agent:    def __init__(self, x, y, gx, gy, radius, name, color):        self.pos = [float(x), float(y)]        self.goal = [float(gx), float(gy)]        self.vel = [0.0, 0.0]        self.radius = radius        self.name = name        self.color = color        self.max_speed = 1.2  def norm_dir(v):    length = math.hypot(v[0], v[1])    if length < 1e-9:        return [0.0, 0.0]    return [v[0] / length, v[1] / length]  def wrap_angle(a):    while a > math.pi:        a -= 2 * math.pi    while a < -math.pi:        a += 2 * math.pi    return a  def preferred_velocity(agent):    toward = [agent.goal[0] - agent.pos[0], agent.goal[1] - agent.pos[1]]    dx, dy = norm_dir(toward)    dist = math.hypot(toward[0], toward[1])    speed = min(agent.max_speed, dist)    return [dx * speed, dy * speed]  def orca_pair(a, b):    diff = [b.pos[0] - a.pos[0], b.pos[1] - a.pos[1]]    dist = math.hypot(diff[0], diff[1])    if dist < 1e-9:        return False    sight_angle = math.atan2(diff[1], diff[0])    cone_half = math.asin(min(0.95, (a.radius + b.radius + 0.1) / dist))    rel_v = [a.vel[0] - b.vel[0], a.vel[1] - b.vel[1]]    rel_angle = math.atan2(rel_v[1], rel_v[0])    if abs(wrap_angle(rel_angle - sight_angle)) >= cone_half:        return False    target_angle = sight_angle + cone_half if wrap_angle(rel_angle - sight_angle) > 0 else sight_angle - cone_half    speed = math.hypot(rel_v[0], rel_v[1])    new_rel = [math.cos(target_angle) * speed, math.sin(target_angle) * speed]    u = [new_rel[0] - rel_v[0], new_rel[1] - rel_v[1]]    a.vel[0] += u[0] / 2    a.vel[1] += u[1] / 2    b.vel[0] -= u[0] / 2    b.vel[1] -= u[1] / 2    return True  def simulate(steps=120, dt=0.1):    a = Agent(0.0, 0.0, 10.0, 0.0, 0.5, "A", "indigo")    b = Agent(10.0, 0.0, 0.0, 0.0, 0.5, "B", "green")    hit = False    for step in range(steps):        a.vel = preferred_velocity(a)        b.vel = preferred_velocity(b)        orca_pair(a, b)        a.pos[0] += a.vel[0] * dt        a.pos[1] += a.vel[1] * dt        b.pos[0] += b.vel[0] * dt        b.pos[1] += b.vel[1] * dt        gap = math.hypot(b.pos[0] - a.pos[0], b.pos[1] - a.pos[1])        if step % 20 == 0:            print("step{:>3d} | A=({:5.2f},{:5.2f}) | B=({:5.2f},{:5.2f}) | gap={:4.2f}".format(                step, a.pos[0], a.pos[1], b.pos[0], b.pos[1], gap))        if gap < a.radius + b.radius:            hit = True            break    print("结果:", "发生碰撞!" if hit else "互惠避让成功, 两智能体安全擦肩而过")    print("最终位置: A =", a.pos, ", B =", b.pos)  if __name__ == "__main__":    simulate()
目的:用几十行小模拟复现 ORCA 的核心——两个智能体迎面相遇时,各自按“各让一半”调整速度,全程最小间距始终大于半径和,成功擦肩而过。
  1. cone_half = math.asin(min(0.95, (a.radius + b.radius + 0.1) / dist))算出碰撞锥半角:距离越近、安全半径越大,锥越胖
  2. rel_v = [a.vel[0] - b.vel[0], a.vel[1] - b.vel[1]]计算相对速度,把“两人都在动”变成“一人动一人静”
  3. if abs(wrap_angle(rel_angle - sight_angle)) >= cone_half:判断“相对箭头”是否落在危险锥内,不在锥内就不需要避让
  4. target_angle = sight_angle + cone_half if ... else sight_angle - cone_half把相对速度方向“掰”到锥边界,这是最小改动
  5. u = [new_rel[0] - rel_v[0], new_rel[1] - rel_v[1]]求出让相对速度脱险的最小改动向量
  6. a.vel[0] += u[0] / 2` 与 `b.vel[0] -= u[0] / 2互惠核心:两人各承担一半改动
  7. gap = math.hypot(b.pos[0] - a.pos[0], b.pos[1] - a.pos[1])每步检查实际间距,验证是否真的没撞

常见误区

误区我以为 VO 圆锥是物理空间里的一片禁区,规划路径时要专门绕过那片红色扇形,就像绕开一堵墙一样。

正解VO 住在“速度空间”里,它的横纵坐标是速度分量而不是位置坐标。它回答的是“用什么速度会撞”,不是“哪里不能走”。同一个位置,只要换一个安全速度,照样过得去。就像夜市里两人迎面,危险的不是“你站的地方”,而是“你现在这个走法”。

为何常见:图形画在两人之间呈锥形,视觉上太像地图上的障碍区,让人误以为它在物理空间里。

误区把时间视界 τ 设得很大,把所有未来可能碰撞都考虑进来,应该最安全、最稳妥。

正解τ 太大意味着几十米外有人稍微偏一下你都要绕,路径极端保守,可能走走停停甚至死锁;τ 太小又来不及反应。τ 是“预见力”与“果断力”的平衡,工程上通常取 1~5 秒,并不是越大越好。

为何常见:直觉上“想得越远越稳”,却忽略了预测越远越不可靠、约束越多路越绕这个反效果。

误区只要用了 ORCA,机器人就再也不会发生碰撞了,可以放心大胆地跑。

正解ORCA 的保证建立在“所有智能体都按同一互惠规则行动”之上。换成不合作的障碍——静止的墙、不看路的行人、突然加速的车辆——互惠假设破裂,照样会撞。生产中常把 ORCA 当作“局部安全层”,配合全局规划与急停逻辑一起使用。

为何常见:演示视频里全是规规矩矩的智能体,协作得过于完美,给人一种“万能”的错觉。

误区有了 ORCA,就不需要 A*、RRT 这些全局规划了,机器人自己就能找到路。

正解ORCA 只解决“当前这一小步怎么迈”,不回答“往哪个方向最终能到目的地”。它在 U 形墙、迷宫等凹形障碍前会原地打转(局部最优),必须靠全局规划提供目标方向和期望速度。两者是“长远路线 + 脚下灵活”的分工,谁也替代不了谁。

为何常见:ORCA 本身就能让两个小球互相躲闪,看起来像“自己会走路”,掩盖了它依赖目标输入这一事实。

面试题

Q1. ORCA 中“互惠”(reciprocal)最核心的含义是?

单选题 · easy

  • A.智能体之间需要频繁通信
  • B.双方各承担一半避让责任← 正确答案
  • C.由中央服务器统一调度
  • D.跟随前一个智能体轨迹

答案:B。ORCA 的“互惠”指两个面临碰撞的智能体各自承担一半避让责任,即把相对速度的最小改动 u 对半分摊。它恰恰不需要通信(A 错),也不是集中式调度(C 错)或队列跟随(D 错),而是“你我各让半步”的对等协作。

Q2. 速度障碍(VO)描述的是?

单选题 · easy

  • A.物理空间中不可通行的区域
  • B.会导致碰撞的速度的集合← 正确答案
  • C.地图上的障碍物轮廓信息
  • D.机器人刹车距离的计算值

答案:B。VO 是速度空间里的危险集合:只要 A 的速度落在集合内,就会在时间视界内与 B 相撞。A 把它当物理禁区是常见误解;C 是静态地图信息;D 是运动学估算,都与 VO 的定义不符。

Q3. 碰撞锥半角 α 主要由哪两项决定?

单选题 · easy

  • A.双方距离与安全半径之和← 正确答案
  • B.机器人最大加速度数值
  • C.目标点与起点间长度
  • D.邻居搜索树的深度

答案:A。α = arcsin((rA+rB)/‖pB−pA‖),距离越近、半径和越大,锥越胖,危险速度越多。加速度(B)、路径长度(C)、搜索树深度(D)都不进入这个几何公式,属于干扰项。

Q4. ORCA 半平面表示的是?

单选题 · mid

  • A.三维速度空间中的一个平面
  • B.速度平面中安全线的一侧← 正确答案
  • C.物理位置空间中的半边圆弧
  • D.加速度平面上的约束线

答案:B。从危险锥中取一条切线,把二维速度平面切成两半,安全的那一半就是半平面。因为责任对半分,A 只需待在自己这半边。A 的三维说法、C 把概念搬回位置空间、D 说成加速度空间,都搞错了所在的决策空间。

Q5. 多个邻居时,ORCA 如何选出最终速度?

单选题 · mid

  • A.直接取所有邻居速度的平均
  • B.交集中取离期望速度最近点← 正确答案
  • C.始终选择最大速度向目标冲刺
  • D.随机挑选一个邻居求解结果

答案:B。线性规划的目标正是“在可行域内最小化与期望速度的距离”,可行域是各邻居半平面与速度圆盘的交集。A 只做平均会无视避让约束;C 可能冲进危险速度;D 随机不保证安全。

Q6. 关于时间视界 τ,下列说法正确的是?

单选题 · mid

  • A.τ越大避让越保守易绕路← 正确答案
  • B.τ越小路径必然越短
  • C.τ与邻居搜索范围无关
  • D.τ越大越能保证零碰撞

答案:A。τ 把“未来几秒内可能撞”的危险速度都算进 VO,τ 越大避让越早越保守,路径更绕甚至死锁;τ 越小则临危才躲。D 错在:对不合作障碍,τ 再大也保证不了绝对安全。

Q7. 邻居搜索半径通常与哪个量直接相关?

单选题 · mid

  • A.双方最大速度乘时间视界← 正确答案
  • B.机器人自身质量多少
  • C.全局规划路径长度
  • D.目标点分布密集度

答案:A。邻居搜索只需覆盖“时间视界内双方可能接近的范围”,即 (vA^max + vB^max)·τ,超出这个范围的智能体现在撞不上,不必建约束。质量、路径长度、目标密度都与“哪些障碍当下有威胁”无关。

Q8. 下列哪种场景最容易让 ORCA 的互惠假设失效?

单选题 · hard

  • A.两个机器人面对面相遇
  • B.机器人遇到不看路的行人← 正确答案
  • C.三个机器人交叉汇聚
  • D.机器人最大速度偏小

答案:B。ORCA 假设“对方也会让一半”,行人若完全不改向,互惠分摊就不成立,可能相撞。A 与 C 中各方都按规则行动,存在可行解;D 只是速度慢,仍可按规则避让。

Q9. ORCA 属于“局部避让”,与全局规划配合时正确的认识有?

多选题 · mid

  • A.可用 ORCA 完全替代全局路径规划
  • B.全局规划负责给出目标与期望速度← 正确答案
  • C.ORCA 负责实时处理动态障碍← 正确答案
  • D.两者必须共享同一个坐标系

答案:BC。ORCA 只决定“当前这一步用多快的速度、朝哪偏一点”,需要全局规划提供目标方向(期望速度);它在 U 形墙前会陷入局部最优,替代不了全局规划,故 A 错。坐标系通常统一是工程要求,但并非 ORCA 算法的前提,D 过于绝对。

Q10. 工程实现 ORCA 时,常见的性能优化手段有?

多选题 · hard

  • A.只用邻近智能体构造约束← 正确答案
  • B.空间网格加速邻居搜索← 正确答案
  • C.对全体智能体建立约束
  • D.每帧把上一步速度当初值← 正确答案

答案:ABD。远处智能体在时间视界内撞不上,用空间网格只查近邻、只给近邻建约束,是 RVO2 等库的核心加速手段;C“全量建约束”浪费算力且无必要。D 是热启动技巧,把上一步解作为线性规划初值,能显著减少迭代次数。