本项目用于构建和评测基于 HM3D 场景的多目标视觉导航 benchmark。Agent 根据目标指令、当前 RGB 图和深度图,在 Habitat-Sim 场景中寻找一个或多个目标物体。
如果只想评测 agent,不需要重新构建数据集;准备好 HM3D 原始场景和发布的 dataset_semantic/ 后即可运行 run_eval.py。数据生成、属性提取和 episode 构建流程见 DATASET_BUILDING.md。
支持四种 target_mode:
single: 寻找一个指定目标
any: 多个符合描述的目标中找到任意一个
many: 寻找指定数量的符合描述目标
all: 寻找全部符合描述目标
其中 many 对应 MOS, Multi-Object Search;all 对应 MOC, Multi-Object Collection。single/any 主要用于 smoke test、消融和兼容单目标导航设置。
推荐使用 conda。当前项目已验证版本:
Python 3.9
habitat-sim 0.3.3
habitat-lab 0.3.3
创建环境:
conda create -n habitat python=3.9 cmake=3.14.0 -y
conda activate habitat安装 Habitat-Sim:
# 有显示器的本地机器
conda install habitat-sim=0.3.3 withbullet -c conda-forge -c aihabitat -y
# 无显示器服务器或集群机器
conda install habitat-sim=0.3.3 withbullet headless -c conda-forge -c aihabitat -y如果找不到 0.3.3,可以先查看可用版本:
conda search habitat-sim -c aihabitat安装 Habitat-Lab:
git clone --branch stable https://github.com/facebookresearch/habitat-lab.git
cd habitat-lab
pip install -e habitat-lab本项目不需要 habitat-baselines。如果后续要训练 Habitat Baselines agent,再额外安装:
pip install -e habitat-baselines安装本项目依赖:
cd /path/to/habitat_benchmark
pip install numpy pillow tqdm python-dotenv openai requests opencv-python omegaconf无 GUI 服务器可把 opencv-python 换成:
pip install opencv-python-headless检查安装:
python -c "import habitat_sim, habitat; print('ok')"官方安装说明:
- Habitat-Sim: https://github.com/facebookresearch/habitat-sim#installation
- Habitat-Lab: https://github.com/facebookresearch/habitat-lab#installation
评测需要两类数据:
- HM3D 原始场景和 navmesh,用于 Habitat-Sim 渲染、碰撞和路径长度计算。
- 本 benchmark 发布的数据,即
dataset_semantic/<scene_id>/goals.json、queries.json和episodes.json。
推荐目录结构:
/path/to/hm3d/val/
00800-TEEsavR23oF/
TEEsavR23oF.basis.glb
TEEsavR23oF.basis.navmesh
/path/to/habitat_benchmark/dataset_semantic/
TEEsavR23oF/
goals.json
queries.json
episodes.json
已经生成好的 benchmark 数据,对外发布或提交时通常只需要包含 dataset_semantic/。dataset_semantic_attributes/、rendered viewpoint 图片和 VLM 中间结果属于数据构建阶段产物,不是运行测评的必要输入。
如果需要从 HM3D semantic annotations 重新生成数据,请阅读 DATASET_BUILDING.md。
自然语言模式是默认测评模式,使用 queries.json 中的自然语言 instruction:
python run_eval.py \
--config hm3d_config.yaml \
--agent random \
--goal-type description \
--dataset-dir dataset_semantic \
--scene-root /path/to/hm3d/val \
--limit 1 \
--episode-limit 5Object-nav 模式模仿 GOAT 的 object goal 语义,goal_text 是数据集中的类别名:
python run_eval.py \
--config hm3d_config.yaml \
--agent random \
--goal-type object \
--dataset-dir dataset_semantic \
--scene-root /path/to/hm3d/val \
--limit 1 \
--episode-limit 5Object-nav 只使用纯类别 query:query_program.where 中唯一条件必须是 category == <类别名>,且该类别必须出现在当前 scene 的 goals.json 中。带 color、material、feature 等属性约束的 query 仍属于自然语言/description 模式。
如果评测能正常启动,说明 Habitat-Sim、HM3D 路径和 benchmark 数据基本配置已经连通。
本 benchmark 使用 GOAT-style agent API,并为 MOS/MOC 多目标导航扩展了动作语义。测评者通常只需要模仿 agents_to_test/simple_agent.py,把其中的策略逻辑替换成自己的方法。
两种 goal 类型使用同一套 agent 接口:
description: goal_text 是自然语言目标描述
object: goal_text 是 dataset-defined category,例如 chair / table / bath mat
Object 模式不是任意开放词输入;它只使用当前 benchmark 数据中出现过的类别,并且只从纯类别 query 构造 episode。target_mode 仍然可以是 single / any / many / all,指标和自然语言模式一致。
最小接口:
def act(self, observation) -> int:
...推荐同时实现:
def reset(self) -> None:
...不需要继承基类,不需要注册到 Habitat,也不需要理解评测框架内部结构。
- 在
agents_to_test/下新建文件,例如agents_to_test/my_agent.py。 - 定义一个 agent 类,例如
MyAgent。 - 实现
act(self, observation),每一步返回一个动作 ID 或benchmark_api.Action。 - 如果 agent 有 episode 内历史状态,实现
reset(self)。 - 从
observation.rgb、observation.depth、observation.goal_text、observation.task_prompt等字段读取输入。 - 到达一个匹配目标附近时返回
Action.TARGET_FOUND或整数6。 - 认为整个 episode 完成时返回
Action.FINISH或整数0。 - 用
--agent agents_to_test.my_agent:MyAgent指定要评测的 agent。
single/any 下,找到目标后返回 6 或 0 都可以结束 episode,推荐返回 6。many/all 下,6 只表示“当前位置发现了一个目标”,不会结束 episode;只有 0 表示 agent 主动结束整个任务。
自定义 agent 每一步收到 benchmark_api.Observation:
@dataclass(frozen=True)
class Observation:
rgb: np.ndarray # (H, W, 3), 当前 RGB
depth: Optional[np.ndarray] # 当前深度图
goal_text: str # object 模式为类别名;description 模式为自然语言描述
goal_embedding: Optional[np.ndarray]
goal_image: Optional[np.ndarray]
goal_type: str # object / description / image,目前主要是 object 或 description
subtask_type: str # GOAT-style 别名,同 goal_type
target_mode: str # single / any / many / all
target_count: Optional[int] # many 模式要求数量,其他模式通常为 None
episode_id: str
scene_id: str
step_count: int
gps: np.ndarray # (2,), 当前 x/z 平面位置
compass: float # 当前朝向角,单位 rad
max_steps: int
task_prompt: Optional[str] # 完整任务 prompt,给 VLM/LLM agent 使用
previous_action: Optional[int]
extras: Optional[dict]注意:many/all 模式下,goal_text 和 task_prompt 都不会告诉 agent 还剩几个目标未找到。goal_embedding 和 goal_image 预留给后续 object/image/language 多模态目标;当前 object 和 description 模式都主要使用文本目标。
可以直接返回整数,也可以返回 benchmark_api.Action:
from benchmark_api import Action
return Action.MOVE_FORWARD动作定义:
0 = FINISH
1 = MOVE_FORWARD, 0.25m
2 = TURN_LEFT, 30 degrees
3 = TURN_RIGHT, 30 degrees
4 = LOOK_UP, 30 degrees
5 = LOOK_DOWN, 30 degrees
6 = TARGET_FOUND / SUBTASK_STOP
9 = LEGACY_FINISH
仓库提供了推荐模板:
agents_to_test/simple_agent.py
测评者可以复制该文件,或新建:
agents_to_test/my_agent.py
示例:
from benchmark_api import Action, Observation
class MyAgent:
def reset(self) -> None:
self.steps = 0
def act(self, observation: Observation) -> int:
self.steps += 1
# observation.rgb / observation.depth 可以送入自己的 VLM 或导航模型。
# observation.goal_text 是 GOAT-style 目标文本。
# observation.task_prompt 是完整任务说明。
if self.steps < 10:
return Action.MOVE_FORWARD
if observation.target_mode in {"many", "all"}:
return Action.FINISH
return Action.TARGET_FOUND运行自定义 agent:
python run_eval.py \
--config hm3d_config.yaml \
--agent agents_to_test.my_agent:MyAgent \
--limit 1 \
--episode-limit 5运行仓库自带模板:
python run_eval.py \
--config hm3d_config.yaml \
--agent agents_to_test.simple_agent:SimpleAgent \
--limit 1 \
--episode-limit 1 \
--max-steps 2如果要接入外部 VLM,可以在 act() 中把 observation.rgb、observation.depth 和 observation.goal_text 发给自己的模型服务,再把模型输出解析成动作 ID:
from benchmark_api import Action, Observation
class MyVLMAgent:
def __init__(self):
self.history = []
def reset(self) -> None:
self.history = []
def act(self, observation: Observation) -> int:
response = call_your_model(
rgb=observation.rgb,
depth=observation.depth,
instruction=observation.goal_text,
history=self.history,
)
action = parse_action(response)
self.history.append((observation.step_count, action))
return action其中 call_your_model() 和 parse_action() 由被测者自己实现。
旧版自定义 agent 如果已经实现:
def act(self, rgb=None, depth=None, prompt=None) -> int:
...可以临时用兼容模式运行:
python run_eval.py \
--config hm3d_config.yaml \
--agent my_agents.old_agent:OldAgent \
--legacy-agent-api \
--action-protocol legacy新代码建议使用 act(observation) 接口。
默认使用 --action-protocol goat,即 GOAT-style API with MOC extension。输入观测尽量沿用 GOAT-Bench 风格,动作语义扩展出 MOC 所需的全局终止判断。
默认动作:
0 = FINISH, agent 认为整个 episode 已完成
1 = MOVE_FORWARD, 0.25m
2 = TURN_LEFT, 30 degrees
3 = TURN_RIGHT, 30 degrees
4 = LOOK_UP, 30 degrees
5 = LOOK_DOWN, 30 degrees
6 = TARGET_FOUND / SUBTASK_STOP, agent 认为当前位置附近有一个匹配目标实例
9 = LEGACY_FINISH, 旧版 finish 别名,兼容保留
和 GOAT 的关键区别:
- GOAT 中
SUBTASK_STOP=6通常表示当前子任务完成,benchmark 会进入下一个给定目标。 - 本 benchmark 中
TARGET_FOUND=6只登记当前发现的实例,不会告诉 agent 下一个目标是谁。 - Agent 必须自己决定继续搜索哪里,以及何时
FINISH。
single/any:
- Agent 找到目标后输出
6或0都可以结束 episode。 - 推荐输出
6,与 GOAT 的SUBTASK_STOP风格保持一致。
many/all:
- Agent 认为当前位置附近有匹配目标时输出
6。 6会尝试确认当前附近的新目标,但不会结束 episode,也不会给出下一个目标。- Agent 认为整个任务完成时输出
0。 9作为旧版 finish alias 仍然被接受。- 评测时不会告诉模型还剩几个目标没找到。
旧协议中,many/all 下的 0 仍表示确认目标,9 表示结束 episode:
python run_eval.py ... --action-protocol legacycompat 协议也接受 6,但保留旧版 0 确认语义。
python run_eval.py \
--config hm3d_config.yaml \
--agent agents_to_test.simple_agent:SimpleAgent \
--goal-type description \
--dataset-dir dataset_semantic \
--scene-root /path/to/hm3d/valpython run_eval.py \
--config hm3d_config.yaml \
--agent agents_to_test.simple_agent:SimpleAgent \
--goal-type object \
--dataset-dir dataset_semantic \
--scene-root /path/to/hm3d/val两种模式使用同一套动作协议和指标。区别是:description 使用自然语言 instruction;object 只使用纯类别 query,并把 observation.goal_text 设置为类别名。
conda activate habitat
python run_eval.py \
--config hm3d_config.yaml \
--agent human \
--limit 1 \
--episode-limit 1 \
--render-live \
--log-prompts \
--log-positionsHuman 控制:
0 = finish episode
1 = move_forward
2 = turn_left
3 = turn_right
4 = look_up
5 = look_down
6 = target_found
9 = legacy_finish
q = quit
conda activate habitat
python run_eval.py \
--config hm3d_config.yaml \
--agent random \
--limit 1 \
--episode-limit 2 \
--max-steps 2 \
--log-episodes常用调试参数:
--log-positions
--log-prompts
--log-episodesOER-space 和 FTR 中的“是否仍有未探索空间”使用 navmesh 采样 oracle 近似计算。默认每个 episode 采样 200 个可达点,agent 轨迹进入 1.5m 半径即视为探索该点:
--space-coverage-samples 200
--space-coverage-radius 1.5快速 smoke test 可以关闭 OER-space:
--space-coverage-samples 0当前正式 benchmark 指标与 多目标导航.md 保持一致。主榜单只聚合 many/all 多目标 episode,报告:
sr
f1
spl_multi
同时输出诊断指标:
precision
recall
pe
tse
rtsr
false_termination_rate / ftr
oer_target
oer_space
为兼容已有脚本,输出中仍保留 spl、raw_spl、coverage 等旧字段;其中 spl 是 spl_multi 的别名。
single 和 any 是单目标或任一目标导航任务,当前只作为 smoke test、消融和基础导航能力检查。它们的模式级指标只输出:
sr_single, spl_single
sr_any, spl_any
single/any 不单独报告 precision / recall / f1。这两个模式没有多目标部分完成问题,F1 基本会退化成与 SR 同步的 0/1 指标。
required_target_count 按模式定义:
single: 1
any: 1
many: target_count
all: len(target_object_ids)
Agent 输出 6 表示确认当前附近目标。评测器会记录 report_events:
{
"step": 12,
"near_targets": [101],
"new_targets": [101],
"duplicate_targets": [],
"is_true_positive": true,
"is_false_positive": false
}每个 episode 统计:
TP:6信号覆盖此前未认领过的合法目标实例。FP:6信号附近没有新的合法目标实例,包括误报和重复认领。FN:episode 结束时仍未被覆盖的 required target。
计算方式:
precision = TP / (TP + FP)
recall = min(1, found_targets / required_target_count)
f1 = 2 * precision * recall / (precision + recall)
如果 TP + FP = 0,则 precision = 0。如果 precision + recall = 0,则 f1 = 0。
SR 按任务类型定义:
MOS / many: found_targets >= target_count
MOC / all: found_targets == len(target_object_ids) 且 agent 主动输出 FINISH(0)
这意味着 all 模式中,即使 agent 找到了所有目标,只要没有主动输出 0,也不算 MOC 成功。9 作为 legacy finish alias 也会被接受。
trajectory_length 是 agent 实际走过的路径长度 p_i。
optimal_subset_distance 是全局最优多目标开路径长度 l_i。对于 many,它对应从所有合法目标中选择 target_count 个后的最短路径;对于 all,它对应访问全部目标的最短路径。
agent_subset_distance 是针对 agent 实际确认的目标子集重新计算的最优开路径长度。
Episode 级 SPL-multi:
spl_multi_i = success_i * optimal_subset_distance_i
/ max(trajectory_length_i, optimal_subset_distance_i)
细粒度拆分:
pe_i = agent_subset_distance_i / max(trajectory_length_i, agent_subset_distance_i)
tse_i = optimal_subset_distance_i / agent_subset_distance_i
PE 衡量“agent 选定这组目标后走得是否绕路”。TSE 衡量“agent 选的目标子集是否接近全局最优子集”。当前 TSE 主要对 many/MOS 有意义;all/MOC 没有子集选择,默认不作为主要诊断项。
RTSR 衡量重复目标选择率:
rtsr = (m - unique(S)) / m
其中 S 是 agent 发出的确认信号对应到真实目标实例后的序列。重复确认同一实例会提高 RTSR。
FTR 只在 MOC/all 中统计。agent 主动 FINISH 时,如果仍未找全目标,或 navmesh 采样 oracle 认为仍有未探索空间,则记为 false termination:
ftr = mean(agent 输出 FINISH 时仍未找全目标)
OER-target 衡量 agent 已经找齐 required targets 后仍继续探索的比例:
oer_target_i = (finish_step_i - all_required_found_step_i) / max_steps
如果 agent 找齐后一直没有 FINISH,则用 max_steps 作为停止时间。
OER-space 衡量 sampled navmesh 空间已经被探索完后,agent 仍继续探索的比例:
oer_space_i = (finish_step_i - space_exhausted_step_i) / max_steps
只有当 episode 内确实达到 space_exhausted_step 时才计算;否则该 episode 的 oer_space 为 null,聚合时跳过。space_exhausted_step 来自 --space-coverage-samples 和 --space-coverage-radius 控制的近似空间覆盖 oracle。
总指标对 episode-level 指标取平均。sr / f1 / spl_multi 默认只聚合 many/all 多目标 episode:
sr = mean(sr_i)
f1 = mean(f1_i for many/all)
spl_multi = mean(spl_multi_i)
按任务类型额外输出:
sr_mos, f1_mos, spl_multi_mos
sr_moc, f1_moc, spl_multi_moc
按旧模式额外输出:
sr_single, spl_single
sr_any, spl_any
sr_many, f1_many, precision_many, recall_many
sr_all, f1_all, precision_all, recall_all
仓库按“评测核心、agent 接口、数据构建、示例数据和文档”组织:
.
├── README.md # benchmark 使用说明:接口、运行评测、指标
├── DATASET_BUILDING.md # 数据构建说明:从 HM3D semantic 生成 episodes
├── 多目标导航.md # 任务和正式论文版指标设计文档
├── hm3d_config.yaml # Habitat-Sim 配置
│
├── run_eval.py # 评测入口,解析参数并启动 evaluator
├── evaluator.py # episode 执行、动作协议、目标确认和路径统计
├── metrics.py # episode/result 聚合指标
├── dataset.py # 读取 goals / queries / episodes 数据格式
├── benchmark_api.py # 对外暴露的 Observation / Action 接口
├── policy.py # human / random / API / custom agent 适配
├── prompt.py # agent prompt 和 VLM prompt
│
├── agents_to_test/
│ └── simple_agent.py # 推荐给测评者复制/修改的 agent 模板
│
├── dataset_building/
│ ├── extract_from_semantic.py # 从 HM3D semantic annotations 构建 goals.json
│ ├── attribute_extractor.py # 渲染 viewpoint 并用 VLM 提取目标属性
│ ├── normalize_goal_attributes.py # 规范化 color / material 等属性
│ ├── build_queries.py # 根据 goal 类别和属性生成结构化 queries
│ ├── generate_query_language.py # 为 query 生成自然语言描述
│ └── build_episodes.py # 根据 queries 和 navmesh 生成 episodes
│
├── tools/
│ └── show_pic_of_instance.py # 渲染 goal viewpoint,人工检查图片质量
│
└── dataset_semantic/
└── <scene_id>/
├── goals.json # 场景中的候选目标实例及几何/属性信息
├── queries.json # 结构化 query 和自然语言指令
└── episodes.json # 可直接评测的 episode 列表
数据构建阶段还可能产生:
dataset_semantic_attributes/<scene_id>.json # 属性提取中间结果
rendered_viewpoints/<scene_id>/<goal_id>/ # viewpoint 检查图片
这些文件不是运行测评的必要输入。
不会。many/all 模式下,模型需要自己判断是否找全。到达一个目标附近输出 6,认为任务完成输出 0。
可以。评测所需 benchmark 数据是 dataset_semantic/<scene_id>/goals.json、queries.json 和 episodes.json。运行测评时还需要用户本地准备 HM3D 原始场景和 navmesh。
HM3D semantic annotations、viewpoint、属性提取、query 生成和 episode 生成相关问题见 DATASET_BUILDING.md。
不一定。只要场景、navmesh 和渲染能正常加载,类似缺少 *.basis.scene_instance.json 的 warning 可能不影响评测。