利用 Open3D 保存并载入相机视角的简单示例

news/2025/2/26 20:43:23

1. 前言

在使用 Open3D 进行三维可视化和点云处理时,有时需要将当前的视角(Camera Viewpoint)保存下来,以便下次再次打开时能够还原到同样的视角。本文将演示如何在最新的 Open3D GUI 界面(o3d.visualization.gui / o3d.visualization.O3DVisualizer)中实现这一功能,并展示完整示例代码及运行效果。

2. 环境准备

  • Python 版本:3.x
  • Open3D 版本:0.15+ 或 0.16+(支持新的 GUI)
  • 其他依赖:Numpy

如果你使用的是 pip 安装,确保安装最新的 Open3D:

pip install --upgrade open3d

3. 实现思路

在 Open3D 中,相机视角主要由**内参(Intrinsic)外参(Extrinsic)**两个部分组成。

  • 内参(Intrinsic):描述相机的焦距(fx, fy)和主点坐标(cx, cy)。
  • 外参(Extrinsic):描述世界坐标系到相机坐标系的变换关系,包含旋转和平移。

由于 Open3D 内部在新的 GUI 中使用了 OpenGL 风格的坐标系,因此需要进行一次坐标变换。文中使用了一个 ToGLCamera 矩阵与其逆矩阵来做坐标系之间的转换。

当我们在 O3DVisualizer 中查看点云并进行旋转、平移等操作时,可以通过 vis.scene.camera.get_model_matrix() 获取到对应的模型矩阵(model matrix),然后再转换为外参矩阵(extrinsic)。最后,我们把这些参数序列化(用 pickle)存储起来,之后就可以再读取并还原相机的视角。

4. 关键函数解析

4.1 model_matrix_to_extrinsic_matrix(model_matrix)

python">def model_matrix_to_extrinsic_matrix(model_matrix):
    return np.linalg.inv(model_matrix @ FromGLGamera)
  • 这里 model_matrix 来自 vis.scene.camera.get_model_matrix()
  • 由于 Open3D GUI 中的相机使用了与传统坐标系不同的变换,需要先右乘一个 FromGLGameraToGLCamera 的逆)矩阵,然后对结果取逆,才能得到最终的外参矩阵。

4.2 create_camera_intrinsic_from_size(width, height, hfov, vfov)

python">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) 的含义是根据给定视场角和图像尺寸来估计相机焦距。
  • 最终返回一个 3×3 的内参矩阵。

4.3 save_view(vis, fname='saved_view.pkl')

python">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 序列化保存到文件。

4.4 load_view(vis, fname="saved_view.pkl")

python">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)
  • 反序列化读取之前保存的 intrinsicextrinsic 和窗口大小信息。
  • 调用 vis.setup_camera(...) 还原相机视角。
    点击Action按钮,即可导出或载入视角

5. 完整示例代码

下面贴出完整的示例代码,供参考(假设文件名为 demo_save_load_view.py)。代码中已经包含了上述四个关键函数,并演示了如何加载点云、如何在 GUI 中添加菜单项来保存/加载视角,以及如何在程序启动后自动加载之前保存的视角并截图保存。

python">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()

6. 使用方法

  1. 确保安装好 Open3D(最好是最新版本),并将上面的代码保存为 demo_save_load_view.py
  2. 修改点云文件路径:将 pcd = o3d.io.read_point_cloud('/path/to/your.pcd') 替换为你自己的点云文件路径。
  3. 运行脚本:
    python demo_save_load_view.py
    
  4. 首次运行时,如果本地没有 saved_view.pkl 文件,会提示找不到文件;你可以手动在菜单里选择 Actions -> Save Camera View 来保存当前视角。
  5. 下次再运行脚本时,程序会自动执行 load_view_delayed(),从上次保存的 saved_view.pkl 中加载相机视角,并在 1 秒后截图。

7. 总结

通过本文示例,我们可以看到,在新的 Open3D GUI(O3DVisualizer)中,保存并还原相机视角的核心思路就是:

  1. 获取当前相机的 model_matrix
  2. 结合一个与 OpenGL 坐标系相关的转换矩阵,计算出外参;
  3. 根据窗口大小和视场角,生成内参;
  4. 将这些数据保存到文件,日后可以轻松加载还原相机视角。

这样就能方便地在多次打开程序或者不同机器上还原同一个观察视角。希望这篇文章能给你在使用 Open3D 的项目中带来帮助。


http://www.niftyadmin.cn/n/5869151.html

相关文章

ref和reactive的区别 Vue3

Vue3中ref和reactive的区别 ref 可以定义基本数据类型,也可定义对象类型的响应式数据 reactive 只能定义对象类型的响应式数据 ref和reactive定义对象类型的响应式数据有什么不同 不同点1 ref定义的响应式数据,取值时需要先 .value 不同点2 替换整…

单片机的串口(USART)

Tx - 数据的发送引脚,Rx - 数据的接受引脚。 串口的数据帧格式 空闲状态高电平,起始位低电平,数据位有8位校验位,9位校验位,停止位是高电平保持一位或者半位,又或者两位的状态。 8位无校验位传输一个字节…

KubeSphere部署redis集群

一、部署前准备 (一)KubeSphere部署redis集群思路 参考上一篇文章的部署思路:KubeSphere安装mysql-CSDN博客 (二)部署方法参考 1、参考Docker Hub的中docker部署redis的方法 部署方法按照Docker Hub官网部署redis的…

DeepSeek “源神”启动!「GitHub 热点速览」

上周,DeepSeek 官方宣布将陆续发布 5 个开源项目。本周一开源社区就迎来了首发之作——FlashMLA!该项目开源后,不到一天 Star 数已突破 6k,并且还在以惊人的速度持续飙升。 GitHub 地址:github.com/deepseek-ai/FlashM…

RBAC授权

4 RBAC授权 4.1 什么是RBAC 在Kubernetes中,所有资源对象都是通过API进行操作,他们保存在etcd里。而对etcd的操作我们需要通过访问kube-apiserver来实现,上面的Service Account其实就是APIServer的认证过程,而授权的机制是通过RBA…

银河麒麟高级服务器操作系统通用rsync禁止匿名访问操作指南

银河麒麟高级服务器操作系统通用rsync禁止匿名访问操作指南 一 系统环境二、基础信息2.1 IP信息2.2 两台机器分别执行关闭firewalld或者放行相关的端口 三、服务端配置3.1 配置rsync.conf文件3.2 创建密码文件3.3 重启rsyncd 服务 四、客户端测试4.1 allowed_user1和allowed_us…

火语言RPA--Excel获取Sheet页列表

【组件功能】:Excel获取Sheet页列表 配置预览 示例 获取Excel文档所有Sheet页列表 描述 分别获取F:\HuoYuYan\test.xls和F:\HuoYuYan\test.xlsx2种扩展名的sheet页列表。 配置 输出结果

贪心算法精品题

1.找钱问题 本题的贪心策略在于我们希望就可能的保留作用大的5元 class Solution { public:bool lemonadeChange(vector<int>& bills) {std::map<int ,int> _map;for(auto ch:bills){if(ch 5) _map[ch];else if(ch 10){if(_map[5] 0) return false;else{_m…