在使用 Open3D 进行三维可视化和点云处理时,有时需要将当前的视角(Camera Viewpoint)保存下来,以便下次再次打开时能够还原到同样的视角。本文将演示如何在最新的 Open3D GUI 界面(o3d.visualization.gui
/ o3d.visualization.O3DVisualizer
)中实现这一功能,并展示完整示例代码及运行效果。
如果你使用的是 pip
安装,确保安装最新的 Open3D:
pip install --upgrade open3d
在 Open3D 中,相机视角主要由**内参(Intrinsic)和外参(Extrinsic)**两个部分组成。
fx, fy
)和主点坐标(cx, cy
)。由于 Open3D 内部在新的 GUI 中使用了 OpenGL 风格的坐标系,因此需要进行一次坐标变换。文中使用了一个 ToGLCamera
矩阵与其逆矩阵来做坐标系之间的转换。
当我们在 O3DVisualizer
中查看点云并进行旋转、平移等操作时,可以通过 vis.scene.camera.get_model_matrix()
获取到对应的模型矩阵(model matrix),然后再转换为外参矩阵(extrinsic)。最后,我们把这些参数序列化(用 pickle
)存储起来,之后就可以再读取并还原相机的视角。
model_matrix_to_extrinsic_matrix(model_matrix)
def model_matrix_to_extrinsic_matrix(model_matrix):
return np.linalg.inv(model_matrix @ FromGLGamera)
model_matrix
来自 vis.scene.camera.get_model_matrix()
。FromGLGamera
(ToGLCamera
的逆)矩阵,然后对结果取逆,才能得到最终的外参矩阵。create_camera_intrinsic_from_size(width, height, hfov, vfov)
def create_camera_intrinsic_from_size(width=1024, height=768, hfov=60.0, vfov=60.0):
fx = (width / 2.0) / np.tan(np.radians(hfov)/2)
fy = (height / 2.0) / np.tan(np.radians(vfov)/2)
return np.array(
[[fx, 0, width / 2.0],
[0, fy, height / 2.0],
[0, 0, 1]])
width
、高度 height
以及水平和垂直视角(hfov
, vfov
)来计算焦距。(width / 2) / tan(hfov / 2)
的含义是根据给定视场角和图像尺寸来估计相机焦距。save_view(vis, fname='saved_view.pkl')
def save_view(vis, fname='saved_view.pkl'):
try:
model_matrix = np.asarray(vis.scene.camera.get_model_matrix())
extrinsic = model_matrix_to_extrinsic_matrix(model_matrix)
width, height = vis.size.width, vis.size.height
intrinsic = create_camera_intrinsic_from_size(width, height)
saved_view = dict(extrinsic=extrinsic, intrinsic=intrinsic, width=width, height=height)
with open(fname, 'wb') as pickle_file:
dump(saved_view, pickle_file)
except Exception as e:
print(e)
extrinsic
。intrinsic
。saved_view
里并用 pickle
序列化保存到文件。load_view(vis, fname="saved_view.pkl")
def load_view(vis, fname="saved_view.pkl"):
try:
with open(fname, 'rb') as pickle_file:
saved_view = load(pickle_file)
vis.setup_camera(saved_view['intrinsic'], saved_view['extrinsic'],
saved_view['width'], saved_view['height'])
except Exception as e:
print("Can't find file", e)
intrinsic
、extrinsic
和窗口大小信息。vis.setup_camera(...)
还原相机视角。下面贴出完整的示例代码,供参考(假设文件名为 demo_save_load_view.py
)。代码中已经包含了上述四个关键函数,并演示了如何加载点云、如何在 GUI 中添加菜单项来保存/加载视角,以及如何在程序启动后自动加载之前保存的视角并截图保存。
import numpy as np
import open3d as o3d
import open3d.visualization.gui as gui
from pickle import load, dump
ToGLCamera = np.array([
[1, 0, 0, 0],
[0, -1, 0, 0],
[0, 0, -1, 0],
[0, 0, 0, 1]
])
FromGLGamera = np.linalg.inv(ToGLCamera)
def model_matrix_to_extrinsic_matrix(model_matrix):
return np.linalg.inv(model_matrix @ FromGLGamera)
def create_camera_intrinsic_from_size(width=1024, height=768, hfov=60.0, vfov=60.0):
fx = (width / 2.0) / np.tan(np.radians(hfov)/2)
fy = (height / 2.0) / np.tan(np.radians(vfov)/2)
return np.array(
[[fx, 0, width / 2.0],
[0, fy, height / 2.0],
[0, 0, 1]])
def save_view(vis, fname='saved_view.pkl'):
try:
model_matrix = np.asarray(vis.scene.camera.get_model_matrix())
extrinsic = model_matrix_to_extrinsic_matrix(model_matrix)
width, height = vis.size.width, vis.size.height
intrinsic = create_camera_intrinsic_from_size(width, height)
saved_view = dict(extrinsic=extrinsic, intrinsic=intrinsic, width=width, height=height)
with open(fname, 'wb') as pickle_file:
dump(saved_view, pickle_file)
print(f"View saved to {fname}")
except Exception as e:
print(e)
def load_view(vis, fname="saved_view.pkl"):
try:
with open(fname, 'rb') as pickle_file:
saved_view = load(pickle_file)
vis.setup_camera(saved_view['intrinsic'], saved_view['extrinsic'],
saved_view['width'], saved_view['height'])
print(f"View loaded from {fname}")
except Exception as e:
print("Can't find file", e)
def main():
gui.Application.instance.initialize()
vis = o3d.visualization.O3DVisualizer("Demo to Load a Camera Viewpoint for O3DVisualizer", 1920, 1080)
# 添加窗口
gui.Application.instance.add_window(vis)
# 设置一些可视化参数
vis.point_size = 8
vis.show_axes = True
# 在菜单中添加保存和加载相机视角的选项
vis.add_action("Save Camera View", save_view)
vis.add_action("Load Camera View", load_view)
# 调整一些可视化效果
vis.point_size = 4
vis.show_axes = False
vis.show_skybox(False)
# 读取并添加点云到可视化
pcd = o3d.io.read_point_cloud('/10.pcd')
vis.add_geometry("Random Point Cloud", pcd)
# 延迟加载视角
def load_view_delayed():
load_view(vis, 'saved_view.pkl')
gui.Application.instance.post_to_main_thread(vis, load_view_delayed)
# 延迟一秒后截图
def take_screenshot():
import time
time.sleep(1)
vis.export_current_image("screenshot.png")
print("Screenshot saved to screenshot.png")
gui.Application.instance.post_to_main_thread(vis, take_screenshot)
gui.Application.instance.run()
if __name__ == "__main__":
main()
demo_save_load_view.py
。pcd = o3d.io.read_point_cloud('/path/to/your.pcd')
替换为你自己的点云文件路径。python demo_save_load_view.py
saved_view.pkl
文件,会提示找不到文件;你可以手动在菜单里选择 Actions -> Save Camera View
来保存当前视角。load_view_delayed()
,从上次保存的 saved_view.pkl
中加载相机视角,并在 1 秒后截图。通过本文示例,我们可以看到,在新的 Open3D GUI(O3DVisualizer
)中,保存并还原相机视角的核心思路就是:
model_matrix
;这样就能方便地在多次打开程序或者不同机器上还原同一个观察视角。希望这篇文章能给你在使用 Open3D 的项目中带来帮助。