WSL2下复现FoundationPose

1.准备工作

下载仓库

1
git clone https://github.com/NVlabs/FoundationPose.git

下载权重

前往此处下载权重,放在weights/目录下。

下载测试数据集

前往此处下载测试数据,解压到demo_data/目录下。

下载训练数据(可选)

如果要自己train的话,前往此处下载大规模训练数据

下载经过预处理的参考视图(可选)

如果要跑model-free的少样本学习版本, 前往此处下载,解压到demo_data/目录下。

下载YCB-Video数据集(可选)

如果需要YCB-Video数据集,这是一个200G+的数据集,BOP版做了筛选,在100G左右,前往此处下载,解压到demo_data/目录下。

其他细节看这篇:FoundationPose复现及Realsense应用-阿里云开发者社区

2.环境配置

安装eigen3

1
sudo apt install libeigen3-dev

安装Conda

……

安装Cuda

见另一篇博客**《WSL下配置Cuda》**

创建Conda环境

1
conda create -n foundationpose python=3.9

安装依赖

Step1.

把requirements.txt 文件原来的 torch 、torchvision、torchaudio先注释一下,手动安装

1
pip install torch==2.4.0 torchvision==0.19.0 torchaudio==2.4.0 --index-url https://download.pytorch.org/whl/cu124

Step2.

1
python -m pip install -r requirements.txt

Step3.

1
python -m pip install --quiet --no-cache-dir git+https://github.com/NVlabs/nvdiffrast.git

如果下载速度慢或网络卡顿,可以先手动下载到本地后安装

1
2
3
4
5
6
7
8
# 下载源码
git clone https://github.com/NVlabs/nvdiffrast.git

# 进入目录
cd nvdiffrast

# 用 pip 安装(本地路径)
python -m pip install --no-cache-dir .

Step4.

1
python -m pip install --quiet --no-cache-dir kaolin==0.16.0 -f https://nvidia-kaolin.s3.us-east-2.amazonaws.com/torch-2.4.0_cu124.html

Step5.

1
conda install https://anaconda.org/pytorch3d/pytorch3d/0.7.8/download/linux-64/pytorch3d-0.7.8-py39_cu121_pyt241.tar.bz2

安装PyTorch3D 这里也是安装和pytorch对应的具体看这个PyTorch3D 安装-CSDN博客 ,pytorch3d官网地址install pytorch3d version: pytorch3d

Step6.

1
CMAKE_PREFIX_PATH=$CONDA_PREFIX/lib/python3.9/site-packages/pybind11/share/cmake/pybind11 bash build_all_conda.sh

报错:

1
2
error: #error You need C++17 to compile PyTorch
error: #error C++17 or later compatible compiler is required to use PyTorch.

原因是当前的nvcc编译.cu文件时使用了 -std=c++14, Pytorch C++扩展必须使用C++ 17 或更高版本。解决方法是修改编译参数将C++ 14改为C++ 17。

首先清理之前的构建文件

1
2
cd FoundationPose/bundlesdf/mycuda
rm -rf build *.egg-info

确保 C++17 编译(必须),设置 GPU 架构(可选,但推荐)。修改mycudasetup.py

1
2
3
4
5
6
7
8
9
c_flags = ['-O3', '-std=c++17']
nvcc_flags = [
'-O3',
'-std=c++17',
'-U__CUDA_NO_HALF_OPERATORS__',
'-U__CUDA_NO_HALF_CONVERSIONS__',
'-U__CUDA_NO_HALF2_OPERATORS__',
'-gencode', 'arch=compute_89,code=sm_89' # 针对 RTX 4060
]

重新编译

1
python setup.py build_ext --inplace

或者

1
pip install -e . --use-pep517

过时警告:

1
2
3
easy_install command is deprecated
setup.py install is deprecated
Unknown distribution option: 'extra_cflags'

当前 PyTorch/CUDA 扩展依赖的 setup.py 使用了过时方法。pip 25+ 会对 setup.py develop 出现错误。解决方法是使用 PEP 517/518 的方式:

1
pip install -e . --use-pep517

或直接通过 python setup.py build_ext --inplace 编译扩展,而不是 develop。

3.测试Demo

1
python run_demo.py

报错:

1
c++: fatal error: Killed signal terminated program cc1plus

这种情况一般是 编译过程中被系统杀掉了,最常见原因是 内存不足(尤其是 8GB GPU 或内存较小的机器),而不是代码本身的问题。解决方法是降低 CPU 内存占用,或者修改 TORCH_CUDA_ARCH_LIST,避免编译所有架构。

1
2
export MAX_JOBS=1	# 方法1
export TORCH_CUDA_ARCH_LIST="8.9" # 方法2 对应你的 RTX 4060

然后运行

1
python run_demo.py

报错:

1
ImportError: ... libstdc++.so.6: version `GLIBCXX_3.4.32' not found

意思是 系统的 libstdc++ 版本太旧,无法满足 nvdiffrast 插件编译时的要求。这个问题通常发生在 Linux 系统自带的 GCC/标准库版本较旧,而 PyTorch 或 nvdiffrast 需要较新的 GLIBCXX

检查系统当前libstdc++版本,如果没有 GLIBCXX_3.4.32,说明版本过低。

1
strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX

如果有可能是 Python/conda 环境没使用到系统的 libstdc++,或者 nvdiffrast 在编译时链接到了一个旧版本的 libstdc++。

确保conda 优先使用系统 libstdc++

1
2
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH

然后直接运行

1
python run_demo.py

4.自制数据集

数据集文件夹格式

1
2
3
4
5
6
7
8
9
10
.
├── mustard0 # 数据集文件夹
├── cam_K.txt # 相机内参
├── depth # 视频流深度帧
├── masks # 第一帧的掩膜
├── mesh # 3D模型数据
| | ├──texture_map.png # 模型纹理
| | ├──textured_simple.obj # 模型文件
| | ├──textured_simple.obj.mtl # 模型和纹理的桥梁文件
├── rgb # 视频流彩色帧

注意:rgb文件夹下的彩色最好是png,否则要修改读取图片的代码

5.Demo解析

1
2
3
4
import os
from estimater import * # 引入姿态估计模块(自定义的,封装了 FoundationPose)
from datareader import * # 引入数据读取模块
import argparse

引入必要模块

1
2
3
if __name__=='__main__':
parser = argparse.ArgumentParser()
code_dir = os.path.dirname(os.path.realpath(__file__)) # 获取当前脚本所在路径

参数解析器,支持从命令行传参

1
2
3
4
5
6
7
8
9
10
parser.add_argument('--mesh_file', type=str, 
default=f'{code_dir}/demo_data/mustard0/mesh/textured_simple.obj')
parser.add_argument('--test_scene_dir', type=str,
default=f'{code_dir}/demo_data/mustard0')
parser.add_argument('--est_refine_iter', type=int, default=5) # 初始位姿 refinement 迭代次数
parser.add_argument('--track_refine_iter', type=int, default=2) # 跟踪 refinement 迭代次数
parser.add_argument('--debug', type=int, default=1) # debug 等级
parser.add_argument('--debug_dir', type=str, default=f'{code_dir}/debug')
args = parser.parse_args()

定义命令行参数:

  • mesh_file:物体 3D 模型路径
  • test_scene_dir:测试场景(包含 RGBD 数据)的目录
  • est_refine_iter:初始估计 refinement 次数
  • track_refine_iter:跟踪 refinement 次数
  • debug:调试模式(等级越高保存的中间结果越多)
  • debug_dir:调试文件输出目录
1
2
set_logging_format()
set_seed(0)

设置日志格式和随机种子,保证实验可复现。

1
mesh = trimesh.load(args.mesh_file)

加载 3D 网格模型(用 trimesh 库)

1
2
3
debug = args.debug
debug_dir = args.debug_dir
os.system(f'rm -rf {debug_dir}/* && mkdir -p {debug_dir}/track_vis {debug_dir}/ob_in_cam')

清空 debug 文件夹,并新建保存可视化结果的子目录

1
2
to_origin, extents = trimesh.bounds.oriented_bounds(mesh)
bbox = np.stack([-extents/2, extents/2], axis=0).reshape(2,3)

计算 mesh 的 有向包围盒 (OBB),得到:

  • to_origin: mesh 对齐到原点的变换矩阵

    mesh 变换到一个新的坐标系里,在这个坐标系下,物体的 OBB 就是 轴对齐的(盒子中心在原点,边和 XYZ 轴平行)。

    举例:如果原来的 mesh 歪着放,to_origin 就是“把它摆正、放到中心”的变换。

  • extents: 包围盒的长宽高

    [lx, ly, lz]

  • bbox: 最终的 3D 包围盒坐标(min/max)

    一个最大点和一个最小点确定3D包围盒

1
2
3
scorer = ScorePredictor()
refiner = PoseRefinePredictor()
glctx = dr.RasterizeCudaContext()

初始化关键组件:

  • ScorePredictor:评分网络(用于评估姿态的好坏)。
  • PoseRefinePredictor:位姿 refinement 网络。
  • dr.RasterizeCudaContext():基于 CUDA 的可微分渲染器(用来渲染和对比 RGBD)。
1
2
3
4
5
6
7
8
9
10
est = FoundationPose(model_pts=mesh.vertices, 
model_normals=mesh.vertex_normals,
mesh=mesh,
scorer=scorer,
refiner=refiner,
debug_dir=debug_dir,
debug=debug,
glctx=glctx)
logging.info("estimator initialization done")

创建 FoundationPose 实例,传入模型点云、法向量、网格和网络组件。

1
2
3
reader = YcbineoatReader(video_dir=args.test_scene_dir, 
shorter_side=None,
zfar=np.inf)

数据读取器(这里针对 YCB-InEOAT 数据集),返回 RGB、深度、mask 等

1
2
3
4
for i in range(len(reader.color_files)):
logging.info(f'i:{i}')
color = reader.get_color(i)
depth = reader.get_depth(i)

遍历每一帧,读取 RGB 和深度。

1
2
3
4
if i==0:
mask = reader.get_mask(0).astype(bool)
pose = est.register(K=reader.K, rgb=color, depth=depth, ob_mask=mask, iteration=args.est_refine_iter)

在第一帧,调用 register(): 输入相机内参 K、RGB、深度图和物体掩膜,估计物体的初始 6D 姿态。

  if debug>=3:
    m = mesh.copy()
    m.apply_transform(pose)
    m.export(f'{debug_dir}/model_tf.obj')
    xyz_map = depth2xyzmap(depth, reader.K)
    valid = depth>=0.001
    pcd = toOpen3dCloud(xyz_map[valid], color[valid])
    o3d.io.write_point_cloud(f'{debug_dir}/scene_complete.ply', pcd)

如果 debug≥3:

  • 导出变换后的 mesh。
  • 保存完整的点云(场景点云 + RGB 颜色)。
1
2
else:
pose = est.track_one(rgb=color, depth=depth, K=reader.K, iteration=args.track_refine_iter)

后续帧调用 track_one(),根据上一帧的结果进行跟踪。

os.makedirs(f'{debug_dir}/ob_in_cam', exist_ok=True)
np.savetxt(f'{debug_dir}/ob_in_cam/{reader.id_strs[i]}.txt', pose.reshape(4,4))

把每一帧的 4×4 位姿矩阵保存为 txt 文件。、

if debug>=1:
  center_pose = pose@np.linalg.inv(to_origin)
  vis = draw_posed_3d_box(reader.K, img=color, ob_in_cam=center_pose, bbox=bbox)
  vis = draw_xyz_axis(color, ob_in_cam=center_pose, scale=0.1, K=reader.K, thickness=3, transparency=0, is_input_rgb=True)
  cv2.imshow('1', vis[...,::-1])
  cv2.waitKey(1)

如果 debug≥1:

  • 将 3D 包围盒投影到图像上。

  • 画出物体坐标系三轴。

  • 用 OpenCV 实时显示结果。

    if debug>=2:
    os.makedirs(f’{debug_dir}/track_vis’, exist_ok=True)
    imageio.imwrite(f’{debug_dir}/track_vis/{reader.id_strs[i]}.png’, vis)

如果 debug≥2:保存跟踪可视化结果到 track_vis 文件夹。