【机器人】复现 HOV-SG 机器人导航 | 分层 开放词汇 | 3D 场景图
HOV-SG 是通过语言指令实现机器人导航的,核心特点是分层结构、开放词汇、3D场景图。
来自RSS 2024,大规模、多层次的环境构建精确的、开放词汇的3 场景图,并使机器人能够通过语言指令在其中有效地导航。
论文地址:Hierarchical Open-Vocabulary 3D Scene Graphs for Language-Grounded Robot Navigation
代码地址:https://github.com/hovsg/HOV-SG
本文分享HOV-SG复现和模型推理的过程~
下面是一个3D场景图的示例:
1、创建Conda环境
首先下载HOV-SG代码,进行工程目录
git clone https://github.com/hovsg/HOV-SG.gitcd HOV-SG
创建一个Conda环境,名字为hovsg,参考environment.yaml中的要求进行创建,如下所示:
name: hovsgchannels: - defaults - pytorchdependencies: - python=3.9 - numpy - pytorch::faiss-gpu - pip - pip: - matplotlib==3.7.3 - scipy==1.13.1 - open3d==0.18.0 - opencv-python - git+https://github.com/facebookresearch/segment-anything.git - torchmetrics - ftfy - tqdm - open-clip-torch - transformers - openai==1.3.7 - plyfile - hydra-core - pyvista - scikit-fmm - pathos - opencv-python-headless==4.8.1.78
再进行SG_Nav环境,上面的两条命令对应为:
conda env create -f environment.yamlconda activate hovsg
安装成功打印信息:
2、安装habitat模拟器
我们需要安装habitat-sim 和 habitat-lab
conda install habitat-sim -c conda-forge -c aihabitat
等待安装完成~
3、安装依赖库
执行下面命令进行安装:
pip install -e .
安装成功打印信息:
2025/7/ 补丁1:pip install numpy==1.24.3
补丁2:先卸载 pip uninstall faiss -y,再安装 pip install faiss-cpu==1.7.4
补丁3:将 Matplotlib 降级到 3.4.x 版本,
pip install matplotlib==3.4.3
4、配置OpenCLIP
HOV-SG 使用 Open CLIP 模型从 RGB-D 帧中提取特征。
要下载 Open CLIP 模型权重,CLIP-ViT-H-14-laion2B-s32B-b79K
请参阅Open CLIP。
执行下面命令,进行下载 CLIP模型权重:
mkdir checkpointswget https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_pytorch_model.bin?download=true -O checkpoints/temp_open_clip_pytorch_model.bin && mv checkpoints/temp_open_clip_pytorch_model.bin checkpoints/laion2b_s32b_b79k.bin
等待下载完成:
(可选)另一个选择是使用 OVSeg 微调的 Open CLIP 模型
执行下面命令,进行下载型权重:
pip install gdowngdown --fuzzy https://drive.google.com/file/d/17C9ACGcN7Rk4UT4pYD_7hn3ytTa3pFb5/view -O checkpoints/ovseg_clip.pth
4、配置SAM
HOV-SG 使用SAM为 RGB-D 帧生成与类别无关的分割掩码。
执行下面命令,进行下载 SAM模型权重:
wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth -O checkpoints/sam_vit_h_4b8939.pth
等待下载完成:
5、准备HM3D数据集
下载地址:https://github.com/matterport/habitat-matterport-3dresearch
选择“Downloading HM3D v0.2”的 val四个文件:
下载完成后,创建一个data/hm3d/val目录
mkdir -p data/hm3d/val
然后将hm3d-val-glb-v0.2、hm3d-val-habitat-v0.2、hm3d-val-semantic-annots-v0.2目录,复制合并在val内
进入val内的某个子文件夹是这样的:
最终形成这样的目录结构:
├── hm3d
│ ├── hm3d_annotated_basis.scene_dataset_config.json # this file is necessary
│ ├── val
│ │ └── 00824-Dd4bFSTQ8gi
│ │ ├── Dd4bFSTQ8gi.basis.glb
│ │ ├── Dd4bFSTQ8gi.basis.navmesh
│ │ ├── Dd4bFSTQ8gi.glb
│ │ ├── Dd4bFSTQ8gi.semantic.glb
│ │ └── Dd4bFSTQ8gi.semantic.txt
...
...
...
其中后面会用到场景ID包括:
00824-Dd4bFSTQ8gi
00829-QaLdnwvtxbs
00843-DYehNKdT76V
00861-GLAQ4DNUx5U
00862-LT9Jq6dN3Ea
00873-bxsVRursffK
00877-4ok3usBNeis
00890-6s7QHgap2fW
6、姿态转为RGB-D观测值
首先复制hovsg/data/hm3dsem/metadata/poses到 data/hm3dsem_poses中
# 创建data/hm3dsem_poses文件夹mkdir data/hm3dsem_poses# 执行复制命令cp hovsg/data/hm3dsem/metadata/poses/* data/hm3dsem_poses/
复制后是这样的:
运行代码:
python hovsg/data/hm3dsem/gen_hm3dsem_walks_from_poses.py --dataset_dir data/hm3d/ --save_dir data/hm3dsem_walks/
运行打印信息:
(hovsg) lgp@lgp-MS-7E07:~/2025_project/HOV-SG$ python hovsg/data/hm3dsem/gen_hm3dsem_walks_from_poses.py --dataset_dir data/hm3d/ --save_dir data/hm3dsem_walks//home/lgp/anaconda3/envs/hovsg/lib/python3.9/site-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.17.3 and ={np_minversion} and <{np_maxversion}\"scene: data/hm3d//val/00824-Dd4bFSTQ8gi/Dd4bFSTQ8gi.glb-------------data/hm3d//val/00824-Dd4bFSTQ8gi/Dd4bFSTQ8gi.basis.glbdata/hm3d/hm3d_annotated_basis.scene_dataset_config.json-------------{}agent_state: position [ 1.0737159 0.0688231 -2.2800648] rotation quaternion(1, 0, 0, 0)saving frame 2252/2254: 100%|█████████████████████████████████████████████████████████████| 2253/2253 [01:18<00:00, 28.64it/s]scene: data/hm3d//val/00829-QaLdnwvtxbs/QaLdnwvtxbs.glb-------------data/hm3d//val/00829-QaLdnwvtxbs/QaLdnwvtxbs.basis.glbdata/hm3d/hm3d_annotated_basis.scene_dataset_config.json-------------{}agent_state: position [-3.6943688 0.13573912 -3.9164376 ] rotation quaternion(1, 0, 0, 0)saving frame 1803/1805: 100%|█████████████████████████████████████████████████████████████| 1804/1804 [01:07<00:00, 26.88it/s]scene: data/hm3d//val/00843-DYehNKdT76V/DYehNKdT76V.glb-------------data/hm3d//val/00843-DYehNKdT76V/DYehNKdT76V.basis.glbdata/hm3d/hm3d_annotated_basis.scene_dataset_config.json
7、创建场景图
修改application/create_graph.py代码,要不然会报错:
FileNotFoundError: [Errno 2] No such file or directory: \'data/hm3dsem_walks/val/00824-Dd4bFSTQ8gi/val/00824-Dd4bFSTQ8gi/rgb\'
示例代码:
import osimport hydrafrom omegaconf import DictConfigfrom hovsg.graph.graph import Graph# pylint: disable=all@hydra.main(version_base=None, config_path=\"../config\", config_name=\"create_graph\")def main(params: DictConfig): # 创建日志目录 save_dir = os.path.join(params.main.save_path, params.main.dataset, params.main.scene_id) params.main.save_path = save_dir # params.main.dataset_path = os.path.join(params.main.dataset_path, params.main.split, params.main.scene_id) print(\"params.main.split:\", params.main.split) print(\"params.main.scene_id:\", params.main.scene_id) print(\"params.main.dataset_path:\", params.main.dataset_path) if not os.path.exists(save_dir): os.makedirs(save_dir, exist_ok=True) # 创建图 from hovsg.graph.graph import Graph # 假设Graph类在这里定义 hovsg = Graph(params) # hovsg.create_feature_map() # 创建特征图 # 保存完整点云、特征和掩码点云(所有对象的pcd) # hovsg.save_masked_pcds(path=save_dir, state=\"both\") # hovsg.save_full_pcd(path=save_dir) # hovsg.save_full_pcd_feats(path=save_dir) # 用于调试:按如下方式加载预构建的地图 hovsg.load_full_pcd(path=save_dir) hovsg.load_full_pcd_feats(path=save_dir) hovsg.load_masked_pcds(path=save_dir) # 仅当数据集不是Replia或ScanNet时创建图 print(params.main.dataset) if params.main.dataset != \"replica\" and params.main.dataset != \"scannet\" and params.pipeline.create_graph: hovsg.build_graph(save_path=save_dir) else: print(\"Skipping hierarchical scene graph creation for Replica and ScanNet datasets.\")if __name__ == \"__main__\": main()
执行下面命令:
python application/create_graph.py main.dataset=hm3dsem main.dataset_path=data/hm3dsem_walks/val/00824-Dd4bFSTQ8gi/ main.save_path=data/scene_graphs/00824-Dd4bFSTQ8gi
运行信息:
(hovsg) lgp@lgp-MS-7E07:~/2025_project/HOV-SG$ python application/create_graph.py main.dataset=hm3dsem main.dataset_path=data/hm3dsem_walks/val/00824-Dd4bFSTQ8gi/ main.save_path=data/scene_graphs/00824-Dd4bFSTQ8gi
params.main.split: val
params.main.scene_id: 00824-Dd4bFSTQ8gi
params.main.dataset_path: data/hm3dsem_walks/val/00824-Dd4bFSTQ8gi/
[2025-06-29 23:52:13,474][root][INFO] - Loaded ViT-H-14 model config.
[2025-06-29 23:52:17,300][root][INFO] - Loading pretrained ViT-H-14 weights (checkpoints/laion2b_s32b_b79k.bin).
Creating RGB-D point cloud: 100%|███████████████████████████████████████████████████████████| 226/226 [00:11<00:00, 19.32it/s]
Extracting features: 46%|██████████████████████████████▎ | 104/226 [06:09<07:12, 3.55s/it]
生成目录文件
运行成功打印信息:
segmenting/identifying objects...
Floor: 0: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [03:59<00:00, 239.40s/it]
number of objects: 1204
creating graph...
Getting paths between all nodes. Node number: 892/4246
time for computing all pairs shortest path: 205.90228915214539 seconds
Sparsifying graph: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████| 892/892 [00:01<00:00, 470.88it/s]
connecting stairs and floor 0
# floors: 1
# rooms: 7
# objects: 1204
--> HOV-SG representation successfully built
8、可视化场景图
需要先修改代码 application/visualize_graph.py
import osimport jsonimport globimport numpy as npfrom collections import defaultdictimport hydraimport open3d as o3d # 用于点云数据处理的库import matplotlib.pyplot as plt # 用于颜色映射生成import pyvista as pv # 用于3D可视化的库from tqdm import tqdm # 用于进度条显示(当前代码未使用)from omegaconf import DictConfig # 用于处理Hydra配置def get_cmap(n, name=\"hsv\"): \"\"\"返回一个函数,将0, 1, ..., n-1范围内的每个索引映射到不同的RGB颜色 关键字参数name必须是matplotlib的标准颜色映射名称\"\"\" return plt.cm.get_cmap(name, n)@hydra.main(version_base=None, config_path=\"../config\", config_name=\"visualize_graph\")def main(params: DictConfig): # 初始化PyVista可视化器 p = pv.Plotter() # 加载楼层的PLY点云文件路径和对应的JSON元数据路径 floors_ply_paths = sorted(glob.glob(os.path.join(params.graph_path, \"floors\", \"*.ply\"))) floors_info_paths = sorted(glob.glob(os.path.join(params.graph_path, \"floors\", \"*.json\"))) # 初始化用于存储点云和元数据的数据结构 floor_pcds = {} # 存储楼层点云,键为floor_id floor_infos = {} # 存储楼层元数据,键为floor_id hier_topo = defaultdict(dict) # 层次拓扑结构:floor_id -> room_id -> [object_id列表] init_offset = np.array([7.0, 2.5, 4.0]) # 可视化时各楼层的初始偏移量(用于分层显示) # 处理每个楼层数据 for counter, (ply_path, info_path) in enumerate(zip(floors_ply_paths, floors_info_paths)): # 读取楼层元数据JSON文件 with open(info_path, \"r\") as fp: floor_info = json.load(fp) # 存储相关的楼层元数据(筛选需要的字段) floor_infos[floor_info[\"floor_id\"]] = { k: v for k, v in floor_info.items() if k in [\"floor_id\", \"name\", \"rooms\", \"floor_height\", \"floor_zero_level\", \"vertices\"] } # 为每个楼层应用可视化偏移量(通过counter实现不同楼层的偏移) floor_infos[floor_info[\"floor_id\"]][\"viz_offset\"] = init_offset * counter # 在层次拓扑中初始化该楼层包含的房间 for r_id in floor_info[\"rooms\"]: hier_topo[floor_info[\"floor_id\"]][r_id] = [] # 读取楼层点云数据 floor_pcds[floor_info[\"floor_id\"]] = o3d.io.read_point_cloud(ply_path) # 检查是否加载到楼层数据 if not hier_topo: print(f\"错误:在{params.graph_path}/floors路径下未找到楼层数据\") return # 加载房间的PLY点云文件路径和对应的JSON元数据路径 rooms_ply_paths = sorted(glob.glob(os.path.join(params.graph_path, \"rooms\", \"*.ply\"))) rooms_info_paths = sorted(glob.glob(os.path.join(params.graph_path, \"rooms\", \"*.json\"))) # 初始化用于存储房间点云和元数据的数据结构 room_pcds = {} # 存储房间点云,键为room_id room_infos = {} # 存储房间元数据,键为room_id # 处理每个房间数据 for ply_path, info_path in zip(rooms_ply_paths, rooms_info_paths): # 读取房间元数据JSON文件 with open(info_path, \"r\") as fp: room_info = json.load(fp) # 存储相关的房间元数据(筛选需要的字段) room_infos[room_info[\"room_id\"]] = { k: v for k, v in room_info.items() if k in [\"room_id\", \"name\", \"floor_id\", \"room_height\", \"room_zero_level\", \"vertices\"] } # 将房间包含的物体ID添加到层次拓扑中 for o_id in room_info[\"objects\"]: hier_topo[room_info[\"floor_id\"]][room_info[\"room_id\"]].append(o_id) # 加载房间点云并应用过滤(去除天花板附近的点) orig_cloud = o3d.io.read_point_cloud(ply_path) orig_cloud_xyz = np.asarray(orig_cloud.points) # 将点云转换为numpy数组 # 计算天花板下方的过滤条件(距离天花板0.4米以内的点被过滤) below_ceiling_filter = ( orig_cloud_xyz[:, 1] # y轴坐标(假设代表高度) 100 # 过滤点数量过少的物体 ): # 添加线条连接物体中心点和所属房间的中心点 p.add_mesh( pv.Line( tuple(room_centroids_viz[obj_info[\"room_id\"]]), # 起点:所属房间的可视化中心点 tuple(obj_centroids_viz[obj_id]) # 终点:物体的可视化中心点 ), line_width=1.5, # 线条宽度 opacity=0.5, # 透明度 ) print(\"已包含类别为以下的物体:\", obj_info[\"name\"]) # 为物体点云随机赋色 object_pcds[obj_id].paint_uniform_color(np.random.rand(3)) # 转换为PyVista格式并添加到可视化器 cloud_xyz = np.asarray(object_pcds[obj_id].points) cloud = pv.PolyData(cloud_xyz) p.add_mesh( cloud, scalars=np.asarray(object_pcds[obj_id].colors), # 使用点云颜色 rgb=True, # 启用RGB颜色模式 point_size=5, # 点大小 show_vertices=True, # 显示顶点 ) # 显示可视化结果 if len(p.actors) == 0: # 检查是否有可显示的元素 print(\"警告:没有可可视化的内容,请检查输入数据是否正确。\") else: p.show() # 显示3D可视化窗口if __name__ == \"__main__\": main()
执行下面命令:
python application/visualize_graph.py graph_path=data/scene_graphs/00824-Dd4bFSTQ8gi/hm3dsem/00824-Dd4bFSTQ8gi/graph
示例效果:(构建一个 3D场景图层次结构,包括 floor、room 和 object)
不同视角:
构建完3D场景图后,用于导航查询
示例效果:
分享完成~