参考:在PyQt5窗口中嵌入open3d窗口显示点云图形_open3d嵌入pyqt-CSDN博客
1.安装
pip install open3D
pip install pyqt5
2.使用designer制作pyqt ui文件并编译为py文件
ui文件转换成py文件:
pyuic5 designer.ui -o designer.py
3.主要代码-point2recct2.py
需要的包
# 导入
import sys
import win32gui # 用于Windows GUI编程,这里主要用来查找Open3D创建的窗口句柄
import numpy as np
import open3d as o3d # 3D数据处理和可视化
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget # 用于创建和管理GUI应用程序的窗口
from PyQt5.QtGui import QWindow # 提供了创建和操作窗口的功能
from PyQt5.QtCore import QTimer # 提供定时器功能,用于周期性的执行任务
import designer # 导入ui转换的那个py文件
类初始化
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.ui = designer.Ui_MainWindow() # 初始化ui界面
self.ui.setupUi(self) # 设置界面
# 初始化起始点参数,方便在主函数中直接给这两个参数赋值,不用再给函数传参
self.start_point = None
self.end_point = None
# 初始化open3D可视化器
self.init_visualizer()
# 初始化并创建和添加open3D的窗口转化成的QWidget
self.init_display_container()
# 启动pyqt定时器
self.start_timer()
初始化open3D可视化器
创建一个空的open3D的可视化器,另外创建一个空的线集出来
def init_visualizer(self):
self.vis = o3d.visualization.Visualizer() # 创建一个可视化器
self.vis.create_window(visible=False) # 创建一个不可见的open3D窗口,用于后台渲染
self.line_set_eye = o3d.geometry.LineSet() # 初始化空的线组 注意这里是视线
self.current_line_index = 0 # 初始化当前视线索引
初始化一个展示的容器
用win32gui找到open3D刚才初始化时创建的窗口的句柄,然后从句柄创建QWindow,再转换成QWidget并且把这个widget嵌入到pyqt的某个窗口里面
def init_display_container(self):
# 查找Open3D创建的窗口的句柄 open3D代表窗口标题 ,这两个把任何一个设置成None都能正常运行
self.winid = win32gui.FindWindow('GLFW30', 'open3D')
# 从句柄创建QWindow
self.sub_window = QWindow.fromWinId(self.winid)
# 将QWindow转换为QWidget
self.displayer = QWidget.createWindowContainer(self.sub_window)
# 将这个QWidget添加到UI布局的gridLayout中
self.ui.gridLayout.addWidget(self.displayer)
找到平行四边形第四个点D
原理:假设平行四边形的前三个点是ABC,这里是找到在同一个平面上的平行四边形的第四个点D,这里要假设AD//BC,BD//AC 则有-> A-C = CA; CA = BD; BD + B = D;
这里要注意,ABCD的顺序 谁和谁连接着,不然会造成连线不对的现象
这里的顺序要和后面的线条对应 因为AD//BC,BD//AC 所以画线的时候要画 AD BC BD AC 四条线
这里不是用的A-B-C-D-A的方式,而是AD-DB-BC-CA的方式,可以自行更改
def find_four_points(self, A, B, C):
# 根据加载的点计算第四个点D的坐标
A = point_array[0]
B = point_array[1]
C = point_array[2]
D = np.array([A[0] + B[0] - C[0], A[1] + B[1] - C[1], A[2] + B[2] - C[2]])
# 判断是否在一个平面上
print(window.are_points_coplanar(A, B, C, D))
# 将计算得到的四个点组成一个新的NumPy数组
points = np.array([A, B, C, D])
return points
open3D画平行四边形和给已知的三个点ABC染色
def draw_ABCD_point2line_and_add_geometry(self, points):
#
# 读取已知的三个点的数据 这里为了区分已知的ABC和推断出的那个点D 对ABC进行了染色
self.pcd = o3d.io.read_point_cloud('xyz.txt', format="xyz") # 以点云的方式读取已知的三个点
colors_points = [[0, 1, 0], [0, 1, 0], [0, 1, 0]]
self.pcd.colors = o3d.utility.Vector3dVector(colors_points) # 设置已知的三个点的颜色
# 定义线段的索引 这里顺序要和生成点 对应起来 平行四边形生成点不唯一
# 平面都是在同一个平面
lines = [[0, 2], [2, 1], [1, 3], [3, 0]] # 0代表A 1代表第二个B [0, 2]代表第一个点和第三个点的连线
# AC CB BD DA
self.line_set = o3d.geometry.LineSet( # 创建线组 这个线组是平行四边形的线组
points=o3d.utility.Vector3dVector(points),
lines=o3d.utility.Vector2iVector(lines), )
colors_lines = [[1, 0, 0] for i in range(len(lines))] # 定义平行四边形线段颜色
self.line_set.colors = o3d.utility.Vector3dVector(colors_lines) # 设置平行四边形线的颜色
self.vis.add_geometry(self.pcd) # 将点云格式的三已知点ABC添加到可视化工具中
self.vis.add_geometry(self.line_set) # 将平行四边形线集添加到可视化工具中
设置qt timer定时触发事件,删除原有的视线并创建下一条视线
def draw_update(self):
# 更新可视化
################################################################
if self.current_line_index < len(self.end_point): # 判断是否跑完了所有端点
if self.current_line_index != 0: # 如果线的索引不是0就删除之前的画的线
self.vis.remove_geometry(self.line_set_eye)
# 射线的端点
start_point = self.start_point
end_point = self.end_point[self.current_line_index]
# 将新线添加到视线集中
self.line_set_eye.points = o3d.utility.Vector3dVector(np.array([start_point, end_point]))
self.line_set_eye.lines = o3d.utility.Vector2iVector([[0, 1]])
# 设置视线线条的颜色为蓝色
self.line_set_eye.colors = o3d.utility.Vector3dVector([[0, 0, 1]])
# 把新的视线添加到图形
self.vis.add_geometry(self.line_set_eye)
# 视线索引+1
self.current_line_index += 1
################################################################
# 对Open3D可视化窗口进行更新
self.vis.poll_events()
self.vis.update_renderer()
启动定时器并且设置定时器时间和触发事件
def start_timer(self):
self.clock = QTimer(self) # 创建一个计时器
self.clock.timeout.connect(self.draw_update) # 将计时器的timeout信号连接到draw_update方法
# 设置计时器每0毫秒触发一次 但并不是真的0毫秒触发一次 而是每次draw_update运行结束 就马上重新运行一次
self.clock.start(0)
xyz坐标文本加载成array代码和判断是否四个点在一个平面上的代码
def load_txt(self, file_path):
# 从文本文件读取点云数据
points = []
with open(file_path, 'r') as file:
for line in file:
# 分割每一行的内容,转换为浮点数,并添加到列表中
x, y, z = map(float, line.strip().split(' '))
points.append([x, y, z])
# 将列表转换为NumPy数组,便于后续处理
points = np.array(points)
return points
def are_points_coplanar(self, A, B, C, D): # 验证四个点确实在一个平面上
# 计算向量
AB = B - A
AC = C - A
AD = D - A
# 计算叉乘
cross1 = np.cross(AB, AC)
cross2 = np.cross(AB, AD)
# 计算两个叉乘的点乘
dot = np.dot(cross1, cross2)
# 如果点乘接近于0,则两个叉乘向量平行,点共面
return dot < 0.00001 # 小于计算误差的话 即可确定他们确实在一个平面上
4.整体代码:
import sys
import win32gui # 用于Windows GUI编程,这里主要用来查找Open3D创建的窗口句柄
import numpy as np
import open3d as o3d # 用于3D数据处理和可视化
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget # 用于创建和管理GUI应用程序的窗口
from PyQt5.QtGui import QWindow # 提供了创建和操作窗口的功能
from PyQt5.QtCore import QTimer # 提供定时器功能,用于周期性的执行任务
import designer # 导入ui转换的那个py文件
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.ui = designer.Ui_MainWindow() # 初始化ui界面
self.ui.setupUi(self) # 设置界面
self.start_point = None
self.end_point = None
# 初始化open3D可视化器 并且初始化视线线集
self.init_visualizer()
# 创建和添加open3D的窗口转化成的QWidget
self.init_display_container()
# 启动pyqt定时器
self.start_timer()
def init_visualizer(self):
self.vis = o3d.visualization.Visualizer() # 创建一个可视化器
self.vis.create_window(visible=False) # 创建一个不可见的open3D窗口,用于后台渲染
self.line_set_eye = o3d.geometry.LineSet() # 初始化空的线组 注意这里是视线
self.current_line_index = 0 # 初始化当前视线索引
def init_display_container(self):
# 查找Open3D创建的窗口的句柄 open3D代表窗口标题 ,这两个把任何一个设置成None都能正常运行
self.winid = win32gui.FindWindow('GLFW30', 'open3D')
# 从句柄创建QWindow
self.sub_window = QWindow.fromWinId(self.winid)
# 将QWindow转换为QWidget
self.displayer = QWidget.createWindowContainer(self.sub_window)
# 将这个QWidget添加到UI布局的gridLayout中
self.ui.gridLayout.addWidget(self.displayer)
def find_four_points(self, A, B, C):
# 根据加载的点计算第四个点D的坐标
A = point_array[0]
B = point_array[1]
C = point_array[2]
D = np.array([A[0] + B[0] - C[0], A[1] + B[1] - C[1], A[2] + B[2] - C[2]])
# 判断是否在一个平面上
print(window.are_points_coplanar(A, B, C, D))
# 将计算得到的四个点组成一个新的NumPy数组
points = np.array([A, B, C, D])
return points
def draw_ABCD_point2line_and_add_geometry(self, points):
#
# 读取已知的三个点的数据 这里为了区分已知的ABC和推断出的那个点D 对ABC进行了染色
self.pcd = o3d.io.read_point_cloud('xyz.txt', format="xyz") # 以点云的方式读取已知的三个点
colors_points = [[0, 1, 0], [0, 1, 0], [0, 1, 0]]
self.pcd.colors = o3d.utility.Vector3dVector(colors_points) # 设置已知的三个点的颜色
# 定义线段的索引 这里顺序要和生成点 对应起来 前面
# 平面都是在同一个平面
lines = [[0, 2], [2, 1], [1, 3], [3, 0]] # 0代表第一个点 1代表第二个 [0, 2]代表第一个点和第三个点的连线
# AC CB BD DA
self.line_set = o3d.geometry.LineSet( # 创建线组 这个线组是平行四边形的线组
points=o3d.utility.Vector3dVector(points),
lines=o3d.utility.Vector2iVector(lines), )
colors_lines = [[1, 0, 0] for i in range(len(lines))] # 定义平行四边形线段颜色
self.line_set.colors = o3d.utility.Vector3dVector(colors_lines) # 设置平行四边形线的颜色
self.vis.add_geometry(self.pcd) # 将点云格式的三已知点ABC添加到可视化工具中
self.vis.add_geometry(self.line_set) # 将平行四边形线集添加到可视化工具中
def draw_update(self):
# 更新可视化
################################################################
if self.current_line_index < len(self.end_point): # 判断是否跑完了所有端点
if self.current_line_index != 0: # 如果线的索引不是0就删除之前的画的线
self.vis.remove_geometry(self.line_set_eye)
# 射线的端点
start_point = self.start_point
end_point = self.end_point[self.current_line_index]
# 将新线添加到视线集中
self.line_set_eye.points = o3d.utility.Vector3dVector(np.array([start_point, end_point]))
self.line_set_eye.lines = o3d.utility.Vector2iVector([[0, 1]])
# 设置视线线条的颜色为蓝色
self.line_set_eye.colors = o3d.utility.Vector3dVector([[0, 0, 1]])
# 把新的视线添加到图形
self.vis.add_geometry(self.line_set_eye)
# 视线索引+1
self.current_line_index += 1
################################################################
# Open3D可视化更新的调用
self.vis.poll_events()
self.vis.update_renderer()
def start_timer(self):
self.clock = QTimer(self) # 创建一个计时器
self.clock.timeout.connect(self.draw_update) # 将计时器的timeout信号连接到draw_update方法
# 设置计时器每0毫秒触发一次 但并不是真的0毫秒触发一次 而是每次draw_update运行结束 就马上重新运行一次
self.clock.start(0)
def load_txt(self, file_path):
# 从文本文件读取点云数据
points = []
with open(file_path, 'r') as file:
for line in file:
# 分割每一行的内容,转换为浮点数,并添加到列表中
x, y, z = map(float, line.strip().split(' '))
points.append([x, y, z])
# 将列表转换为NumPy数组,便于后续处理
points = np.array(points)
return points
def are_points_coplanar(self, A, B, C, D): # 验证四个点确实在一个平面上
# 计算向量
AB = B - A
AC = C - A
AD = D - A
# 计算叉乘
cross1 = np.cross(AB, AC)
cross2 = np.cross(AB, AD)
# 计算两个叉乘的点乘
dot = np.dot(cross1, cross2)
# 如果点乘接近于0,则两个叉乘向量平行,点共面
return dot < 0.00001 # 小于计算误差的话 即可确定他们确实在一个平面上
#
#
# def __del__(self):
# self.vis.destroy_window()
# 主程序入口
if __name__ == '__main__':
app = QApplication(sys.argv) # 创建一个QApplication,作为GUI应用程序的入口
window = MainWindow() # 实例化MainWindow
point_cloud_txt = 'xyz.txt' # 定义已知的三个点的文件路径
point_array = window.load_txt(point_cloud_txt) # 加载三个点的数据 返回[3,3]的array数组
# 根据三个点A,B,C的位置获得第四个点D 并返回[4, 3]的数组
points = window.find_four_points(point_array[0], point_array[1], point_array[2])
window.start_point = np.array([4, 4, 5]) # 设置射线起点
window.end_point = window.load_txt('xyz_vector_2.txt') # 加载射线终点
# 给ABC三个点染色,并绘制出ABCD这个平行四边形
window.draw_ABCD_point2line_and_add_geometry(points)
window.show() # 显示主窗口
sys.exit(app.exec_()) # 进入程序的主循环,等待用户事件并作出响应,直到退出程序
5.问题
1)现在用到的视线的起点是固定的起点,终点是平行四边形四条线间隔0.08采样离散点得来的 是加载的txt文件得到的array
如果是不断检测得到的起点和终点
想法是 定义一个起点和终点 然后把检测结果赋值给他俩 让这个定时器 判断这个起点和终点是不是变了 变了之后然后就触发更新 删除视线集中原有的点 然后将这个新的点添加进去