点云标注工具开发记录(四)之点云根据类别展示与加速渲染

先前我们使用的是Open3D进行点云加载与展示,但由于Open3D更侧重于点云处理,其缺少一些相关的GUI控件,因此采用PyQt进行开发,同时使用OpenGL进行3D渲染,那么具体要如何实现呢?

复选框类别选中点云展示

如何开发根据点云类别展示点云的功能呢,效果如下:

当我们的复选框被取消时,相应的点云会不显示

main.py文件中,其代码如下:

首先需要给复选框绑定事件

python 复制代码
check_box.stateChanged.connect(functools.partial(self.point_cloud_visible))

self.openGLWidget.categorys是点云的类别,其值如下:

接下来的事件处理都是围绕着self.openGLWidget.categorys展开的,其值代表着每个点云所属的类别

事件处理逻辑如下:

python 复制代码
def point_cloud_visible(self):
		#首先是判断当前的展示类型,是否是CATEGORY
        if self.openGLWidget.display == DISPLAY.CATEGORY:
        #根据点云类别颜色生成蒙版,初始全为True
            mask = np.ones(self.openGLWidget.categorys.shape, dtype=bool)
            #遍历类别复选框,根据索引号与self.openGLWidget.categorys中的点云类别值做匹配,从而选择这些点云是否可见
            for index in range(self.label_listWidget.count()):#self.label_listWidget.count()的值为8
                item = self.label_listWidget.item(index)
                widget = self.label_listWidget.itemWidget(item)
                check_box = widget.findChild(QtWidgets.QCheckBox, 'check_box')
                #如果当前的索引下被选中
                if not check_box.isChecked():
                	#根据索引index使对应的mask变为Fale,即不可见
                    mask[self.openGLWidget.categorys==index] = False
                    self.openGLWidget.category_display_state_dict[index] = False
                    #其值{0: True, 1:False, 2: True, 3: True, 4: True, 5: True, 6: True, 7: True}
                else:
                    self.openGLWidget.category_display_state_dict[index] = True
			#获得最终的mask
            self.openGLWidget.mask = mask
            #根据mask更改当前点云,包含坐标和颜色
            self.openGLWidget.current_vertices = self.openGLWidget.pointcloud.xyz[self.openGLWidget.mask]
            self.openGLWidget.current_colors = self.openGLWidget.category_color[self.openGLWidget.mask]

        elif self.openGLWidget.display == DISPLAY.INSTANCE:
            mask = np.ones(self.openGLWidget.instances.shape, dtype=bool)
            for index in range(self.label_listWidget.count()):
                item = self.label_listWidget.item(index)
                widget = self.label_listWidget.itemWidget(item)
                label_instance = widget.findChild(QtWidgets.QLabel, 'label_instance')
                label_instance = int(label_instance.text())
                check_box = widget.findChild(QtWidgets.QCheckBox, 'check_box')
                if not check_box.isChecked():
                    mask[self.openGLWidget.instances==label_instance] = False
                    self.openGLWidget.instance_display_state_dict[label_instance] = False
                else:
                    self.openGLWidget.instance_display_state_dict[label_instance] = True

            self.openGLWidget.mask = mask
            self.openGLWidget.current_vertices = self.openGLWidget.pointcloud.xyz[self.openGLWidget.mask]
            self.openGLWidget.current_colors = self.openGLWidget.instance_color[self.openGLWidget.mask]
        #重新渲染点云
        self.openGLWidget.init_vertex_vao()
        self.openGLWidget.update()

最终效果如下:

点云渲染加速

每当我们执行了某个操作,最后要想对点云有效果,最后都需要调用一段代码:

python 复制代码
self.openGLWidget.init_vertex_vao()
self.openGLWidget.update()

其中init_vertex_vao()是我们自定义的,即初始化点云(每次点云发生修改都需要进行初始化,这里的初始化可以认为是重写)而update函数则是OpenGL的更新函数

opengl_widget.py中的init_vertex_vao函数定义代码如下:

这段代码主要是将一些数据加载到缓冲区,用以提升渲染效率,这里我们需要了解一些基础概念:

  • VBO vertex buffer object 顶点缓冲对象 :VBOCPUGPU传递信息的桥梁,我们把数据存入VBO是在CPU上操作,VBO会自动将数据送至GPU。送至GPU不需要任何人为操作。
  • VAO vertex array object 顶点数组对象:VBO将顶点数据传送至GPU只是一堆数字,要怎么向GPU解释它们呢,就需要VAO,其内包含的是顶点数据的格式信息
  • EBO element(index) buffer object 索引缓冲对象
python 复制代码
   def init_vertex_vao(self):
        self.vertex_vao = glGenVertexArrays(1)
		#请求生成 2 个新的缓冲区对象名称(VBO)
        vbos = glGenBuffers(2)
        #将特定的缓冲区对象名称(或称为"缓冲区标识符"即vbos[0])与特定的缓冲区目标绑定在一起,GL_ARRAY_BUFFER 是一个枚举值,指定了缓冲区的目标类型。在这个例子中,它表示该缓冲区将用作顶点属性数据(如顶点位置、颜色、纹理坐标等)的存储,即该段代码的含义是绑定
        glBindBuffer(GL_ARRAY_BUFFER, vbos[0])
        #加载是数据,glBufferData 或 glBufferSubData 函数来上传数据到缓冲区
        glBufferData(GL_ARRAY_BUFFER, self.current_vertices.nbytes, self.current_vertices, GL_STATIC_DRAW)
        glBindBuffer(GL_ARRAY_BUFFER, 0)
        
		#这段代码首先将vbos[1]绑定到GL_ARRAY_BUFFER目标上,然后使用glBufferData函数上传self.current_vertices数组中的数据到该缓冲区对象。self.current_colors.nbytes指定了数据的大小(以字节为单位),GL_STATIC_DRAW表示数据的使用模式(即数据很少更改,且会被多次绘制)。最后,将GL_ARRAY_BUFFER的绑定解除(绑定到0)。
        glBindBuffer(GL_ARRAY_BUFFER, vbos[1])
        glBufferData(GL_ARRAY_BUFFER, self.current_colors.nbytes, self.current_colors, GL_STATIC_DRAW)
        glBindBuffer(GL_ARRAY_BUFFER, 0)

        glBindVertexArray(self.vertex_vao)
        glBindBuffer(GL_ARRAY_BUFFER, vbos[0])
        glEnableVertexAttribArray(0)
        #glVertexAttribPointer函数的参数指定了顶点属性的位置、大小、类型、是否归一化、步长以及偏移量。
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, self.current_vertices.itemsize * 3, ctypes.c_void_p(0))
        #ctypes.c_void_p(0)用于指定顶点属性数据的偏移量。在这个例子中,由于数据是紧密打包的,且从缓冲区的开始位置读取,所以偏移量为0。
        glBindBuffer(GL_ARRAY_BUFFER, 0)

        glBindBuffer(GL_ARRAY_BUFFER, vbos[1])
        glEnableVertexAttribArray(1)
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, self.current_colors.itemsize * 3, ctypes.c_void_p(0))
        glBindBuffer(GL_ARRAY_BUFFER, 0)
		#将VAO的绑定解除。
        glBindVertexArray(0)

点云拾取

在点云标注中,获取点云坐标是一个十分重要的功能。

首先,双击鼠标触发事件,这里,获取点云坐标的功能是将该点作为坐标中心点

python 复制代码
def mouseDoubleClickEvent(self, event: QtGui.QMouseEvent):
		#获取点击的坐标
        x, y = event.pos().x(), self.height() - event.pos().y()
        if self.pointcloud is None:
            return
        #计算当前点的点云位置
        point = self.pickpoint(x, y)
        print(point)
        # 双击点移动到坐标中心
        if point.size:
            self.vertex_transform.setTranslationwithRotate(-point[0], -point[1], -point[2])
        self.update()

计算点云坐标的代码实现如下:

python 复制代码
def pickpoint(self, x, y):
        if self.pointcloud is None:return np.array([])

        point1 = QVector3D(x, y, 0).unproject(self.camera.toMatrix() * self.vertex_transform.toMatrix(),
                                              self.projection,
                                              QtCore.QRect(0, 0, self.width(), self.height()))
        point2 = QVector3D(x, y, 1).unproject(self.camera.toMatrix() * self.vertex_transform.toMatrix(),
                                              self.projection,
                                              QtCore.QRect(0, 0, self.width(), self.height()))
        vector = (point2 - point1)  # 直线向量
        vector.normalize()

        # 点到直线(点向式)的距离
        t = (vector.x() * (self.current_vertices[:, 0] - point1.x()) +
             vector.y() * (self.current_vertices[:, 1] - point1.y()) +
             vector.z() * (self.current_vertices[:, 2] - point1.z())) / (vector.x() ** 2 + vector.y() ** 2 + vector.z() ** 2)

        d = (self.current_vertices[:, 0] - (vector.x() * t + point1.x())) ** 2 + \
            (self.current_vertices[:, 1] - (vector.y() * t + point1.y())) ** 2 + \
            (self.current_vertices[:, 2] - (vector.z() * t + point1.z())) ** 2

        pickpoint_radius = self.pickpoint_radius * self.ortho_change_scale
        mask = d < pickpoint_radius**2

        if not any(mask):
            return np.array([])

        mask1 = self.mask.copy()
        mask1[mask1==True] = mask
        points = self.pointcloud.xyz[mask1]
        index = np.argmin(points[:, 0] * vector.x() + points[:, 1] * vector.y() + points[:, 2] * vector.z())
        point = points[index]   # 取最近的点
        return point

效果如下:

相关推荐
凌云行者9 小时前
OpenGL入门008——环境光在片段着色器中的应用
c++·cmake·opengl
闲暇部落4 天前
Android OpenGL ES详解——立方体贴图
opengl·天空盒·立方体贴图·环境映射·动态环境贴图
闲暇部落5 天前
Android OpenGL ES详解——实例化
android·opengl·实例化·实例化数组·小行星带
GIS 数据栈6 天前
博客摘录「 pyqt 为新建子线程传参以及子线程返回数据到主线程」2023年12月7日
笔记·python·pyqt·多线程·多线程通信
闲暇部落7 天前
Android OpenGL ES详解——几何着色器
opengl·法线·法向量·几何着色器
西木九9 天前
解决:WSL2可视化opencv和pyqt冲突:QObject::moveToThread
python·opencv·pyqt
充值内卷10 天前
PyQt入门指南五十一 文档与注释规范
开发语言·python·pyqt
刘好念12 天前
[OpenGL]使用OpenGL实现硬阴影效果
c++·计算机图形学·opengl
王哈哈^_^13 天前
【数据集】【YOLO】【目标检测】树木倒塌识别数据集 9957 张,YOLO道路树木断裂识别算法实战训练教程!
人工智能·深度学习·算法·yolo·目标检测·计算机视觉·pyqt
闲暇部落13 天前
Android OpenGL ES详解——纹理:纹理过滤GL_NEAREST和GL_LINEAR的区别
opengl·texture·linear·纹理过滤·nearest·邻近过滤·线性过滤