habitat-基础使用记录

一些有用的参考:

  • Habitat-sim: https://github.com/facebookresearch/habitat-sim
    • Doc: https://aihabitat.org/docs/habitat-sim/index.html
    • Tutorial: https://aihabitat.org/tutorial/2020/
  • Habitat-lab: https://github.com/facebookresearch/habitat-lab
    • Doc:https://aihabitat.org/docs/habitat-lab/
  • Resource Index: https://aihabitat.org/docs/resource_index/

Habitat-Sim Usage

首先介绍一些核心的概念,这些概念会贯穿整个使用流程。

  • Agent:在场景中的Agent,能够观察场景以及与场景进行交互
  • Sensor:装配在Agent上的各种传感器,能够对环境进行感知,包括视觉信息、深度信息等
  • Scene:场景的模拟3D环境
  • Simulator:模拟器,融合上面的概念,进行模拟

Habitat-Sim的基础运行流程可以总结为:配置并启动simulator(habitat_sim.simulator.Simulator),加载scene,配置带有sensor的agent(habitat_sim.agent.Agent),执行action,获取sensor观察结果(rgb、semantic、depth等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
## 1. 创建Simulator,指定相关Config

sim_cfg = habitat_sim.SimulatorConfiguration()
# sim_cfg.xxx = xxx
agent_cfg = habitat_sim.agent.AgentConfiguration()
# agent_cfg.xxx = xxx
sensor_spec = habitat_sim.CameraSensorSpec()
# sensor_spec.xxx = xxx

cfg = habitat_sim.Configuration(sim_cfg, [agent_cfg])
sim = habitat_sim.Simulator(cfg)

## 2. 对Agent进行初始化,即指定Agent的初始状态
# Set agent state
agent = sim.initialize_agent(sim_settings["default_agent"])
agent_state = habitat_sim.AgentState()
agent_state.position = np.array([0. ,0., 0.]) # world space
agent_state.rotation = quaternion.from_euler_angles(0, -np.pi/2, -np.pi/2)
agent.set_state(agent_state)

## 3. 调用action space中的动作进行执行
observations = sim.step(action)

## 4. 从observations中取出相关的sensor观察
  • 创建Simulator,需要利用config(habitat_sim.simulator.Configuration)来指定相关配置,主要包括:

    • For simulator backend:指定要加载的场景,是否加载语义mesh、是否启用物理等

    • For agent:给定Agent的初始化参数,包括高度、质量、Agent相关的Sensor、每次移动的物理量等

根据官方给的入门教程,我们同样列举如下的一些概念:

NavMesh:用于指定场景中的可导航区域。habitat中提供pathfinder可以用来加载或者构建NavMash,然后在上面进行点采样、路径查找、碰撞等查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 利用pathfinder来query某个点是否是可达的
sim.pathfinder.is_navigable(nav_point)

# NavMesh的可视化:获取topdown_view
sim.pathfinder.get_topdown_view(meters_per_pixel, height) # 这是habitat-sim的接口,在habitat-lab中也提供相关接口

# NavMesh的使用
# 搜索距离最近障碍的距离以及碰撞点:
sim.pathfinder.distance_to_closest_obstacle(nav_point, max_search_radius)
sim.pathfinder.closest_obstacle_surface_point(nav_point, max_search_radius)

# 设定了某个世界坐标position之后,需要将其snap到可导航的有效点上
sim.pathfinder.snap_point(position)

# 可以通过NavMesh进行最短路径寻找并可视化路上的结果

# 显式加载NavMesh,后缀为.navmesh
sim.pathfinder.load_nav_mesh(xxx)

# 随机加载一个可以到达的点
sim.path.pathfinder.get_random_navigable_point()

# 在运行过程中,可以重新计算NavMesh,当然也可以进行保存
sim.recompute_navmesh(sim.pathfinder, navmesh_settings)
  • island_radius:最小包含圆是指能够包含所有属于该导航岛屿的点的最小圆。island_radius 是这个圆的半径。导航岛屿中的点的质心(中心点)是这个圆的圆心。island_radius 参数表示的是一个点所属的连通组件(connected component)的大小。island_radius 可以用来验证一个点是否在一个有意义的导航表面上,例如地板。如果一个点的island_radius值很小,这意味着该点可能位于一个小的、孤立的表面上,比如桌面或椅子,而不是一个大的、连续的表面如地板。这有助于过滤掉那些不适合导航的点,确保AI代理只在合理的、可导航的表面上移动。
  • Snap Point:在导航和路径规划领域,特别是在模拟和机器人研究中,“snap point” 通常指的是将一个查询点调整(或“吸附”)到最接近的有效导航点的过程。这个概念用于确保AI代理或机器人在导航时始终位于可导航的表面上,而不是在无效的或无法导航的区域。
  • 重新计算NavMesh参考:Digesting Duck: Recast Settings Uncovered

Sliding:Agent的滑动操作。这是一个配置选项,表示Agent是否能够沿着障碍进行滑动sim.config.sim_cfg.allow_sliding。需要注意的是,滑动和连续动作空间并不是同一个概念。允许滑动可以使训练更加容易,产生更高的模拟性能,但是会损害训练策略的sim2real性能。

入门教程中还提供了如下参考资料:

Habitat-Lab Usage

Habitat-Lab是一个模块化的高级库,它在Habitat-sim的基础上构建,可以完成相关操作,包括定义具身AI任务(例如导航、指令跟随、问答)、配置具身Agent、训练Agent、在定义任务上进行基准测试。下面是一个Quick Start,允许我们通过键盘操作完成一个PointNav任务(完整实现可以参考quick-start,这里只保留了一些关键代码便于说明):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import habitat
from habitat.sim.habitat_simulator.actions import HabitatSimActions
import cv2

env = habitat.Env(
config=habitat.get_config("benchmark/nav/pointnav/pointnav_habitat_test.yaml")
)
observation = env.reset()

while not env.episode_over:
action = HabitatSimActions.xxx
observation = env.step(action)

distance = observations["pointgoal_with_gps_compass"][0]
theta = observations["pointgoal_with_gps_compass"][1] # radians
image = observations["rgb"]
  • 完整quick-start中的可视化功能是通过cv2提供的,与habitat-sim是否使用headless没有关系

  • habitcat.Env:其中抽象了利用simulator处理emnodied task所需的所有信息,包括dataset(episodes),simulator(sim)和EmbodiedTask(task)

    • Env通过config来完成初始化,config中配置sensor和agent等信息
    • config可以是一个文件,其中指定默认值,然后通过habitat.get_config来获取。配置管理系统使用hydra。配置系统可以参考READMEconfig key
    • 所有的config基本配置都在habitat-lab/habitat/config
    • 也可以通过read_write来更新默认值:
1
2
3
4
5
6
7
8
9
import habitat
from habitat.config import read_write

config = habitat.get_config(config_path="xxx/xxx/xxx")
with read_write(config):
config.habitat.dataset.split = "val"
...

env = habitat.Env(config=config)
  • simulator.Simulator:Abstract Simulator,要添加到habitat中的新Simulator都需要继承该类并实现抽象方法

  • embodied_task.EmbodiedTask:Abstract Embodied Task,包括任务定义相关的action space、observation space、measures、simulator usage

上图是Habitat的架构说明,包含如下关键概念:

Habitat-Baseline Usage

Habitat-Baseline是Habitat官方提供的一套用于训练和评估 AI 智能体的强化学习基线算法集合,构建于Habitat-Lab之上,提供了可直接使用的训练框架和模型实现。它是用于在 Habitat 环境中快速训练智能体的“开箱即用”工具,广泛用于导航、探索、任务完成等研究。

一个典型的运行流程如下:

  1. 通过habitat-baselines.run.execute_exp来启动整个流程
  2. 通过Trainer来进行训练和评估,类似Pytorch-Lightning的管理方式
    • 通过config中的trainer name来初始化Trainer,默认实现是PPO Trainer
    • 需要通过@baseline_registry.register_trainer来将其他Trainer实现注册到系统中
    • 根据配置中的cfg.habitat_baselines.evaluate来选择走Trainer.train()还是Trainer.eval()
  3. Trainer中包括重要属性,Agent和Envs
  4. 通过_init_envs()来初始化VectorEnv
    • VectorEnv就是多个envs组成的,可以同时运行多个Envs来加速训练。VectorEnv维护多个进程Process,每个进程会有一个worker_env,对应config.habitat.env_task.GymHabitatEnv
      • 这个Env同样是可以注册的@habitat.registry.register_env(*name*="GymHabitatEnv")
    • 而这个Env中又包含一个成员是RLTaskEnv -> habitat.RLEnv -> Env,最终和Habitat-sim中的Env联系在一起
  5. 通过Trainer中的env.step()来驱动所有的Env和Agent等。这个也是Habitat-Lab Trainer和Habitat-sim Env之间的核心桥梁

在Habitat-sim和Habitat-lab中包含多种Env的定义,它们可能是相互嵌套的关系,实际使用过程中需要明确每个Env究竟是什么Class

  1. 核心步骤的源码追踪如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
observations, reward, done, info = env.step(data)

# 对应的实现在RLEnv中
@profiling_wrapper.RangeContext("RLEnv.step")
def step(self, *args, **kwargs) -> Tuple[Observations, Any, bool, dict]:
r"""Perform an action in the environment.

:return: :py:`(observations, reward, done, info)`
"""

observations = self._env.step(*args, **kwargs)
reward = self.get_reward(observations)
done = self.get_done(observations)
info = self.get_info(observations)

return observations, reward, done, info

# 对应的get_reward实际在Env中
def get_metrics(self) -> Metrics:
return self._task.measurements.get_metrics()
  • 对应的实现在RLEnv
1
2
3
4
5
6
7
8
9
10
11
12
13
@profiling_wrapper.RangeContext("RLEnv.step")
def step(self, *args, **kwargs) -> Tuple[Observations, Any, bool, dict]:
r"""Perform an action in the environment.

:return: :py:`(observations, reward, done, info)`
"""

observations = self._env.step(*args, **kwargs)
reward = self.get_reward(observations)
done = self.get_done(observations)
info = self.get_info(observations)

return observations, reward, done, info

使用记录

坐标系约定

关于坐标系的教程可以参考坐标系教程。核心概念包括:

  • 世界坐标系:高度轴为y,并且遵循右手系
  • 物体坐标系:高度轴为y,并且遵循右手系,同时物体中心为原点
  • 相机坐标系:-z(blue)指向前方,y(green)指向上方,x(red)轴指向右方「opengl」

Dataset加载-以Replica为例

在Habitat中,支持的Dataset需要满足一定的组织格式,可以参考文档

顶层描述文件xxx.scene_dataset_config.json

  • 子属性包括:
    • stage:构成场景背景的mesh组件,对于室内场景来说就是一个空房间的mesh
    • objects: 物体
    • articulated_objects:铰链物体
    • lights_setup:光照设置
    • scene_instances:场景描述
    • semantic_scene_descriptor_instances:语义场景
    • navmesh_instances: NavMesh描述
  • 每个子属性都可以用一个json来描述「json路径或者JSON Object」
  • stage:一个场景只能有一个stage_instance
  • 在json文件中配置的路径为相对路径
  • user_defined:可以在任意json文件中叠加。并且 Habitat-sim 功能既不依赖于也不处理此标签下的任何特定元数据。您可以使用此标签缓存特定使用案例的对象信息,或在用户代码中跟踪一段时间内的模拟属性。

这里我们记录了一些相关Issue:

  • Replica数据加载,参考issue1782
    • 指定xxx.scene_dataset_config.json, scene_id
  • 可以像参考程序一样直接加载glb,也可以像Replica一样加载ply,参考issue63
  • scene_dataset_config.json内容说明,围绕此来构造集成自己的数据集,参考issue2353

[habitat-sim]获取Agent和Sensor的state

以下代码可以获取Agent的state:

1
2
agent_state = agent.get_state()
print("agent_state: position", agent_state.position, "rotation", agent_state.rotation)
  • agent的position是当前所在地面位置的全局坐标「注意agent必须贴地行走」

  • agent的旋转与sensor的旋转相同

以下代码可以获取sensor的state:

1
2
3
agent_state = agent.get_state()
sensor_state = agent_state.sensor_states
# sensor_state = agent.state.sensor_states
  • 分别返回position和rotation「一共六个数字」,相当于相机的外参

  • 注意sensor的状态和agent的状态并不存在绝对的联系

  • sensor参数的获取:尤其是内参的获取

[habitat-sim]获取observation

在Habitat-sim中可以通过以下两种方式来获取observation:

1
2
3
4
5
6
7
8
9
10
11
12
agent = sim.get_agent(sim_settings["default_agent"])
agent_state = habitat_sim.AgentState()
agent_state.position = np.array(translation)
agent_state.rotation = quaternion.quaternion(quaternion_[0], quaternion_[1], quaternion_[2], quaternion_[3])
agent.set_state(agent_state)
# 以上为设置agent的状态的代码

# 下面两种方式都可以获取observation,不过有点不同
# 1.直接获取当前状态的observation,允许agent浮空,因此可以获得与上面设置的agent状态完全一致的观察
observations = sim.get_sensor_observations(sim_settings["default_agent"])
# 2.经过step之后再获取观察,此时agent会接地,因此此时agent的状态与上面设置的状态可能有所差别
observations = sim.step('stay_quiet')

[habitat-sim]获取场景语义信息

  • 参考:https://github.com/facebookresearch/habitat-sim/issues/2364

[habitat-sim]使用ShortestFollower

在Habitat-sim中使用ShortedFollower,可以参考样例Notebook

[habitat-lab]获取state

在Habitat-lab中获取Agent的state:

1
2
3
4
5
6
env = habitat.Env(config=config)

obs = env.reset()
initial_state = env._sim.get_agent_state(0)
init_translation = initial_state.position
init_rotation = initial_state.rotation

Sensor的state则包含在agent的state中。

[habitat-lab]构造new sensor

这里说明如何构造simulator.sim_sensor

  • 在Config系统中注册相关配置。config系统可以参考文档

  • 如果需要额外的配置信息,也需要在config中指定增加配置

  • 实现相关的类型,进行register

    • 在Habitat中,如果不给@registry.register_sensor装饰器指定名字参数,它会使用类名作为默认的注册名。
  • 目前可用的config key,可以参考文档

  • 注意simluator.sim_sensor在最终配置系统中Package的变化:

    • ```Bash /gsim/simulator/sensors@habitat.simulator.agents.main_agent: gs_rgb_sensor
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43

      ## [habitat-lab]构造基于ReplicaCAD的PointNav

      引入ReplicaCAD的关键在于Env中的Sim配置需要对齐,包括scene_dataset_config_json的指定,以及scene_id的指定。这里列出了一些需要解决的问题

      - sim使用的是封装了habitat_sim的[HabtatSim](https://github.com/facebookresearch/habitat-lab/blob/main/habitat-lab/habitat/sims/habitat_simulator/habitat_simulator.py#L269)
      - habitat_sim的config和habitat_lab的config之间关键参数的对应「[reference](https://github.com/facebookresearch/habitat-lab/blob/main/habitat-lab/habitat/sims/habitat_simulator/habitat_simulator.py#L324-L327)」
      - habitat_sim.config.scene_dataset_config.file <=> habitat_lab.config.simulator.scene_datset
      - habitat_sim.config.scene_id <=> habitat_lab.config.scene
      - habitat_lab.config.simulator.scene=habitat_lab.config.simulator.scene_dir + episode.scene_id「in train.json」

      - 我们不能够直接在habitat-lab yaml中指定habitat.simulator.scene_dataset,因为它会被episode中的scene_dataset_config覆盖「[reference](https://github.com/facebookresearch/habitat-lab/blob/main/habitat-lab/habitat/core/env.py#L104-L108)」


      因此最终的解决方案是:

      - 在yaml中如下指定,其中scene_dir设置为空字符串:

      ```yaml
      # @package _global_

      defaults:
      - pointnav_base
      - _self_

      habitat:
      environment:
      max_episode_steps: 500
      simulator:
      agents:
      main_agent:
      sim_sensors:
      rgb_sensor:
      width: 256
      height: 256
      depth_sensor:
      width: 256
      height: 256
      dataset:
      type: PointNav-v1
      split: train
      data_path: data/datasets/pointnav/replica_cad_test/v1/{split}/{split}.json.gz
      scenes_dir: ""
  • 在train.json中如下指定,其中通过scene_dataset_config指定为对应的json路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"episodes": [
{
"episode_id": 0,
"scene_id": "apt_0",
"scene_dataset_config": "<your_path>/data/replica_cad/replicaCAD.scene_dataset_config.json",
"start_position": [-1.2600001, 0.11937292, 0],
"start_rotation": [0, 0, 0, 1],
"info": {
"geodesic_distance": 6.1,
"difficulty": "easy"
},
"goals": [
{
"position": [2.652453, 0.11937272, 6.1090064],
"radius": 0.2
}
],
"shortest_paths": null,
"start_room": null
}
]
}
  • 这里还需要注意rotation的指定,在train.json中指定的start_rotation的顺序是(x, y, z, w)reference」,但是在agent中获得的rotation输出的顺序是(w, x, y, z)「该rotation的类型是quaternion.quaternion,这个类对应的顺序是wxyz」

[habitat-lab]获取habitat-sim相关的配置内容

在habitat-lab中,可以通过某种方式获取到scene_dataset_config.json里面的内容。关于Config属性可以参考说明文档。要获取对应的Config属性,需要获取template_manager,然后获取其中的key。

  • 相关manager:reference
  • Attributes对应的C++实现位置
  • stage和object的缩放和平移在两个配置文件中有定义
    • stage:「stage的缩放好像没有生效?」
      • stage_config.json中
        • up: y
        • front: -z
        • origin: xyz
        • scale: 缩放
      • scene_instance.json中的stage_instance:
        • translation xyz
        • rotation wxyz
        • uniform_scale
        • non_uniform_scale
    • object:
      • object_config.json中
        • up:y
        • front:-z
        • COM:xyz
        • scale:缩放
      • scene_instance.json中的object_instances:
        • translation xyz
        • rotation wxyz
        • uniform_scale
        • non_uniform_scale

[habitat-lab]episode切换的流程

以下主要介绍在habitat-lab中切换episode实际上经过的操作:

  1. 首先在初始化Env的时候,会依次初始化dataset「List of Episode」,sim「simulator」和task 「EmbodiedTask」
  2. 通过env.reset()进行episode的切换
  • 获取next episode
  • Reconfigure
    • task.overwrite_sim_config
    • sim.reconfigure(simulator_config+current_episode)
  • 在task中进行reset
  • task.mesurements.rest_measures():重置相关指标
  1. 但是需要注意的是next epsiode的处理逻辑,它并不是完全按照顺序一个个给出指定的env.episodes中的值,而是会构造一个迭代器,这个迭代器中顺序会尽可能减少场景的切换,参考habitat-lab中的EpisodeIterator

[habitat-lab]实现过程中场景物体的控制

在habitat-lab中,我们可以在过程中控制场景中的物体,包括物体的种类、位置等。一种实现思路是:在episode的描述文件中添加需要在该episode中加入的物体,然后在切换episode的时候,将对应的物体加载到场景中。其中的核心类是habitat_sim中的RigidObjectManager「动态管理的API参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# self为Simulator类

# 获取RigidObjectManager
rigid_obj_mgr = self.get_rigid_object_manager()
rigid_obj_mgr.remove_all_objects()

# 通过对应配置文件的handle来实例化对应的物体
object_box = rigid_obj_mgr.add_object_by_template_handle(object_config_path)

# 修改物体的相关属性
# 相关的Package
# import magnum as mn
# from habitat_sim._ext.habitat_sim_bindings import MotionType
object_box.translation = np.array([1, 2, 3])
object_box.rotation = mn.Quaternion(mn.Vector3(q.x, q.y, q.z), q.w)
object_box.motion_type = MotionType.STATIC
...

habitat-基础使用记录
http://example.com/2025/05/07/habitat-基础使用记录/
作者
EverNorif
发布于
2025年5月7日
许可协议