想象一下,你对家里的扫地机器人说一句“去沙发右边的垃圾桶旁边”,它就能准确理解并执行。这背后需要机器具备两个关键能力:一是理解“右边”“旁边”这类物体之间的位置关系,二是把这种理解转化为可执行的移动路径,也就是生成导航指令。空间推理能力正是让机器从“看得见”走向“懂空间”的桥梁,本文将从位置关系的建模方法、导航指令的生成流程以及工程实践中的优化技巧三个角度展开讲解。

一、物体位置关系如何表示:从坐标系到语义描述
要让机器描述物体之间的位置关系,第一步是把物理空间数字化。最常见的做法是建立全局坐标系,通常以环境中的某个固定点为原点,用平面坐标表示物体的位置。每个物体在坐标系中占据一个区域,可以用外接矩形或中心点来近似。有了这些数据,机器就能计算物体之间的距离、方位角等几何量。
但仅有几何数据还不够。人类说“杯子在桌子左边”时,参考系其实是人的朝向,这叫相对坐标系。同一个杯子,面向桌子的人和背对桌子的人会给出完全相反的描述。因此在构建位置关系时,通常需要同时维护两套信息:一套是全局坐标,用于精确计算;一套是以观察者或参照物为中心的相对方位,用于语言表达。下面的代码演示了如何根据参照物和观察者朝向计算相对方位词。
import math
def relative_direction(ref_pos, target_pos, observer_heading):
"""
ref_pos: 参照物坐标 (x, y)
target_pos: 目标物坐标 (x, y)
observer_heading: 观察者朝向角,单位为度,0表示正北
返回: 方位词字符串
"""
dx = target_pos[0] - ref_pos[0]
dy = target_pos[1] - ref_pos[1]
# 将全局坐标系下的角度转换为以观察者朝向为基准的角度
world_angle = math.degrees(math.atan2(dx, dy))
rel_angle = (world_angle - observer_heading) % 360
directions = ["正前方", "右前方", "正右方", "右后方",
"正后方", "左后方", "正左方", "左前方"]
index = round(rel_angle / 45) % 8
return directions[index]
# 沙发位于(5, 5),观察者朝北(0度),目标在(8, 3)
print(relative_direction((5, 5), (8, 3), 0)) # 输出: 右前方
除了方位,拓扑关系也是空间推理的重要内容。“在……里面”“穿过”“相邻”这类关系无法用简单的角度和距离表达,需要引入区域包含、连通性判断等逻辑。实际工程中常采用栅格地图或拓扑图来存储这类信息:栅格地图把空间切成小格子,标记每个格子是否被占用、属于哪个物体;拓扑图则把房间、门口、走廊抽象成节点和边,更适合做高层级的语义描述。两种表示各有侧重,栅格图精确但数据量大,拓扑图轻量但损失了细节,成熟系统往往两者结合使用。
二、导航指令生成的完整链路
导航指令生成可以拆解为三个环节:环境建模、路径规划、语言表达。环境建模解决“机器知道什么”的问题,输出是一张包含物体位置和可通行区域的地图;路径规划解决“怎么走”的问题,常用算法有A*、Dijkstra以及适合动态环境的D* Lite,它们在栅格地图上搜索一条从起点到终点的最优路径;语言表达解决“怎么说”的问题,把路径序列翻译成人类能听懂的自然语言。
传统方案使用模板填充的方式生成指令。预先定义好一批句式,例如“向前走{n}米后左转”“在{object}处右转”,规划出路径后提取关键节点,填入模板拼接成完整指令。这种方案的好处是完全可控、易于调试,缺点是指令生硬,遇到复杂路口时表达不够灵活。下面是一个简化示例,展示如何从路径点序列生成模板化指令。
def generate_instructions(path, waypoints):
"""
path: 路径点坐标列表
waypoints: 关键路标信息,如 [(3, '电梯'), (7, '茶水间')]
"""
instructions = []
landmark_map = {pos: name for pos, name in waypoints}
step = 0
for i, point in enumerate(path):
step += 1
if point in landmark_map:
turn = "左转" if i % 2 == 0 else "右转"
instructions.append(
f"直行{step}步到达{landmark_map[point]},然后{turn}"
)
step = 0
instructions.append(f"继续直行{step}步,到达目的地")
return instructions
path = [(0,0), (1,0), (2,0), (3,0), (4,0), (5,0)]
waypoints = [((3,0), '电梯'), ((5,0), '会议室')]
for line in generate_instructions(path, waypoints):
print(line)
近年来大语言模型的引入改变了这一格局。可以把环境摘要、物体坐标、规划路径一起作为上下文喂给模型,让它生成更自然的指令,例如“出门后沿走廊直走,经过电梯右手边就是会议室”。这种方式的指令流畅度和上下文适应能力明显更强,但也有代价:模型可能产生与实际地图不符的“幻觉路标”,而且推理延迟高于模板方案。稳妥的工程做法是混合架构,结构化信息由传统组件保证准确,语言润色交给模型,同时在输出后做路标校验,剔除环境中不存在的物体名称。
三、实践中的常见误区与优化思路
第一个常见误区是忽视参考系的一致性。系统在理解阶段用全局坐标,在表达阶段却假设用户朝向某个固定方向,一旦用户转身,生成的“左转”“右转”就会全部颠倒。解决办法是在每次交互开始时显式确定参考系:如果设备有陀螺仪就读取实时朝向,没有就让用户先说一句“我面对着门”来锚定。
第二个误区是指令粒度不匹配。给视障用户导航需要精确到“向前两步”级别,而给司机导航说“两步”就荒谬了。指令生成前应根据使用者类型和移动速度确定空间粒度,行人场景一般以一到五米为一个单元,车辆场景则以路口和道路名称为单元。粒度还会影响路标的选择,粒度越细,越需要关注小物体,这要求环境感知模块有相应的分辨率。
第三个优化点是对话式的动态修正。一次性生成的指令在用户走错路时会失效,更好的做法是把导航拆成多轮对话:系统在关键节点检测用户实际位置,偏离则重新规划并生成修正指令,例如“你好像走过了,往回走几步,在第二个路口等我”。实现上需要位置跟踪与指令生成模块之间建立事件回调,用户位置更新触发偏差检测,偏差超过阈值即触发重规划。这种闭环设计在实际产品中带来的体验提升,往往比单纯优化语言流畅度更明显。
最后需要强调评测的重要性。空间推理系统的错误往往不是均匀分布的,方位词错误、路标识别错误、路径规划错误需要分开统计。建议构建一个包含典型场景的测试集,覆盖遮挡、对称房间、多参照物歧义等情况,用指令准确率和任务完成率两个指标分别评估表达层和执行层,这样定位问题时才有据可依。