- 创建环境
scss
conda create -n tsdf python=3.10 -y
conda activate tsdf
# 2.1 升级打包工具
python -m pip install --upgrade pip setuptools wheel
# 2.2 安装核心库(建议直接用 pip 安装到当前 conda 环境)
pip install open3d opencv-python numpy scikit-image numba tqdm
# 2.3 快速自检:打印各库版本
python -c "import open3d as o3d, cv2, numpy as np, skimage, numba, tqdm; print('open3d:', o3d.__version__); print('opencv:', cv2.__version__); print('numpy:', np.__version__); print('skimage:', skimage.__version__); print('numba:', numba.__version__); print('tqdm ok')"
- 下载测试包,带相机参数的深度图 链接 下载后可以看到color里面是正常的照片,depth里面是深度图,其他文件是相机内参等。
- 用官方小样数据跑一个 TSDF 重建
python
import os, glob, json
import numpy as np
import open3d as o3d
# === 1) 指定你的数据目录(你已经解压到这里) ===
DATA_DIR = r".\tsdf_demo\redwood_data"
color_paths = sorted(glob.glob(os.path.join(DATA_DIR, "color", "*.jpg")))
depth_paths = sorted(glob.glob(os.path.join(DATA_DIR, "depth", "*.png")))
assert len(color_paths) == len(depth_paths) and len(color_paths) > 0, "color/depth 数量不匹配或为空"
print(f"[INFO] 找到 {len(color_paths)} 对 RGB-D 图像")
# === 2) 相机内参:优先读 camera_primesense.json,读不到就用常用默认值 ===
intr_json = os.path.join(DATA_DIR, "camera_primesense.json")
if os.path.exists(intr_json):
with open(intr_json, "r", encoding="utf-8") as f:
data = json.load(f)
# Open3D 的示例里常见字段是 intrinsic_matrix(3x3, 行优先)
K = data.get("intrinsic_matrix", None)
if K and len(K) == 9:
fx, fy = K[0], K[4]
cx, cy = K[2], K[5]
width = data.get("width", 640)
height = data.get("height", 480)
else:
# 兜底
fx = fy = 525.0
cx, cy = 319.5, 239.5
width, height = 640, 480
else:
fx = fy = 525.0
cx, cy = 319.5, 239.5
width, height = 640, 480
intr = o3d.camera.PinholeCameraIntrinsic(width, height, fx, fy, cx, cy)
print(f"[INFO] Intrinsics: fx={fx:.1f}, fy={fy:.1f}, cx={cx:.1f}, cy={cy:.1f}, size={width}x{height}")
# === 3) 创建 TSDF 体 ===
voxel = 0.02 # 体素边长 2cm
sdf_trunc = 0.08 # 截断 8cm
tsdf = o3d.pipelines.integration.ScalableTSDFVolume(
voxel_length=voxel,
sdf_trunc=sdf_trunc,
color_type=o3d.pipelines.integration.TSDFVolumeColorType.RGB8
)
# === 4) 逐帧:里程计 + 融合(深度单位 mm) ===
depth_scale = 1000.0
depth_trunc = 3.0 # 3 米外丢弃
odo_option = o3d.pipelines.odometry.OdometryOption()
odo_method = o3d.pipelines.odometry.RGBDOdometryJacobianFromHybridTerm()
def make_rgbd(cpath, dpath):
color = o3d.io.read_image(cpath)
depth = o3d.io.read_image(dpath)
return o3d.geometry.RGBDImage.create_from_color_and_depth(
color, depth,
depth_scale=depth_scale,
depth_trunc=depth_trunc,
convert_rgb_to_intensity=False
)
T_global = np.eye(4)
rgbd_prev = make_rgbd(color_paths[0], depth_paths[0])
tsdf.integrate(rgbd_prev, intr, np.linalg.inv(T_global))
for i in range(1, len(color_paths)):
rgbd = make_rgbd(color_paths[i], depth_paths[i])
success, T_step, _ = o3d.pipelines.odometry.compute_rgbd_odometry(
rgbd_prev, rgbd, intr, np.eye(4), odo_method, odo_option
)
if success:
T_global = T_global @ T_step
else:
print(f"[WARN] 第 {i} 帧里程计失败,沿用上一帧位姿")
tsdf.integrate(rgbd, intr, np.linalg.inv(T_global))
rgbd_prev = rgbd
# === 5) 提取网格并保存 ===
mesh = tsdf.extract_triangle_mesh()
mesh.compute_vertex_normals()
out_path = os.path.join(os.getcwd(), "mesh_redwood.ply")
o3d.io.write_triangle_mesh(out_path, mesh)
print(f"[OK] 已输出: {out_path}")
- 用 Open3D 的简易查看器看看网格
scss
python -c "import open3d as o3d; m=o3d.io.read_triangle_mesh(r'.\tsdf_demo\mesh_redwood.ply'); m.compute_vertex_normals(); o3d.visualization.draw_geometries([m])"
坐标系约定不一致:图形学里常见 +Y 为上,而很多相机/机器人坐标系里图像坐标 +y 向下、或世界坐标的"上"是 +Z。