本文方法来自:PYQT5内嵌外部exe程序(win7)_pyqt5嵌入外部窗口_这杯可乐有点甜的博客-CSDN博客
open3d在绘制点云等图形时,通常需要创建一个窗口。本文实现了将open3d创建的窗口显示在Qt窗口内,以便于后续通过Qt控件和槽函数调用open3d强大的绘图和处理功能。
运行结果如下图所示:
实现过程:
1.首先,导入需要的库:
import sysimport open3d as o3dfrom PyQt5.QtWidgets import QApplication,QMainWindow,QWidgetfrom PyQt5.QtGui import QWindowfrom PyQt5.QtCore import QTimerimport win32gui
其中win32gui为Pywin32库。
2.设计界面:
用QtDesigner绘制界面,如图:
保存并编译.ui文件,得到对应的.py文件。
3.嵌入窗口:
新建一个python文件,继承刚刚所写的界面的类。
创建一个open3d的Visualizer类的实例vis,并创建窗口:
class MainWindow(QMainWindow):def __init__(self,parent=None):super(MainWindow, self).__init__(parent)self.ui = Ui_display_test.Ui_MainWindow()self.ui.setupUi(self)self.vis = o3d.visualization.Visualizer()self.vis.create_window()
添加断点,或者使用vis.run()使窗口保持显示,在Visual Studio安装目录的Community\Common7\Tools找到并打开Spy++,按Ctrl+F,将“查找程序工具”拖动到打开的open3d窗口上,如图:
得到窗体类名为GLFW30。
通过Pywin32和PyQt5.QtGui找到这个类对应的窗口,并获得对应WinId:
self.winid = win32gui.FindWindow('GLFW30',None)self.sub_window = QWindow.fromWinId(self.winid)
创建WindowContainer,将窗口放入其中,在将WindowContainer添加到Qt布局中:
self.displayer = QWidget.createWindowContainer(self.sub_window)self.ui.grid_display.addWidget(self.displayer)
此时open3d窗口已经嵌入Qt窗口中。
4.加载点云,测试效果:
根据open3d文档,o3d.visualization.draw_geometries([pcd])方法可以用以下代码替代实现:
def custom_draw_geometry(pcd):# The following code achieves the same effect as:# o3d.visualization.draw_geometries([pcd])vis = o3d.visualization.Visualizer()vis.create_window()vis.add_geometry(pcd)vis.run()vis.destroy_window()
因此,添加函数:
def draw_test(self):pcd = o3d.io.read_point_cloud(r'../material/bun000.pcd')#点云路径self.vis.add_geometry(pcd)self.vis.run()
并在析构函数添加
self.vis.destroy_window()
5.修改为非阻塞显示方式:
上面的代码虽然能正常显示点云,但是由于vis.run()是阻塞方式运行的,所以在窗口关闭时destroy_window()并不能被执行,此时会导致GLFW一直报错,程序无法停止运行。
在open3d文档中,有:
代码的下一部分是本教程的核心。update_geometry通知vis相关的几何图形已经更新。最后,可视化器通过调用poll_events和update_renderer渲染一个新帧。在任何for循环迭代之后,destroy_window将关闭窗口。
将vis.run()替换为:
while True:self.vis.poll_events()self.vis.update_renderer()
实现的效果与vis.run()相同。
添加一个定时器,设置定时器为20毫秒,将这两行代码写到定时器信号对应的槽函数中:
def __init__:…………self.clock = QTimer(self)self.clock.timeout.connect(self.draw_update)self.clock.start(20)…………def draw_update(self):self.vis.poll_events()self.vis.update_renderer()
就实现了每20ms刷新一次显示,同时不阻塞程序运行。
完整代码:
display_test.ui:
MainWindow 00828608 MainWindow QLayout::SetDefaultConstraint 0 0 828 26
Ui_display_test.py,由display_test.ui编译生成:
# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'd:\personal programs\gradesign\CSDN文章用\display_test.ui'## Created by: PyQt5 UI code generator 5.15.4## WARNING: Any manual changes made to this file will be lost when pyuic5 is# run again.Do not edit this file unless you know what you are doing.from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_MainWindow(object):def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(828, 608)self.centralwidget = QtWidgets.QWidget(MainWindow)self.centralwidget.setObjectName("centralwidget")self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)self.gridLayout.setObjectName("gridLayout")self.horizontalLayout = QtWidgets.QHBoxLayout()self.horizontalLayout.setObjectName("horizontalLayout")self.grid_display = QtWidgets.QGridLayout()self.grid_display.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)self.grid_display.setObjectName("grid_display")self.horizontalLayout.addLayout(self.grid_display)self.horizontalLayout.setStretch(0, 3)self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)MainWindow.setCentralWidget(self.centralwidget)self.menubar = QtWidgets.QMenuBar(MainWindow)self.menubar.setGeometry(QtCore.QRect(0, 0, 828, 26))self.menubar.setObjectName("menubar")MainWindow.setMenuBar(self.menubar)self.statusbar = QtWidgets.QStatusBar(MainWindow)self.statusbar.setObjectName("statusbar")MainWindow.setStatusBar(self.statusbar)self.retranslateUi(MainWindow)QtCore.QMetaObject.connectSlotsByName(MainWindow)def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
displaytest.py:
import sysimport open3d as o3dfrom PyQt5.QtWidgets import QApplication,QMainWindow,QWidgetfrom PyQt5.QtGui import QWindowfrom PyQt5.QtCore import QTimerimport win32guiimport Ui_display_testclass MainWindow(QMainWindow):def __init__(self,parent=None):super(MainWindow, self).__init__(parent)self.ui = Ui_display_test.Ui_MainWindow()self.ui.setupUi(self)self.vis = o3d.visualization.Visualizer()self.vis.create_window(visible=False)#visible=False窗口不显示,避免启动时一闪而过self.winid = win32gui.FindWindow('GLFW30',None)self.sub_window = QWindow.fromWinId(self.winid)self.displayer = QWidget.createWindowContainer(self.sub_window)self.ui.grid_display.addWidget(self.displayer)self.clock = QTimer(self)self.clock.timeout.connect(self.draw_update)self.clock.start(20)self.draw_test()def draw_test(self):pcd = o3d.io.read_point_cloud(r'../material/bun000.pcd')#点云路径self.vis.add_geometry(pcd)self.vis.update_geometry(pcd)def draw_update(self):self.vis.poll_events()self.vis.update_renderer()def __del__(self):#self.clock.stop()#这一行其实并不需要self.vis.destroy_window()if __name__ == '__main__':app = QApplication(sys.argv)window = MainWindow()window.show()sys.exit(app.exec_())