当前位置: 首页 > news >正文

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

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)

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)

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')

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.heightintrinsic = 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")

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 中添加菜单项来保存/加载视角,以及如何在程序启动后自动加载之前保存的视角并截图保存。

import numpy as np
import open3d as o3d
import open3d.visualization.gui as gui
from pickle import load, dumpToGLCamera = 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.heightintrinsic = 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 = 8vis.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 timetime.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 的项目中带来帮助。

相关文章:

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

1. 前言 在使用 Open3D 进行三维可视化和点云处理时,有时需要将当前的视角(Camera Viewpoint)保存下来,以便下次再次打开时能够还原到同样的视角。本文将演示如何在最新的 Open3D GUI 界面(o3d.visualization.gui / o…...

智绘教:Windows平台上的高效悬浮窗画笔工具深度解析

在Windows平台上,一款高效、实用的悬浮窗画笔工具对于提升工作效率和演示效果至关重要。今天,我要为大家介绍一款备受好评的悬浮窗画笔程序——智绘教。这款软件以其丰富的功能和便捷的操作,成为了众多用户心中的首选。接下来,让我们一起深入了解智绘教的各项特性。 一、体…...

从“Switch-case“到“智能模式“:C#模式匹配的终极进化指南

当代码开始"思考" 你是否厌倦了层层嵌套的if-else地狱?是否想过让代码像侦探推理一样优雅地解构数据?C#的模式匹配正是这样一把瑞士军刀,从C# 7.0到C# 12,它已悄然进化成改变编程范式的利器。 一、模式匹配的三重境界…...

【Linux】进程优先级 | 进程调度(三)

目录 前言: 一、进程优先级: 1.通过nice值修改优先级: 二、进程切换: 三、上下文数据 四、Linux真实调度算法: 五、bitmap位图: 六、命令总结: 总结: 前言: 我…...

wordpress按不同页调用不同的标题3种形式

在WordPress中,可以通过多种方式根据不同的页面调用不同的标题。这通常用于实现SEO优化、自定义页面标题或根据页面类型显示不同的标题内容。 使用wp_title函数 wp_title函数用于在HTML的title标签中输出页面标题。你可以通过修改主题的header.php文件来实现自定义…...

音频进阶学习十六——LTI系统的差分方程与频域分析一(频率响应)

文章目录 前言一、差分方程的有理式1.差分方程的有理分式2.因果系统和ROC3.稳定性与ROC 二、频率响应1.定义2.幅频响应3.相频响应4.群延迟 总结 前言 本篇文章会先复习Z变换的有理分式,这是之前文章中提过的内容,这里会将差分方程和有理分式进行结合来看…...

css实现左右切换平滑效果

2025.02.25今天我学习了如何用css实现平滑效果 一、html相关代码 &#xff08;1&#xff09;设置往左、往右的动画属性&#xff0c;样式可以放在同一级。 &#xff08;2&#xff09;必须设置唯一key进行刷新数据&#xff0c;使用v-show来展示每次渲染的组件数量。 <tran…...

详解Tomcat下载安装以及IDEA配置Tomcat(2023最新)

目录 步骤一&#xff1a;首先确认自己是否已经安装JDK步骤二&#xff1a;下载安装Tomcat步骤三&#xff1a;Tomcat配置环境变量步骤四&#xff1a;验证Tomcat配置是否成功步骤五&#xff1a;为IDEA配置Tomcat 步骤一&#xff1a;首先确认自己是否已经安装JDK jdk各版本通用安…...

Docker快速使用指南

docker pull ubuntu:22.04 //先拉取一个基础镜像&#xff0c;一般是操作系统创建一个Dockerfile&#xff0c;放在任意目录下&#xff0c;内容如下 # 使用 Ubuntu 22.04 作为基础镜像 FROM ubuntu:22.04# 设置环境变量&#xff0c;避免安装过程中出现交互提示 ENV DEBIAN_FRONT…...

【Project】基于Prometheus监控docker平台

一、设计背景 1.1项目简介 本项目旨在创建一个全面的容器化应用程序监控解决方案&#xff0c;基于Prometheus监控Docker平台上的各种服务。在当今的软件开发环境中&#xff0c;容器化技术已成为一种关键的工具&#xff0c;使应用程序能够更快速、可靠地交付和扩展。然而&…...

Binder通信协议

目录 一,整体架构 二,Binder通信协议 三&#xff0c;binder驱动返回协议 四&#xff0c;请求binder驱动协议 一,整体架构 二,Binder通信协议 三&#xff0c;binder驱动返回协议 binder_driver_return_protocol共包含18个命令&#xff0c;分别是&#xff1a; 四&#xff0c…...

使用 Postman 访问 Keycloak 端点

1. 引言 在本教程中&#xff0c;我们将首先快速回顾 OAuth 2.0、OpenID 和 Keycloak。然后&#xff0c;我们将了解 Keycloak REST API 以及如何在 Postman 中调用它们。 2. OAuth 2.0 OAuth 2.0 是一个授权框架&#xff0c;它允许经过身份验证的用户通过令牌向第三方授予访问…...

uniapp-X 对象动态取值

有个对象&#xff0c;例如 const data{age:12,list:[1,2,3,4]} 有个函数如下 export function getValueByPath(obj:UTSJSONObject, path:string):any {const current obj.getAny(path) as any;// 返回最终的值return current; } 期待 通过执行getValueByPath("xx.xx…...

建模软件Blender与Blender GIS插件安装教程

Blender&#xff08;blender.org - Home of the Blender project - Free and Open 3D Creation Software&#xff09;是一款功能强大的开源3D创作套件&#xff0c;它支持整个3D管道—建模、渲染、动画制作、模拟、渲染、合成和运动跟踪&#xff0c;甚至视频编辑和游戏制作&…...

数据解析与处理

数据解析与处理是数据科学、分析或开发中的核心步骤&#xff0c;涉及从原始数据中提取、清洗、转换和存储有效信息的过程。 一、数据解析 数据解析就是将原始数据&#xff08;如文本、二进制、日志、API响应等&#xff09;转换为结构化格式&#xff08;如表格、字典、JSON等&…...

强化学习概览

强化学习的目标 智能体&#xff08;Agent&#xff09;通过与环境&#xff08;Environment&#xff09;交互&#xff0c;学习最大化累积奖励&#xff08;Cumulative Reward&#xff09;​的策略。 数学抽象 马尔科夫决策过程&#xff08;MDP&#xff09; 收益 由于马尔科夫决…...

如何在netlify一键部署静态网站

1. 准备你的项目 确保你的静态网站文件&#xff08;如 HTML、CSS、JavaScript、图片等&#xff09;都在一个文件夹中。通常&#xff0c;项目结构如下&#xff1a; my-static-site/ ├── index.html ├── styles/ │ └── styles.css └── scripts/└── script.js…...

2024中国信通院“集智”蓝皮书合集(附下载)

【目 录】 1. 数字政府一体化建设蓝皮书&#xff08;2024年&#xff09; 2. 数字乡村发展实践蓝皮书&#xff08;2023年&#xff09; 3. 中国工业互联网发展成效评估报告&#xff08;2024年&#xff09; 4. 云计算蓝皮书&#xff08;2024年&#xff09; 5. 具身智能发展报告…...

springboot单机支持1w并发,需要做哪些优化

Spring Boot单机如何支持1万并发&#xff0c;需要做哪些优化。 首先&#xff0c;我得回想一下Spring Boot处理高并发的关键点在哪里。可能涉及到多个层面&#xff0c;比如Web服务器配置、数据库优化、代码层面的调整&#xff0c;还有JVM调优之类的。 首先&#xff0c;用户可能…...

HBuilderx 插件开发变量名称翻译 ,中文转(小驼峰,大驼峰,下划线,常量,CSS类名)

HBuilderx 插件开发变量名称翻译 &#xff0c;中文转&#xff08;小驼峰&#xff0c;大驼峰&#xff0c;下划线&#xff0c;常量&#xff0c;CSS类名&#xff09; 插件开发文档 工具HBuilderx &#xff0c;创建项目 创建成功后目录 插件需求 开发时 用来将中文转为&#xff0…...

零基础轻松拿捏!魔珐星云青少年健康运动教学数字人搭建全流程指南

大家好&#xff01;本次给大家分享一款面向青少年体育教育的AI创意实践项目——青少年健康运动教学智能数字交互系统。本项目聚焦青少年体质健康痛点&#xff0c;围绕体育教学智能化升级需求&#xff0c;打造集健康知识教学、运动动作陪练、健康知识考核、运动能力评测于一体的…...

HFSS仿真结果怎么看?一文读懂S参数与电场图,让你的T型波导分析不再迷茫

HFSS仿真结果深度解析&#xff1a;从S参数到电场图的工程实践指南面对HFSS仿真生成的复杂数据图表&#xff0c;许多工程师常陷入"看得见数据却读不懂含义"的困境。本文将带您穿透数据表象&#xff0c;掌握T型波导性能分析的核心方法论。1. S参数&#xff1a;波导性能…...

对称与负电源测试:动态直流电子负载的设计、原理与应用

1. 项目概述&#xff1a;对称与负电源的静态与动态直流负载在电子实验室里&#xff0c;测试一个电源的性能&#xff0c;尤其是它的动态响应能力&#xff0c;是件既基础又关键的事。我们常说的“直流电子负载”就是这个领域的核心工具。我之前设计并分享过一个用于正电源测试的静…...

WPF虚拟桌宠组件:可嵌入、高性能、工程化UI生命体

1. 这不是“桌面宠物”&#xff0c;而是一个可嵌入的WPF UI组件化生命体你可能在Windows XP时代见过那只晃着尾巴、偶尔打哈欠的3D小猫&#xff0c;也可能在Win10系统托盘里点开过一个会眨眼的像素狐狸——但那些是独立进程、是系统级小工具、是“看一眼就关掉”的轻量娱乐。而…...

可解释AI新突破:基于局部帕累托最优的模型解释框架

1. 项目概述&#xff1a;当AI模型成为“黑箱”&#xff0c;我们如何撬开它&#xff1f;在机器学习项目里摸爬滚打十几年&#xff0c;我见过太多这样的场景&#xff1a;团队花大力气训练出一个准确率高达95%的复杂模型&#xff08;比如深度神经网络&#xff09;&#xff0c;业务…...

Vue2-Verify:解决前端验证码安全性与用户体验平衡问题的技术方案实现

Vue2-Verify&#xff1a;解决前端验证码安全性与用户体验平衡问题的技术方案实现 【免费下载链接】vue2-verify vue的验证码插件 项目地址: https://gitcode.com/gh_mirrors/vu/vue2-verify 在当今Web应用开发中&#xff0c;验证码作为防止自动化攻击的关键安全组件&…...

基于GSM与Arduino的远程控制系统:DIY电话控制与短信报警方案

1. 项目概述与核心价值如果你曾经想过&#xff0c;在离家几十公里外&#xff0c;仅凭一部普通的手机&#xff0c;就能远程打开家里的车库门、查看门窗是否关好&#xff0c;甚至在异常情况发生时让系统自动打电话给你报警&#xff0c;那么这个基于GSM的远程控制系统项目&#xf…...

基于IRS2092的200W D类功放设计:从PWM原理到保护电路实战

1. 项目概述与核心思路折腾音响功放&#xff0c;从经典的AB类玩到D类&#xff0c;感觉就像是从燃油车换到了电动车&#xff0c;动力响应和效率完全是两个维度。这次要聊的这块“200W Class-D Audio Power Amplifier [150115]”单板功放&#xff0c;就是一个非常典型的D类功放设…...

Windows Cleaner:终极免费系统清理工具,彻底解决C盘空间不足问题

Windows Cleaner&#xff1a;终极免费系统清理工具&#xff0c;彻底解决C盘空间不足问题 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服&#xff01; 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner 你是否经常遇到C盘爆红、…...

JWT弱密钥爆破实战:从HS256签名原理到CTF权限提升

1. 这不是密码学考试&#xff0c;而是一场“密钥猜谜”实战JWT&#xff08;JSON Web Token&#xff09;在现代Web系统中早已不是可选项&#xff0c;而是默认配置。登录成功后返回一串形如eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsIm5hbWUiOiLnlKjliYkiLCJpYX…...