城市空间智能体:MLLM 能在真实规模城市中自主导航吗?
一句话总结
UrbanGround 用香港全域 3D 地理数据构建了一个真实规模的城市沙盒,系统测试多模态大语言模型(MLLM)能否从”看懂一张街景”进化为”在城市中持续导航”——结论是:局部能力可以,持续行动不行。
为什么这个问题重要?
自动驾驶和具身智能的终极目标从来都不是”识别一张图”,而是在真实空间中持续行动。这两件事之间有巨大的鸿沟:
- 局部感知:给一张街景图,问”前方有没有红绿灯”——GPT-4V 已经做得不错
- 空间智能体(Spatial Agency):从 A 出发,穿越陌生街道,绕开临时封路,到达 B——目前的 MLLM 还远远做不到
UrbanGround 把这个问题拆成三层递进:
- 场景理解:主动观察后能回答空间问题吗?
- 目标导航:目标越远、越模糊时,还能找到吗?
- 动态适应:路线变化、行人遮挡,行为还稳定吗?
背景知识
为什么需要 3D 城市沙盒?
现有评测的问题:
- 静态 QA 数据集:街景图 + 问题,没有连续交互
- 仿真环境(如 Habitat):室内为主,城市规模小,不真实
- 真实城市测试:成本高,不可重复,危险
UrbanGround 的方案是:从香港领土级地理数据(建筑模型、道路网络、卫星影像)重建物理约束准确的 3D 城市,支持第一视角闭环交互。
MLLM 作为智能体的基本框架
观察 o_t (图像 + 地图)
↓
MLLM (视觉理解 + 推理)
↓
动作 a_t ∈ {前进, 左转, 右转, 停止}
↓
环境反馈 o_{t+1}
关键挑战是上下文积累:第 t 步的决策依赖前 t-1 步的轨迹记忆,而 MLLM 的上下文窗口和时序推理能力都是瓶颈。
核心方法
直觉解释
把城市导航看成一个渐进式失败问题:
每一步决策有一个小误差概率 $\varepsilon$(朝错方向走、误判路口等)。单步看无害,但多步累积:
\[P(\text{成功到达}) = (1 - \varepsilon)^n\]$n = 20$ 步、$\varepsilon = 0.1$ 时,成功率只剩 $0.9^{20} \approx 12\%$。这就是为什么”原子能力强”但”端到端失败”。
三层评测设计
层一:场景接地(Grounding)
\[\text{GroundAcc} = \frac{1}{N} \sum_{i=1}^{N} \mathbb{1}[\hat{a}_i = a_i^*]\]智能体主动转动视角后,回答空间关系问题(”咖啡馆在你的左边还是右边?”)。测的是视觉感知 + 空间语言绑定。
层二:目标导航(Navigation)
\[\text{SPL} = \frac{1}{N} \sum_{i=1}^{N} S_i \cdot \frac{l_i^*}{\max(l_i, l_i^*)}\]Success weighted by Path Length,标准导航指标。目标描述从精确地址到模糊语义逐渐变难。
层三:动态鲁棒性(Robustness)
在导航成功的基础上,随机引入路线封闭或行人遮挡,测试智能体能否重规划而不崩溃。
Pipeline 概览
地理数据 (OSM + 卫星 + 建筑模型)
↓
3D 城市重建 (UrbanGround 环境)
↓
第一视角渲染 → MLLM Agent
↓
[接地/导航/动态] 三层任务评测
↓
轨迹记录 + 错误分析
实现
构建最小 MLLM 导航智能体
下面是一个模拟 UrbanGround 评测逻辑的最小实现,演示闭环交互和误差累积分析:
import base64, json
from dataclasses import dataclass, field
from typing import Optional
from openai import OpenAI # 或任意兼容 API
SYSTEM_PROMPT = """你是一个城市导航智能体。
每步你会收到:1) 当前第一视角图像;2) 简化地图文字描述;3) 目标描述。
输出 JSON: {"action": "forward|turn_left|turn_right|stop", "reason": "..."}
只输出 JSON,不要解释。"""
@dataclass
class NavState:
position: tuple[float, float] # (x, y) in meters
heading: float # degrees, 0=North
step: int = 0
trajectory: list = field(default_factory=list)
error_log: list = field(default_factory=list)
class UrbanMLLMAgent:
def __init__(self, model="gpt-4o", max_steps=50):
self.client = OpenAI()
self.model = model
self.max_steps = max_steps
self.state = None
def encode_image(self, image_path: str) -> str:
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def decide(self, image_path: str, map_desc: str, goal: str) -> dict:
"""单步决策:视觉 + 地图文字 → 动作"""
image_b64 = self.encode_image(image_path)
user_content = [
{"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}},
{"type": "text",
"text": f"地图信息: {map_desc}\n目标: {goal}"}
]
resp = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_content}
],
max_tokens=200,
response_format={"type": "json_object"}
)
return json.loads(resp.choices[0].message.content)
def run_episode(self, env, goal: str) -> dict:
"""完整一轮导航,返回评测指标"""
self.state = NavState(position=env.start_pos, heading=env.start_heading)
success, path_len, optimal_len = False, 0.0, env.optimal_path_length
for step in range(self.max_steps):
obs = env.render_fpv() # 返回图像路径
map_desc = env.get_map_text() # 简化地图描述
decision = self.decide(obs, map_desc, goal)
action = decision.get("action", "stop")
prev_pos = self.state.position
self.state, reward, done = env.step(action, self.state)
path_len += env.distance(prev_pos, self.state.position)
self.state.trajectory.append((self.state.position, action))
if done:
success = env.at_goal(self.state.position)
break
spl = success * (optimal_len / max(path_len, optimal_len))
return {"success": success, "spl": spl, "steps": step + 1}
误差累积可视化
这是 UrbanGround 核心发现的数值还原——展示为什么局部精度高但端到端失败:
import numpy as np
import matplotlib.pyplot as plt
def simulate_error_accumulation(step_error_rates, max_steps=50, trials=1000):
"""蒙特卡洛模拟不同单步误差率下的导航成功率"""
results = {}
for eps in step_error_rates:
successes = []
for _ in range(trials):
# 每步独立误差,误差累积导致偏离目标
errors = np.random.binomial(1, eps, max_steps)
# 连续 3 步误差 = 导航失败(简化模型)
failed = any(errors[i:i+3].sum() >= 3 for i in range(max_steps-2))
successes.append(0 if failed else 1)
results[eps] = [
np.mean(successes[:k]) for k in range(1, max_steps + 1)
]
return results
epsilons = [0.05, 0.10, 0.15, 0.20]
data = simulate_error_accumulation(epsilons)
plt.figure(figsize=(8, 4))
for eps, vals in data.items():
plt.plot(vals, label=f"单步误差率 ε={eps:.0%}")
plt.xlabel("导航步数")
plt.ylabel("累计成功率")
plt.title("误差累积效应:为什么局部能力≠持续导航能力")
plt.legend(); plt.grid(alpha=0.3); plt.tight_layout()
plt.savefig("error_accumulation.png", dpi=150)
预期输出:ε=10% 时,20 步后成功率跌至 12% 左右,与论文观察一致。
空间接地评测
from dataclasses import dataclass
from typing import Literal
import re
SpatialRelation = Literal["left", "right", "front", "behind", "above", "below"]
@dataclass
class GroundingTask:
image_path: str
question: str # "咖啡馆在你的哪个方向?"
answer: SpatialRelation
active_obs: bool = True # 是否允许主动旋转观察
def eval_grounding(agent: UrbanMLLMAgent, tasks: list[GroundingTask]) -> dict:
"""层一评测:场景接地准确率"""
correct, total = 0, len(tasks)
for task in tasks:
prompt = f"观察当前场景(可旋转视角),然后回答:{task.question}"
prompt += "\n只回答方位词: left/right/front/behind"
resp = agent.client.chat.completions.create(
model=agent.model,
messages=[
{"role": "user", "content": [
{"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,"
f"{agent.encode_image(task.image_path)}"}},
{"type": "text", "text": prompt}
]}
], max_tokens=50
)
pred = resp.choices[0].message.content.strip().lower()
# 提取方位词
for direction in ["left", "right", "front", "behind"]:
if direction in pred:
pred = direction; break
correct += int(pred == task.answer)
return {"accuracy": correct / total, "correct": correct, "total": total}
工程实践
实际部署考虑
| 指标 | 学术测试 | 生产部署 |
|---|---|---|
| 响应延迟 | 2-5s/步 | <500ms 要求 |
| 地图更新 | 静态 | 实时动态 |
| 视角质量 | 高分辨率渲染 | 真实摄像头噪声 |
| 错误恢复 | 无 | 必须有重规划 |
常见坑
坑 1:方向基准漂移
MLLM 对”左右”的理解依赖图像内容而非绝对坐标。智能体转弯后,”左边的建筑”可能变成实际的右边。
# 修复:在 prompt 中强制注入绝对朝向
def add_heading_context(prompt: str, heading: float) -> str:
compass = ["北", "东北", "东", "东南", "南", "西南", "西", "西北"]
direction = compass[int((heading + 22.5) / 45) % 8]
return f"[当前朝向: {direction}({heading:.0f}°)]\n" + prompt
坑 2:上下文爆炸
长距离导航会把几十张图像塞入上下文,GPT-4o 会开始”遗忘”早期路径信息。
# 修复:只保留最近 k 步视觉历史 + 文字轨迹摘要
def compress_history(trajectory: list, keep_last: int = 5) -> str:
summary = f"已走 {len(trajectory)} 步,当前位置 {trajectory[-1][0]}"
return summary # 替换掉所有历史图像
什么时候用 / 不用?
| 适用场景 | 不适用场景 |
|---|---|
| 短距离(<10步)接地任务 | 超过 20 步的远距离导航 |
| 语义目标(”找咖啡馆”) | 精确坐标导航 |
| 静态环境 | 密集动态行人场景 |
| 原型验证和评测研究 | 实时嵌入式部署 |
与其他方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 传统 SLAM + 规划 | 精确、实时、可靠 | 需要建图,无语义理解 | 工业机器人 |
| NeRF/3DGS + 规划 | 高保真场景重建 | 静态场景,重建慢 | 视觉导航预训练 |
| MLLM Agent(本文测试对象) | 零样本语义理解 | 持续导航失败、延迟高 | 辅助决策、短程任务 |
| MLLM + 地图工具调用 | 结合两者优点 | 工程复杂度高 | 当前最优实践方向 |
我的观点
UrbanGround 的价值不在于提出了新算法,而在于诚实地揭示了一个系统性失败模式:MLLM 的局部能力无法组合成持续的目标导向行为。这比”又一个 SOTA 点数”更有价值。
短期内值得关注的方向:
- 工具增强导航:让 MLLM 调用结构化地图 API,而不是纯视觉推理
- 错误检测与重规划:识别”我已经迷路了”比假装没事更重要
- 轻量专用模型:大通用模型的延迟不适合实时导航,蒸馏是必须的路
离实际部署还有多远?
短程语义接地(层一):1-2 年内可用。端到端城市级导航(层三):5 年以上,核心瓶颈不是模型能力,是误差校正机制和实时性。
论文链接:UrbanGround: From Local Perception to Spatial Agency in a Real-Scale City
Comments