Skip to content

LIBERO文档阅读笔记

Preface

关于 LIBERO 这个终身机器人benchmark工具的文档阅读笔记。

Aim:如何使用 LIBERO 跑通实验,及如何外部导入 3D 模型构建本地数据集

网址:https://lifelong-robot-learning.github.io/LIBERO/html/index.html

代码结构

环境:

1
2
3
4
5
6
7
8
9
10
11
12
libero/
assets/ # 物体/场景的视觉与物理资产
bddl_files/ # 任务定义
libero_goal/*.bddl # 10 tasks of LIBERO-Goal suite
libero_spatial/*.bddl # 10 tasks of LIBERO-Spatial suite
libero_object/*.bddl # 10 tasks of LIBERO-Object suite
libero_10/*.bddl # 10 tasks of LIBERO-100 for evaluation (aka LIBERO-LONG)
libero_90/*.bddl # 90 tasks of LIBERO-100 for pretraining
benchmark/ # benchmark 怎么组织任务、按什么顺序评测
envs/ # MuJoCo / robosuite 环境本体
init_files/ # 固定好的初始状态
utils/ # 工具函数

算法与实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lifelong/      # Files for algorithms, models, and training / testing
main.py # the main script for reproducing experiments.
algos/
base.py # Base class `Sequential` for all the algorithms
er.py # Algorithm Experience Replay
ewc.py # Algorithm Elastic Weight Consolidation
packnet.py # Algorithm Packnet
multitask.py # Algorithm multitask (baseline)
single_task.py # Algorithm single task (baseline)
language.py

models/
policy/
bc_rnn_policy.py # ResNet-RNN
bc_transformer_policy.py # ResNet-T
bc_vilt_policy.py # ViT-T

创建数据集:

1
2
3
4
scripts/
collect_demonstrations.py # Collect your own demonstrations
create_dataset.py # Create your own dataset
batch_create_dataset.py # Create a batch of datasets by calling create_dataset.py repeatedly

场景创建

Step 1 准备 mesh 文件

可能有用的资源:obj2mjcf

mesh 文件位置:libero/libero/assets

Step 2 创造问题类

问题类定义了场景的区域。在区域上,初始状态被创建,物体放置于此。

问题类定义文件位置: libero/libero/envs/problems

模板: template

问题

  1. 什么是mesh文件,mesh文件定义了什么东西?

    mesh文件可以理解为一个物体的3D外壳/几何表面。计算机存储物体时,使用大量三角形近似它的表面。

    一个mesh的核心信息包括:

    1
    2
    3
    4
    vertices      # 顶点坐标
    faces # 哪几个顶点组成一个三角面
    normals # 表面朝哪个方向
    UV coordinates # 纹理图片怎么贴到表面

    LIBERO常用的mesh文件格式是 .obj

    mesh文件只定义物体形状,关于质量、摩擦系数等这些物理参数,通常由MJCF/XML进一步定义。

  2. obj2mjcf 是什么,如何使用?

    MJCF = MuJoCo XML Configuration Format,就是 MuJoCo 描述仿真物体/场景的 XML 格式。

    obj2mjcf 是一个命令行工具,专门帮你把 .obj 模型整理成 MuJoCo 更容易使用的形式。官方说明它主要做三件事:

    • 把包含多个 material 的复杂 OBJ 拆成若干 sub-mesh;
    • 自动生成包含 mesh、material、geom 引用的 MJCF XML;
    • 可选地用 CoACD 做 convex decomposition,从而产生更适合作为 collision geometry 的网格。

    安装后运行 obj2mjcf --help 运行结果:

    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
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    A CLI for processing composite Wavefront OBJ files for use in MuJoCo.

    ╭─ options ──────────────────────────────────────────╮
    │ -h, --help
    │ show this help message and exit
    │ --obj-dir STR │
    │ path to a directory containing obj files. │
    │ All obj files in the directory will be │
    │ converted (required) │
    │ --obj-filter {None}|STR │
    │ only convert obj files matching this regex │
    │ (default: None) │
    │ --save-mjcf, --no-save-mjcf │
    │ save an example XML (MJCF) file (default: │
    │ False) │
    │ --compile-model, --no-compile-model │
    │ compile the MJCF file to check for errors │
    │ (default: False) │
    │ --verbose, --no-verbose │
    print verbose output (default: False) │
    │ --decompose, --no-decompose │
    │ approximate mesh decomposition using CoACD │
    │ (default: False) │
    │ --texture-resize-percent FLOAT │
    │ resize the texture to this percentage of the │
    │ original size (default: 1.0) │
    │ --overwrite, --no-overwrite │
    │ overwrite previous run output (default: │
    │ False) │
    │ --add-free-joint, --no-add-free-joint │
    │ add a free joint to the root body (default: │
    │ False) │
    ╰────────────────────────────────────────────────────╯
    ╭─ coacd-args options ───────────────────────────────╮
    │ arguments to pass to CoACD │
    │ ────────────────────────────────────────────────── │
    │ --coacd-args.preprocess-resolution INT │
    │ resolution for manifold preprocess (20~100), │
    │ default = 50 (default: 50) │
    │ --coacd-args.threshold FLOAT │
    │ concavity threshold for terminating the │
    │ decomposition (0.01~1), default = 0.05 │
    │ (default: 0.05) │
    │ --coacd-args.max-convex-hull INT │
    │ max # convex hulls in the result, -1 for no │
    │ maximum limitation (default: -1) │
    │ --coacd-args.mcts-iterations INT │
    │ number of search iterations in MCTS │
    │ (60~2000), default = 100 (default: 100) │
    │ --coacd-args.mcts-max-depth INT │
    │ max search depth in MCTS (2~7), default = 3 │
    │ (default: 3) │
    │ --coacd-args.mcts-nodes INT │
    │ max number of child nodes in MCTS (10~40), │
    │ default = 20 (default: 20) │
    │ --coacd-args.resolution INT │
    │ sampling resolution for Hausdorff distance │
    │ calculation (1e3~1e4), default = 2000 │
    │ (default: 2000) │
    │ --coacd-args.pca, --coacd-args.no-pca │
    enable PCA pre-processing, default = false
    │ (default: False) │
    │ --coacd-args.seed INT │
    │ random seed used for sampling, default = 0 │
    │ (default: 0) │
    ╰────────────────────────────────────────────────────╯

    一般使用流程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    网上/Blender 找到一个 phone.obj

    obj2mjcf

    整理 mesh
    生成/辅助生成 MJCF XML
    生成 collision mesh

    放进 LIBERO assets

    让 LIBERO 把它实例化成 PhoneObject
  3. 问题类长什么样,如何创建?

    Problem Class 实际上就是 LIBERO 中用来描述“世界怎么搭起来”的 Python 类。

    Problem class 的定义根据提供的模板修改,其中需要重点修改的函数有:

    • __init__ :设置一些参数;
    • _load_sites_in_arena :加载场景;
    • _setup_camera :设置摄像头。

    对于和现有 LIBERO 场景结构相似的新 Problem Class,通常主要定制 __init___load_sites_in_arena_setup_camera;其余方法可以沿用模板的通用实现。特殊 fixture/object/placement/success logic 才需要额外修改。

任务生成

Step 1 创建场景(previous chapter)

Step 2 布局与初始状态分布

2.1 在codebases里面获取物体和谓词

查看已有数据库中的物体:

1
2
3
4
5
from libero.libero.envs.objects import get_object_dict, get_object_fn

# Get a dictionary of all the objects
object_dict = get_object_dict()
print(object_dict)

获取物体,并查看所在的类别:

1
2
3
category_name = "moka_pot"
object_cls = get_object_fn(category_name)
print(category_name, ": defined in the class ", object_cls)

获取谓词的相关信息:

1
2
3
4
5
6
7
from libero.libero.envs.predicates import get_predicate_fn_dict, get_predicate_fn

predicate_dict = get_predicate_fn_dict()
print(predicate_dict)
print("=============")
predicate_name = "on"
print(get_predicate_fn(predicate_name))

2.2 定义布局与初始状态分布

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import numpy as np
from libero.libero.utils.bddl_generation_utils import get_xy_region_kwargs_list_from_regions_info
from libero.libero.utils.mu_utils import register_mu, InitialSceneTemplates
from libero.libero.utils.task_generation_utils import register_task_info, get_task_info, generate_bddl_from_task_info
# 以下是布局与初始状态分布的主体代码
@register_mu(scene_type="kitchen") #
class KitchenScene1(InitialSceneTemplates):
def __init__(self):
# 定义固定物体和移动物体及数量
fixture_num_info = {
"kitchen_table": 1,
"wooden_cabinet": 1,
}

object_num_info = {
"akita_black_bowl": 1,
"plate": 1,
}

super().__init__(
workspace_name="kitchen_table",
fixture_num_info=fixture_num_info,
object_num_info=object_num_info
)

def define_regions(self):
# 初始化物体的位置
self.regions.update(
self.get_region_dict(region_centroid_xy=[0.0, -0.30],
region_name="wooden_cabinet_init_region",
target_name=self.workspace_name,
region_half_len=0.01,
yaw_rotation=(np.pi, np.pi))
)

self.regions.update(
self.get_region_dict(region_centroid_xy=[0., 0.0],
region_name="akita_black_bowl_init_region",
target_name=self.workspace_name,
region_half_len=0.025)
)

self.regions.update(
self.get_region_dict(region_centroid_xy=[0.0, 0.25],
region_name="plate_init_region",
target_name=self.workspace_name,
region_half_len=0.025)
)
self.xy_region_kwargs_list = get_xy_region_kwargs_list_from_regions_info(self.regions)

@property
def init_states(self):
# 定义物体初始状态的逻辑关系
states = [
("On", "akita_black_bowl_1", "kitchen_table_akita_black_bowl_init_region"),
("On", "plate_1", "kitchen_table_plate_init_region"),
("On", "wooden_cabinet_1", "kitchen_table_wooden_cabinet_init_region")]
return states

2.3 定义任务目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scene_name = "kitchen_scene1"
language = "Your Language 1"
register_task_info(language,
scene_name=scene_name,
objects_of_interest=["wooden_cabinet_1", "akita_black_bowl_1"],
goal_states=[("Open", "wooden_cabinet_1_top_region"), ("In", "akita_black_bowl_1", "wooden_cabinet_1_top_region")]
)

# Create another task with the same scene layout
scene_name = "kitchen_scene1"
language = "Your Language 2"
register_task_info(language,
scene_name=scene_name,
objects_of_interest=["wooden_cabinet_1", "akita_black_bowl_1"],
goal_states=[("Open", "wooden_cabinet_1_top_region"), ("In", "akita_black_bowl_1", "wooden_cabinet_1_bottom_region")]
)

任务目标将以命名元组libero.libero.utils.task_ Generation_utils.TaskInfoTuple的格式临时保存在变量libero.libero.utils.task_ Generation_utils.TASK_INFO中。这样的设计目的是为了方便批量创建任务。

查看PDDL文件名称:

1
2
3
4
5
6
7
8
9
10
# This is the default path to store all the pddl scene files. Here we store the files in the temporary folder. If you want to directly add files into the libero codebase, get the default path use the following commented lines:
# from libero.libero import get_libero_path
# YOUR_BDDL_FILE_PATH = get_libero_path("bddl_files")

YOUR_BDDL_FILE_PATH = "tmp/pddl_files"
bddl_file_names, failures = generate_bddl_from_task_info(folder=YOUR_BDDL_FILE_PATH)

print(bddl_file_names)

print("Encountered some failures: ", failures)

自动生成PDDL文件:

1
2
3
with open(bddl_file_names[0], "r") as f:
content = f.read()
print(content)

Step 3 指定目标和语言指令

1
2
3
4
5
6
7
8
# kitchen_scene_example
scene_name = "kitchen_scene_example"
language = "open the top drawer of the cabinet and put the bowl in it"
register_task_info(language,
scene_name=scene_name,
objects_of_interest=["wooden_cabinet_1", "akita_black_bowl_1"],
goal_states=[("Open", "wooden_cabinet_1_top_region"), ("In", "akita_black_bowl_1", "wooden_cabinet_1_top_region")]
)

请注意,objects_of_interest是一个可选字段,如果您希望修改 LIBERO 问题类以跟踪某些特定对象的内部模拟状态,则可以使用此字段。

添加外部 3D 物体步骤(以 egg 为例)

  1. 从外部网站(如 free3D.com)下载 3D 模型。模型文件格式应为 .obj 和模型预览图 .jpg 。最好是单一物体、非 articulated、面数适中。

  2. 使用文档给出的工具 obj2mjcf 处理 .obj 文件,确认 .obj 文件可被 MuJoCo 解析。(此处生成的 XML 文件通常只是参考,不一定能直接拿来当 LIBERO object)

    1
    2
    3
    4
    5
    obj2mjcf \
    --obj-dir playground/custom_assets/egg \
    --save-mjcf \
    --compile-model \
    --verbose
  3. 检查模型尺寸。运行命令行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    python - <<'PY'
    import trimesh

    mesh = trimesh.load(
    "playground/custom_assets/egg/egg/egg.obj",
    force="mesh"
    )

    print("bounds:", mesh.bounds)
    print("extents:", mesh.extents)
    print("centroid:", mesh.centroid)
    PY

    根据 extents 算真实缩放比例。比如一个鸡蛋,原始长轴约5.32个单位,想做成约6cm,故设置:

    1
    scale="0.01127 0.01127 0.01127"
  4. 把处理后的 mesh 放进 LIBERO assets,文件位置为:

    1
    2
    3
    libero/libero/assets/custom_objects/egg/
    ├── egg.obj
    └── egg.xml
  5. 自己写正式的 egg.xml。这里最好把 visual 和 collision 分开:visual mesh → 外部真实 egg.obj,collision geom → 简单 ellipsoid。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <mesh
    name="egg_visual_mesh"
    file="egg.obj"
    scale="0.01127 0.01127 0.01127"
    />

    <geom
    type="mesh"
    mesh="egg_visual_mesh"
    contype="0"
    conaffinity="0"
    group="1"
    />

    <geom
    type="ellipsoid"
    size="0.0217 0.0217 0.0300"
    density="600"
    friction="0.8 0.3 0.1"
    group="0"
    />
    <site name="bottom_site" ... />
    <site name="top_site" ... />
    <site name="horizontal_radius_site" ... />
  6. 写 LIBERO object class。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @register_object
    class Egg(MujocoXMLObject):
    def __init__(self, name="egg"):
    super().__init__(
    ".../assets/custom_objects/egg/egg.xml",
    name=name,
    joints=[dict(type="free", damping="0.0005")],
    obj_type="all",
    duplicate_collision_geoms=False,
    )

    self.category_name = "egg"

    self.rotation = (0.0, 0.0)
    self.rotation_axis = "z"

    self.object_properties = {
    "vis_site_names": {}
    }
  7. libero/libero/envs/objects/__init__.py 里加上:

    1
    from .custom_objects import *

    验证:

    1
    2
    3
    4
    5
    6
    python - <<'PY'
    from libero.libero.envs.objects import get_object_fn, get_object_dict

    print("egg" in get_object_dict())
    print(get_object_fn("egg"))
    PY

    理想输出:

    1
    2
    True
    <class '...Egg'>
  8. 在 Initial Scene 里声明新物体:

    1
    2
    3
    4
    5
    object_num_info = {
    "egg": 1,
    "tomato_sauce": 1,
    "basket": 1,
    }
  9. define_regions() 里给 egg 定义初始化区域:

    1
    2
    3
    4
    5
    6
    7
    8
    self.regions.update(
    self.get_region_dict(
    region_centroid_xy=[-0.20, -0.10],
    region_name="egg_init_region",
    target_name=self.workspace_name,
    region_half_len=0.08,
    )
    )
  10. init_states 里明确让 Egg 放到对应 region:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @property
    def init_states(self):
    return [
    (
    "On",
    "egg_1",
    "living_room_table_egg_init_region",
    ),
    ]
  11. 在 task 里正常用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    register_task_info(
    language="pick up the egg and place it in the basket",
    scene_name="living_room_scene_custom",
    objects_of_interest=[
    "egg_1",
    "basket_1",
    ],
    goal_states=[
    (
    "In",
    "egg_1",
    "basket_1_contain_region",
    ),
    ],
    )
  12. 在终端重新生成 BDDL 文件:

    1
    2
    rm -rf playground/generated_bddl
    python playground/create_custom_tasks.py
  13. 最后加载模型:

    1
    python playground/preview_custom_task.py

About this Post

This post is written by Ezra Yang, licensed under CC BY-NC 4.0.

#notes #Embodied AI #research

Comments