ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多机器人碰撞检测测试:从仿真到实机的完整指南

多机器人碰撞检测测试:从仿真到实机的完整指南

说实话,多机器人系统的碰撞检测测试,是我这几年见过的最容易被低估的一块。单独拿一台机器人出来测,避障测试做得再好看都是常态;可一旦两三台机器人在同一个仓库里跑起来,各种稀奇古怪的相撞、卡死、互相礼让到死锁的状态就会冒出来。最讽刺的是,很多团队把大量时间花在调参上,却没有一套能回答"到底测了哪些碰撞情况、通过标准是什么、跑完能得出什么结论"的测试体系。

这篇文章想聊的,就是把多机器人碰撞检测测试这件事拆开来讲清楚:它到底在测什么,仿真层怎么做才可信,场景怎么设计才不是随机碰运气,判定标准怎么设定才不算自欺欺人,以及从仿真搬到实机时那些最典型的翻车点。适合正在做多机调度、多机协同导航,或者打算上多机项目的工程师和测试同学参考。你不需要先有完整的测试平台,跟着这套思路从零搭起也完全可行。

1. 先想明白:多机器人碰撞检测测试到底在测什么

1.1 单机避障测试的成熟套路为什么不通用

单机导航的避障测试,业内已经玩得很熟了:把静态障碍物、动态行人、狭窄通道这些场景摆好,看规划器能不能绕过去,测一下最小间距和成功率,基本就交差了。这套思路搬到多机器人系统上,第一反应自然是"把其他机器人当成会动的障碍物就行"。但我敢说,只要你在真实系统里试过一次,就会立刻放弃这个想法——其他机器人不是被动的障碍物,它们会对你做出反应,而你的反应又会被它们感知到,双方陷入一种互相猜步子的博弈。

举个最简单的例子:两台机器人正面相遇,A向左避让,B也检测到了A的动作,出于同样的避让逻辑也向左避让,结果两台车反而对上了。这种情况在单机测试里永远不会出现,因为静态障碍物不会"回应"你的动作。单机测试的有效前提是"环境状态独立于我的行为",而多机系统里这个前提根本不成立。所以多机器人碰撞检测测试的本质,是测交互,不是测几何。

1.2 把多机碰撞拆成三个层次来看

我习惯把多机碰撞问题拆成三个层次,每一层测的东西、暴露的症状都完全不同:

  • 几何层:机器人的外形表达、包围盒、膨胀半径、代价地图、碰撞形状是否准确。这个层出问题,典型症状是"明明有空隙却算成碰了",或者反过来"模型上没碰但实际车身刮了"。比如车顶突出来的天线没算进碰撞模型,仿真里永远碰不到,实机上必挂。
  • 运动层:双方未来的轨迹是否交叉、TTC(碰撞时间)是否够长、速度障碍判定对不对。这个层出问题,典型症状是"每个机器人都在避,但预测轨迹还是相交"。它考验的是对对方运动状态的预测能力,预测用的数据老一秒钟,结论就可能完全反过来。
  • 协商层:谁有路权、谁先走、仲裁规则怎么定、两边的决策协议是否一致。这个层出问题,典型症状就是相互礼让死锁、优先级冲突、谁也不肯先走。

这三个层次是按"决策链"从上往下分的。很多团队测试只盯着几何层,觉得碰撞检测就是"距离够不够",结果上了多机之后被协商层和运动层的问题打得措手不及。所以搭建测试体系之前,先把你要测的系统涉及哪几个层列清楚,再决定用例怎么设计。

1.3 组件测试、双机测试与编队测试各管一段

多机系统的测试不能只做系统级,也不能只做组件级。我的经验是把它分成四档:

  • 组件测试:局部规划器避开静态障碍、通信模块的消息格式和时间戳正确、仲裁规则的单元逻辑正确。这档测的是"每个零件没生锈"。
  • 双机测试:两台机器人之间的迎面、交叉、汇入等博弈行为。这一档性价比最高,因为碰撞本质上是一个双人事件,绝大多数碰撞逻辑问题在双机场景里就能暴露出来。
  • 小群组测试:三四台甚至五六台机器人一起跑,主要看级联效应和连锁避让。比如一台车为了避让A,结果把B的路堵死了,这种"多米诺"问题双机测不出来。
  • 全编队测试:压满系统设计规模,看调度压力下的阻塞传播、排队溢出、大面积死锁。

建议的投入比例是金字塔形:大量仿真双机用例打底,小群组和编队场景只保留最核心的几十个,实机再从中挑最温和的。很多团队反过来,一上来就拉十台真机跑全场景,既烧钱又很难定位问题,属于费力不讨好。

2. 仿真层怎么搭才足够可信:环境、通信与感知的三座大山

2.1 物理引擎失真:碰撞响应比碰撞检测更容易骗人

很多人在仿真环境里遇到"为什么碰撞检测老是误报"的困惑,排查到最后发现不是检测算法的锅,而是物理引擎的接触响应在捣乱。不同物理引擎对"两个物体互相嵌入"的处理方式差异很大:有的会强行推开、有的会产生微小穿透、有的在特定步长下会让物体直接互相穿过去。如果你用"接触响应"来反推"是否发生了碰撞",很容易被引擎的数值行为骗住。

我的做法是:碰撞检测测试里,机器人本体用干净的运动学模型,碰撞判定用独立的几何检测模块,不依赖物理引擎的回弹效果。这样测的是"决策层有没有避免碰撞",而不是"物理引擎有没有正确反弹"。但有一个细节必须较真——模型尺寸要和实机完全一致,包括外伸的支架、货架底盘、安全触边这些"隐性凸出部分"。我参与的一个仓储项目就栽在这上面,仿真里给机器人建模时偷懒只画了底盘矩形,漏了货架两侧各伸出的五厘米护栏,结果实机测试第一周就刮了两次货架,而仿真里怎么蹓跶都是绿的。

2.2 通信仿真:别用共享内存假装无线网

这是仿真环境里最大的"甜蜜陷阱"。很多团队在仿真里让所有机器人直接读写同一份全局状态,等效于零延迟、不丢包、不乱序的神级网络。在这种条件下跑出来的多机协同,参数调得再漂亮都是假的。等到实机上无线网络一抖动,全队的协同立刻失效。

正确做法是给仿真引入网络模型,而且不能只加一个简单的固定延迟。至少要覆盖几类故障:

  • 丢包:可以按比例丢,也可以定向对某台机器人单向丢包。
  • 延迟抖动:延迟不是常数,而是随时间波动,模拟无线拥塞。
  • 消息乱序:后发的消息先到,先发的消息后到,接收方按什么顺序处理。
  • 时钟偏移:每台仿真机器人的时钟有微小偏差,导致时间戳语义不一致。

我后来在仿真里给每台机器人单独维护一个消息缓存,每个消息都带时间戳,接收方按数据年龄排序,并且主动注入"过期消息"场景。这样一来,"拿到的数据到底新鲜不新鲜"就变成了可以被测试的显式条件。很多在"全局共享状态"仿真里完全测不出来的问题,在这一步原形毕露。

2.3 感知仿真:别只给激光数据加高斯噪声

感知这块的常见误区是给测距数据加上高斯噪声就以为模拟了不确定性。但多机场景里真正致命的是目标级的不确定性,也就是检测模块输出的"目标列表"本身出错,而不是单个距离值带点抖动。典型的三种故障:

  • 漏检:另一台机器人明明在视野里,检测模块这帧就是没输出它。
  • 误检:墙上的反光斑被当成了一台机器人,产生一个"幽灵目标"。
  • 目标ID跳变:同一台机器人在前后两帧里被识别成两个不同的目标,轨迹预测瞬间断裂。

这些故障在原始传感器层面很难注入,但如果你的系统里有一个"目标列表"接口,就可以在接口层做故障注入:模拟某台机器人漏检另一台两秒钟、模拟目标位置停留在一秒前、模拟一个虚假的机器人突然出现在走廊尽头。多机协调最怕的不是"测距有误差",而是"那台车我压根没看见"或者"我看到的已经是它一秒前的位置了"。感知仿真建议统一输出为"带时间戳的目标列表",故障注入层在这个列表层面动手,既干净又可控。

3. 场景设计:从随机撒花到刻意刁难的结构化用例库

3.1 先把冲突原型列全

随机撒几千个场景跑一遍,确实能发现不少意料之外的碰撞,但它有两个硬伤:一是复现难,二是覆盖不完整——没撞上只能说明"这次没撞上",不能说明"这类情况不会撞"。所以我的第一原则是:先建冲突原型库,再谈随机。

冲突原型典型场景核心控制参数
对向相遇走廊里两台车迎面而行相对速度、避让方向选择
交叉路口两条路径垂直交叉到达时间差、交叉角度
同向汇入支路汇入主干道汇入角度、主干车速
窄口通过单扇门或窄通道,只能一车通过门宽、两队调度距离
超车变道同向行驶中一侧要超越另一侧速度差、横向间距
第三方阻塞两车避让时被第三台车堵住退路第三台车位置、自身状态

每个原型都要有"基础版"和"压力版"两种:基础版本就是标准的、容易通过的场景,用来做回归基线;压力版则把参数推向极限,比如把走廊宽度收到刚好够一车通过、把交叉路口的到达时间差压到零附近,逼系统露出破绽。

3.2 多机专属的边界用例

有些场景是单机测试里完全不存在、但多机系统里极其常见的,我把它们单独拎出来重点测:

  • 完全对称的迎面死锁:两台机器人参数完全相同、速度相同、在走廊正中面对面。由于两边决策逻辑完全对称,它们可能同时向左避、同时向右避、或者同时停下来等对方,形成完美僵持。这个用例几乎是每个多机项目的必翻车项。
  • 环形互锁:四台机器人首尾相接形成一个环,每台都在等前一台让路。单看每一个局部决策都合理,全局却卡死了。这是级联问题的入门级示例。
  • 感知不对称:A能看到B,但B被货架挡住看不到A。A以为B会避让自己,B压根不知道A存在,两台车直奔同一个目标点。
  • 通信单向故障:B收得到A的消息,但A收不到B的消息。两边的"世界模型"产生分歧,仲裁规则可能因此失效。
  • 优先级冲突:调度系统给两边下了矛盾的优先级指令,两边都认为自己有路权,互不相让。

这些用例的共同点是"两边的状态不对称",而单机测试里根本没有"双方状态"这回事。设计场景库时,我会专门留一个分区存放这类不对称用例,优先级比普普通通的随机场景高得多。

3.3 参数化矩阵与覆盖率

场景库建好之后,下一步是参数化。与其生成海量完全随机的场景,不如定义几个关键维度,做有控制的组合扫描:

  • 速度比(0.5x / 1x / 2x)
  • 交角(30°/ 60° / 90° / 150°)
  • 通信延迟档位(0ms / 100ms / 300ms / 丢包5%)
  • 感知故障档位(无故障 / 漏检1秒 / 位姿延迟1秒)
  • 机器人数量(2 / 3 / 4 / 6)

参数组合会爆炸,所以要控制:先固定一组默认参数,单维扫描一遍;再挑出最有价值的十到二十个组合做双维扫描;最后加一批随机种子做压力翻腾。每次跑测试固定用同一批随机种子,保证可复现。另外我强烈建议留一个"未知集"——一批随机生成的、从不入库的新场景,专门用来检测测试库是否过拟合。如果你在未知集上的失败率明显高于入库场景,说明你的系统已经把库里场景"背下来了",但还没学会处理新情况。

4. 判定指标与通过口径:别让"全绿"骗了你

4.1 一套可用的指标组合

碰撞检测测试的成绩单不能只有"撞了没撞"这一个二进制结果。我实际用的指标组合是这样:

指标计算口径主要回答的问题
碰撞次数几何模型发生接触的次数最基本的及格线
最小间距任意两台机器人之间的最小几何距离本次测试有多"悬"
TTC最低值按当前速度和轨迹推算的碰撞时间最小值系统是否在危险的边缘试探
协商避让触发次数因对方存在而改变原规划的次数避让行为是否过度或不足
死锁/停滞累计时长系统无进展的总时间系统是否会"安全地卡死"
任务完成率在规定时间内到达目标点的比例安全是否以效率为代价

只看碰撞次数是最容易骗自己的。我见过一个项目,测了三千个仿真场景零碰撞,团队很高兴,后来发现这个系统的策略是"只要检测到任何其他机器人,离老远就原地停下等人走"。零碰撞是真的,任务完成率接近零也是真的。一个场景跑了十分钟,两台车隔着八米互相观望,系统宣布"安全通过"——这种通过毫无意义。

4.2 通过口径:零碰撞只是及格线

我推荐的通过口径分三档:

  • 第一档(硬指标):全程无几何接触,无危险接近(最小间距大于安全阈值,比如车身宽度的一半),TTC最低值大于设定阈值(比如0.5秒)。
  • 第二档(软指标):发生危险接近或TTC过低时,系统有合理响应,比如主动减速、急停、重新规划,且没有造成二次危险。这类事件不能算测试失败,但必须人工审查,确认当时的决策链路是合理的。
  • 第三档(效率指标):完成率、绕行率、死锁时长在可接受的范围内。

另外要注意阈值不能一刀切。窄通道场景本来就要求低间距通过,你给它设一个0.5米的最小间距指标,等于逼着系统永远不敢进窄门。所以我的习惯是:安全阈值按场景类型分档,狭窄类场景多盯TTC和通过姿态,开阔类场景多盯最小间距。

4.3 回归基线与随机种子

多机系统的代码一直在改,调度策略在改、避让参数在改、通信协议也在改。没有回归基线,你根本不知道这次改动是变好了还是变坏了。我建议把入库场景集做成回归基线,每次改动后全量重跑,然后对比指标分布,而不是只对比"通过了几个"。

随机种子管理也是硬功夫。仿真必须固定种子,否则同一个场景每次跑出来的行为都不一样,排查问题的时候你根本分不清"这次失败是新问题还是老问题"。我的工具里会给每次测试生成一个配置存档:场景库版本、随机种子、代码版本、参数文件哈希,全部打成一个标签。这样任何一次失败都能精确复现,而不是靠"再跑一次碰运气"。

5. 实机迁移:仿真全绿、实机全红的典型翻车链

5.1 定位漂移与坐标系误差

仿真里的机器人位姿是直接从仿真器读的,精确到毫米。实机完全不是这么回事——轮子打滑、里程计漂移、激光匹配误差,随便就是几厘米到十几厘米的误差。一台宽度三十厘米的机器人,定位漂移十厘米,名义间距零点四米,实际可能只剩零点二米,安全余量直接腰斩。

多机系统还会放大这个问题。如果A把B的位姿通过坐标变换放到自己的局部坐标再做决策,每一次变换都会叠加误差;如果两个机器人各自的坐标框架本身还有偏差,那"B在哪"这件事在A看来就是一个带误差估计的模糊概念。我在实机上吃过这个亏之后,强制定了两条规矩:第一,所有机器人上报的位姿统一转换到全局坐标系,不在局部坐标里做跨机决策;第二,每个位姿消息必须带采样时间戳,决策时按数据年龄动态扩大对对方的膨胀半径——数据越老,膨胀越大。这相当于把"不确定性"和"数据陈旧度"直接换算成安全距离。

5.2 通信的假同步陷阱

实机上的无线通信不像仿真里那么听话。最隐蔽的问题是"假同步"——两边看起来都在跑同一套协同逻辑,实际上由于时钟偏移,A认为的"现在"和B认为的"现在"根本对不上,导致两边对同一事件的先后顺序判断完全相反。举个具体场景:A发送"我已通过路口"的消息,B在两百毫秒后才收到,但B的时钟比A慢了五十毫秒,于是B认为这个消息是在它自己决定进入路口之后才发生的,逻辑上产生了先后矛盾。

解决办法分几步:首先,消息里必须携带数据采集时刻,而不是发送时刻,这能避免"发送延迟被误判为感知延迟";其次,接收方要对消息做陈旧度检查,超过一定年龄的数据直接丢弃或标记为过期,决策模块必须明确处理"没有新数据"的情况;最后,在仿真里就必须把时钟偏移建模进去,不要等到实机才第一次面对它。

5.3 用少量真机做大量测试

真机动辄上万块,谁都不可能买三十台来做测试。更现实的方案是混合仿真:场地里跑N台真机,同时注入M台虚拟机器人,虚拟机器人的位姿通过目标列表的形式喂给真机的感知模块,真机的行为也通过仿真器的反馈回路影响虚拟机器人。这样用两到三台真机就能模拟出接近全编队的对抗压力。

但混合仿真有它的顺序讲究。我的建议是从纯仿真出发,逐步增加真机比例,每一步都要确认前一步的结果稳定再往前走。真机数量不清零的情况下,先在低速下验证安全兜底,再把速度往上提。场地侧必须有围栏、急停按钮、限定活动区域,至少一位安全员在场地内盯着,所有可能出危险的参数先在仿真里摸清边界。从仿真已验证的场景里挑"温和版"先上实机,千万别把压力版的极限场景直接搬到实机——那是拿设备当鞭炮放。

6. 高频踩坑案例与一条可复用的排查链路

6.1 死锁不是碰撞,却是更贵的失败

我在前面提过,很多团队把"零碰撞"当作成功,但多机系统里真正的隐形杀手是死锁和活锁。死锁是两台车面对面僵住,谁都不动;活锁是两边都在动,但一直在绕圈,永远到不了目的地。这些状态不触发任何碰撞告警,看起来安全得很,其实整个系统已经瘫痪了。

这类问题的排查有一个很反直觉的点:它经常不是避让策略的参数问题,而是仲裁协议的逻辑问题。比如两台的优先级判断依赖对方上报的位姿,而位姿在走廊里因为退化特征抖动了一下,导致优先级反复翻转,两边的决策也跟着反复翻转。建议在测试里加一个"停滞检测"模块:任何机器人如果在规定时间内位姿变化小于阈值,且视野里存在其他机器人,就触发告警事件。这个事件要单独统计,不混在碰撞指标里。

6.2 误报与漏报的定位方法

碰撞检测系统本身也是会出错的,而且误报和漏报的排查路径完全不同。误报常出在几何层:碰撞形状参数错、坐标系用错、把对方机器人当成静态障碍来算距离。漏报则常出在数据链路:决策模块拿到的是过期位姿,检查器判定"安全通过"的时候,对方其实已经移动到了碰撞位置。

定位这类问题,靠眼神盯日志是盯不出来的。我的做法是把每个机器人的日志按时间线拆成三段:感知输出、规划器状态、协商协议决策。每一条都带上统一的时钟戳。碰撞或者危险接近发生时,回放决策那一刻三个模块各自的状态,重点关注"规划器做决策那一刻,它看到的对方位姿是老多久以前的"。我定位过的最典型的一类问题就是"规划逻辑完全正确,但它输入的是四百毫秒之前的旧数据",这在共享内存仿真里永远测不出来,因为那里的数据永远是新鲜的。

6.3 排查链路五步法

踩的坑多了以后,我总结出五步排查法,现在遇到什么诡异碰撞或者死锁,都按这个顺序走:

  1. 复现:同一个随机种子、同一个场景参数、同一个代码版本,先把问题稳定复现出来。复现不了的问题先不修,改成先补日志。
  2. 最小化:把场景缩到最简单的形式,通常是两台机器人在直线走廊里对向或同向行驶,去掉第三方干扰、去掉复杂地形,直到能用最小场景稳定触发问题。
  3. 分层排除:按几何层、运动层、协商层、通信层、感知层的顺序逐个排除。每一层单测一遍,确认是这一层的问题再深入,千万不要跨层猜。
  4. 根因定位:定位到具体是哪个模块、哪个条件分支、哪条数据链路出了问题。
  5. 修复并入库回归:修完之后,把最小化场景升级成一个正式入库的用例加进回归基线,防止以后复发。

这套链路看起来简单,但贵在纪律性。我见过太多团队跳过了第1和第2步,直接对着第3步的经验猜原因,结果修了三天发现改的是没问题的模块,回头才看见真正的原因是通信掉了一帧。

6.4 日志与回放是我最不后悔的投入

最后说一个听起来不性感但性价比极高的建议:把日志系统和回放工具当一等公民来建设。每次跑测试都完整记录场景配置、随机种子、代码版本、所有机器人的位姿时间线、通信消息记录、感知目标列表。然后做一个"即时回放"小工具,能够逐帧回放碰撞发生前或危险接近前最后十秒钟的现场。

这个工具不需要多花哨,能按时间轴拖动、能看到每台机器人的感知目标和规划轨迹就够了。真正排查的时候,它比任何高级调试器都管用——因为多机问题本质上是时间和空间上的多方博弈,静态看日志只能看到一行行单个模块的输入输出,永远看不到双方在同一个时间点上的互动关系。

就我个人实际操作的体会来说,多机器人碰撞检测测试的核心资产不是那套测试代码,也不是那几台机器,而是你的场景库和日志回放能力。场景库决定了你能发现什么类型的问题,日志回放决定了你发现问题之后能不能快速定位。我后来换了一个更大的项目,机器人数量翻了三倍,第一件事就是把场景库和回放工具原封不动搬过去,后面的测试工作几乎没有重新摸石头过河。如果你正准备给多机系统搭测试环境,我建议先把日志回放和种子管理这两件事做好,再开始堆场景,顺序别反了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进