open3D嵌入pyqt+动态射线及平面可视化

参考:在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

如果是不断检测得到的起点和终点

想法是 定义一个起点和终点 然后把检测结果赋值给他俩 让这个定时器 判断这个起点和终点是不是变了 变了之后然后就触发更新 删除视线集中原有的点 然后将这个新的点添加进去

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PyQt是一个流行的Python GUI库,而Open3D是一个开源的3D数据处理库。嵌入Open3DPyQt中可以实现在PyQt界面中显示和操作三维数据。 首先,您需要安装PyQtOpen3D库。您可以使用pip命令在Python环境中安装它们: ``` pip install PyQt5 pip install open3d ``` 然后,您需要创建一个PyQt应用程序并添加一个窗口部件来显示Open3D视图。可以使用Qt的QVBoxLayout和QHBoxLayout来布局界面,并将Open3D视图嵌入到QWidget中。例如: ```python from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout import open3d as o3d class MyWidget(QWidget): def __init__(self): super().__init__() # 创建Open3D视图 self.pcd = o3d.geometry.PointCloud() self.pcd.points = o3d.utility.Vector3dVector([[0, 0, 0], [1, 0, 0], [0, 1, 0]]) self.pcd.paint_uniform_color([1, 0, 0]) self.vis = o3d.visualization.VisualizerWithPointCloud() self.vis.create_window() self.vis.add_geometry(self.pcd) # 创建布局 layout = QVBoxLayout() layout.addWidget(self.vis) # 设置主窗口布局 self.setLayout(layout) if __name__ == '__main__': app = QApplication([]) window = MyWidget() window.show() app.exec_() ``` 在上面的示例中,我们使用Open3D创建了一个简单的点云,并使用布局和窗口部件将Open3D视图嵌入PyQt应用程序中。此时,您将能够在PyQt界面中看到该点云的可视化效果。 当然,您还可以根据需要添加其他功能,例如加载和显示其他类型的3D模型、编辑点云等等。这只是一个简单的例子,希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值