使用 Open3D 批量渲染并导出固定视角点云截图

一、前言

在三维点云处理与可视化中,固定视角批量生成点云渲染截图是一个常见的需求。例如,想要将同一系列的点云(PCD 文件)在同样的视角下生成序列图片,以便后续合成为视频或进行其他可视化演示。本文将介绍如何使用 Python + Open3D 实现批量加载 PCD 文件、设置统一的相机视角,并导出渲染截图。

二、环境准备

  1. Python 环境:建议使用 Python 3.7+。
  2. Open3D 库:本文使用的是 open3dopen3d.visualization.gui。安装方式如下:
    pip install open3d
    
  3. 其他依赖库
    • numpy
    • pickle
    • glob
    • time
    • os
      如果缺少对应的库,使用 pip install 库名 即可。

三、代码解析

下面的代码分为几个主要部分:

  1. 相机矩阵转换:将 Open3D 的模型矩阵转换为外参矩阵。
  2. 相机内参生成:根据视口大小与视场角(FOV)生成相机内参。
  3. 保存与加载相机视角:使用 pickle 将当前相机的内外参持久化保存,方便在下次使用时快速恢复相机位置。
  4. 批量处理函数:遍历指定文件夹下所有 .pcd 文件,加载点云、应用相机视角、并自动保存渲染截图。
  5. 主函数:在 if __name__ == "__main__": 中调用批处理函数,或先进行单独的相机视角设置保存操作。

完整代码如下(可直接复制使用):

import numpy as np
import open3d as o3d
import open3d.visualization.gui as gui
from pickle import load, dump
import os
import glob
import time# 用于将坐标系转换为OpenGL风格
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):"""将Open3D的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):"""根据视口大小与水平/垂直FOV生成相机内参"""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"Camera view saved to {fname}")except Exception as e:print("Error saving view:", 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"Camera view loaded from {fname}")except Exception as e:print("Can't load view file:", e)def process_pcd_folder(input_folder, output_folder, view_file='saved_view.pkl'):"""批量处理文件夹中的所有 PCD 文件,应用指定视角并保存截图"""os.makedirs(output_folder, exist_ok=True)pcd_files = sorted(glob.glob(os.path.join(input_folder, "*.pcd")))if not pcd_files:print(f"No PCD files found in {input_folder}")returnprint(f"Found {len(pcd_files)} PCD files")# 初始化GUIgui.Application.instance.initialize()vis = o3d.visualization.O3DVisualizer("PCD Batch Renderer", 1920, 1080)gui.Application.instance.add_window(vis)# 设置渲染参数vis.point_size = 4vis.show_axes = Falsevis.show_skybox(False)def process_next(idx):if idx >= len(pcd_files):print("Batch processing completed!")gui.Application.instance.quit()returnpcd_file = pcd_files[idx]print(f"Processing {idx+1}/{len(pcd_files)}: {os.path.basename(pcd_file)}")try:# 加载点云文件pcd = o3d.io.read_point_cloud(pcd_file)geom_name = f"PointCloud_{idx}"# 清除之前所有几何体,确保内存资源不会累积if idx > 0:vis.remove_geometry(f"PointCloud_{idx-1}")vis.add_geometry(geom_name, pcd)# 加载预先保存的视角load_view(vis, view_file)# 构建输出路径base_name = os.path.splitext(os.path.basename(pcd_file))[0]output_path = os.path.join(output_folder, f"{base_name}.png")def take_screenshot():# 延迟1秒以确保视角和渲染完全加载time.sleep(1)vis.export_current_image(output_path)print(f"Screenshot saved to {output_path}")# 处理完当前文件后,处理下一个process_next(idx + 1)# 使用post_to_main_thread确保截图任务在GUI线程执行gui.Application.instance.post_to_main_thread(vis, take_screenshot)except Exception as e:print(f"Error processing {pcd_file}: {e}")# 出错时跳过当前文件,继续下一个process_next(idx + 1)# 开始处理第一个文件process_next(0)gui.Application.instance.run()def batch_process():"""主函数:指定输入、输出文件夹以及相机视角文件,然后进行批量处理"""input_folder = './input'output_folder = './screenshots'os.makedirs(output_folder, exist_ok=True)view_file = 'saved_view.pkl'process_pcd_folder(input_folder, output_folder, view_file)if __name__ == "__main__":# 若需要先设置视角,运行 save_view 所在的逻辑# 若已设置好视角,运行 batch_process()批量处理batch_process()

1. 代码主要流程

  • 读取文件列表:通过 glob.glob 获取指定文件夹下的所有 .pcd 文件并排序。
  • 初始化 Open3D GUI:使用 O3DVisualizer 进行可视化。
  • 循环处理每个 PCD
    1. 读取点云数据 pcd = o3d.io.read_point_cloud(...)
    2. 加载之前保存的视角参数 load_view(vis, view_file)
    3. 设置几何体到渲染窗口
    4. 通过 vis.export_current_image(...) 将当前视图截图保存
  • 处理结束后退出:当全部 .pcd 文件处理完毕,自动退出 GUI。

2. 视角保存与加载

  • save_view(vis, fname='saved_view.pkl'):从当前的 vis.scene.camera 获取 model_matrix,然后计算外参矩阵、内参矩阵并存储到一个字典中,通过 pickle 持久化到 saved_view.pkl 文件。
  • load_view(vis, fname='saved_view.pkl'):从文件中读取上述字典,调用 vis.setup_camera(...) 将相机恢复到保存时的视角。

这样做的好处是,我们可以先交互式地在 Open3D 中调整一个理想的点云视角,然后保存该视角。后续就可以用同样的参数去渲染其他点云,实现“统一视角”输出。

3. 视角设置的两种方式

  1. 先在单个点云上用脚本交互式设置并保存
    • 先运行一个类似的脚本,只加载一个点云,不做批处理。
    • 在界面中使用鼠标旋转/平移点云至理想位置,然后调用 save_view(vis)
  2. 直接修改代码中的相机参数:如果你对内参、外参很熟悉,也可以直接硬编码想要的矩阵。

四、使用说明

  1. 准备 PCD 文件:将所有需要处理的 .pcd 文件放在同一个文件夹中。
  2. 保存视角(可选):
    • 若你已知道要使用的视角参数,可以跳过这一步;否则先写个简单脚本,加载一两个 PCD 文件后,通过交互操作找到满意的视角,执行 save_view(vis)
    • 此时会生成一个 saved_view.pkl 文件,里面记录了相机的内外参。
  3. 运行批处理
    • 修改 batch_process() 中的 input_folderoutput_folder 为你的输入、输出路径。
    • 运行脚本后,Open3D 窗口会依次加载每个 .pcd,应用保存好的视角,然后自动截图并存储到 output_folder 中。

在所有点云都处理完后,你就能在输出文件夹下看到对应的 .png 文件序列。

五、结果展示

下面是一张示例截图,展示了点云在固定视角下的渲染效果(仅做示意,非实际数据):
在这里插入图片描述

六、后续扩展

  1. 生成视频:如果想将渲染好的序列图片合成为视频,可使用 ffmpeg,示例命令如下:
    ffmpeg -framerate 10 -i labeled_sync_frame_%03d.png -c:v libx264 -pix_fmt yuv420p output.mp4
    
    其中 -framerate 10 表示每秒 10 帧,可根据需要调整。
  2. 更多可视化选项:如改变 point_size、背景颜色、或添加坐标轴等,可参考 Open3D 文档或修改 O3DVisualizer 的属性。
  3. 其他文件格式:如果想批量处理 .ply.xyz,只需要在代码中修改对应的读取方式,以及 glob.glob 匹配模式即可。

七、总结

通过上述方法,可以轻松地在同一视角下对多份点云进行批量渲染和截图,适用于制作点云动画、对比分析等场景。核心思想是事先保存好相机参数,并在批处理过程中为每个点云恢复相同的内外参,保证输出图像的视角一致。希望对你的三维可视化工作有所帮助,欢迎交流讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/24066.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++的继承

封装、继承和多态是c的三大特性,他们的关系甚为紧密 封装的概念简单易懂,其实就是将数据和操作数据的方法结合在一起,形成一个独立的单元(类),通过访问控制符(如private、protected和public&…

3dtiles平移旋转工具制作

3dtiles平移旋转缩放原理及可视化工具实现 背景 平时工作中,通过cesium平台来搭建一个演示场景是很常见的事情。一般来说,演示场景不需要多完善的功能,但是需要一批三维模型搭建,如厂房、电力设备、园区等。在实际搭建过程中&…

我是如何从 0 到 1 找到 Web3 工作的?

作者:Lotus的人生实验 关于我花了一个月的时间,从 0 到 1 学习 Web3 相关的知识和编程知识。然后找到了一个 Web3 创业公司实习的远程工作。 👇👇👇 我的背景: 计算机科班,学历还可以(大厂门槛水平) 毕业工…

进程状态(R|S|D|t|T|X|Z)、僵尸进程及孤儿进程

文章目录 一.进程状态进程排队状态:运行、阻塞、挂起 二.Linux下的进程状态R 运行状态(running)S 睡眠状态(sleeping)D 磁盘休眠状态(Disk sleep)t 停止、暂停状态(tracing stopped)T 停止、暂停状态(stopp…

为什么要将PDF转换为CSV?CSV是Excel吗?

在企业和数据管理的日常工作中,PDF文件和CSV文件承担着各自的任务。PDF通常用于传输和展示静态的文档,而CSV因其简洁、易操作的特性,广泛应用于数据存储和交换。如果需要从PDF中提取、分析或处理数据,转换为CSV格式可能是一个高效…

Starlink卫星动力学系统仿真建模第十讲-基于SMC和四元数的卫星姿态控制示例及Python实现

基于四元数与滑模控制的卫星姿态控制 一、基本原理 1. 四元数姿态表示 四元数运动学方程: 3. 滑模控制设计 二、代码实现(Python) 1. 四元数运算工具 import numpy as npdef quat_mult(q1, q2):"""四元数乘法""…

CSS—引入方式、选择器、复合选择器、文字控制属性、CSS特性

目录 CSS 1.引入方式 2.选择器 3.复合选择器 4.文字控制属性 5.CSS特性 CSS 层叠样式表,是一种样式表语言,用来描述HTML文档的呈现 书写时一般按照顺序:盒子模型属性—>文字样式—>圆角、阴影等修饰属性 1.引入方式 引入方式方…

OpenHarmony-4.基于dayu800 GPIO 实践(2)

基于dayu800 GPIO 进行开发 1.DAYU800开发板硬件接口 LicheePi 4A 板载 2x10pin 插针,其中有 16 个原生 IO,包括 6 个普通 IO,3 对串口,一个 SPI。TH1520 SOC 具有4个GPIO bank,每个bank最大有32个IO:  …

win11 24h2 远程桌面 频繁断开 已失去连接 2025

一、现象 Windows11自升级2025年2月补丁后版本号为系统版本是26100.3194,远程桌面频繁断开连接,尝试连接,尤其在连接旧的server2012 二、临时解决方案 目前经测试,在组策略中,远程桌面连接客户端,关闭客户…

rust学习笔记6-数组练习704. 二分查找

上次说到rust所有权看看它和其他语言比有什么优势,就以python为例 # Python3 def test():a [1, 3, -4, 7, 9]print(a[4])b a # 所有权没有发生转移del b[4]print(a[4]) # 由于b做了删除,导致a再度访问报数组越界if __name__ __main__:test() 运行结…

Windows安装NVIDIA显卡CUDAD调用GPU,适用于部署deepseek r1

显卡、显卡驱动、CUDA之间的关系 显卡:(GPU),主流是NVIDIA的GPU,因为深度学习本身需要大量计算。GPU的并行计算能力,在过去几年里恰当地满足了深度学习的需求。AMD的GPU基本没有什么支持,可以不…

基于无人机遥感的烟株提取和计数研究

一.研究的背景、目的和意义 1.研究背景及意义 烟草作为我国重要的经济作物之一,其种植面积和产量的准确统计对于烟草产业的发展和管理至关重要。传统的人工烟株计数方法存在效率低、误差大、难以覆盖大面积烟田等问题,已无法满足现代烟草种植管理的需求…

《深度学习实战》第3集:循环神经网络(RNN)与序列建模

第3集:循环神经网络(RNN)与序列建模 引言 在深度学习领域,处理序列数据(如文本、语音、时间序列等)是一个重要的研究方向。传统的全连接网络和卷积神经网络(CNN)难以直接捕捉序列中…

【前沿探索篇七】【DeepSeek自动驾驶:端到端决策网络】

第一章 自动驾驶的"感官革命":多模态神经交响乐团 1.1 传感器矩阵的量子纠缠 我们把8路摄像头+4D毫米波雷达+128线激光雷达的融合称为"传感器交响乐",其数据融合公式可以简化为: def sensor_fusion(cam, radar, lidar):# 像素级特征提取 (ResNet-152…

可狱可囚的爬虫系列课程 13:Requests使用代理IP

一、什么是代理 IP 代理 IP(Proxy IP)是一个充当“中间人”的服务器IP地址,用于代替用户设备(如电脑、手机等)直接与目标网站或服务通信。用户通过代理IP访问互联网时,目标网站看到的是代理服务器的IP地址&…

https:原理

目录 1.数据的加密 1.1对称加密 1.2非对称加密 2.数据指纹 2.1数据指纹实际的应用 3.数据加密的方式 3.1只使用对称加密 3.2只使用非对称加密 3.3双方都使用对称加密 3.4非对称加密和对称加密一起使用 4.中间人攻击 5.CA证书 5.1什么是CA证书 CA证书的验证 6.https的原理 1.数据…

Github项目管理之 其余分支同步main分支

文章目录 方法:通过 Pull Request 同步分支1. **创建一个从 main 到目标分支的 Pull Request**2. **合并 Pull Request** 注意事项总结 在 GitHub 网页上,你可以通过 Pull Request 的方式将一个分支(例如 main 分支)的修改同步到…

Aseprite绘画流程案例(5)——花盆

1.最终图片效果 参考素材来源于:手绘像素画第三课:像素画盆花示范(无参考图)_哔哩哔哩_bilibili 2.流程 1.新建画布40X27的画布,打开显示网格,背景色为白色 2.画出梯形的盆 3.给盆进行亮暗对比上色 4.添…

【模板】csdn markdown语法演示

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

【Python系列】PYTHONUNBUFFERED=1的作用

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…