当前位置: 首页 > 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…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...