【问题标题】:Point Cloud Distortion when Drawing with modern OpenGL使用现代 OpenGL 绘图时的点云失真
【发布时间】:2020-11-08 23:45:21
【问题描述】:

我发现nice tutorial 使用 PyQt 和现代 OpenGL 绘制和旋转立方体。我的目标是通过执行以下操作来调整脚本以适应点云(另请参见下面的代码):

  1. 使用 Open3D 加载点云并将坐标和颜色提取为 numpy 数组
  2. 从数组创建顶点缓冲区对象 (VBO)
  3. 将绘图功能改为gl.glDrawElements(gl.GL_POINTS, ...)

不幸的是,点云非常扭曲和薄(见截图)。它实际上应该是一个有椅子和墙壁的房间。

你看我是不是画错了 VBO 或绘图?或者有没有更好的加载点云的方法?

我使用旧的固定管道 (glBegin(GL_POINTS) ... glEnd()) 测试了该示例,并且正确绘制了点云(但性能非常糟糕!)。

from PyQt5 import QtCore      # core Qt functionality
from PyQt5 import QtGui       # extends QtCore with GUI functionality
from PyQt5 import QtOpenGL    # provides QGLWidget, a special OpenGL QWidget
from PyQt5 import QtWidgets

import OpenGL.GL as gl        # python wrapping of OpenGL
from OpenGL import GLU        # OpenGL Utility Library, extends OpenGL functionality
from OpenGL.arrays import vbo
import numpy as np
import open3d as o3d
import sys

# Loading the point cloud from file
def load_pointcloud():
    pcd = o3d.io.read_point_cloud("../pointclouds/0004.ply")
    print(pcd)
    print("Pointcloud Center: " + str(pcd.get_center()))
    points = np.asarray(pcd.points)
    colors = np.asarray(pcd.colors)

    return points, colors

#### here was only the GUI code (slider, ...) , which works fine! ####
        
class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent=None):
        self.parent = parent
        QtOpenGL.QGLWidget.__init__(self, parent)

    def initializeGL(self):
        self.qglClearColor(QtGui.QColor(250, 250, 250))     # initialize the screen to blue
        gl.glEnable(gl.GL_DEPTH_TEST)                   # enable depth testing

        self.initGeometryPC()

        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0

    def setRotX(self, val):
        self.rotX = val

    def setRotY(self, val):
        self.rotY = val

    def setRotZ(self, val):
        self.rotZ = val
        
    def resizeGL(self, width, height):
        gl.glViewport(0, 0, width, height)
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()
        aspect = width / float(height)

        #GLU.gluPerspective(45.0, aspect, 1.0, 100.0)   #GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
        gl.glOrtho(-2.0, 2.0, -2.0, 2.0, 1.0, 100.0)
 
        gl.glMatrixMode(gl.GL_MODELVIEW)
        
    def paintGL(self):
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)

        gl.glPushMatrix()                       # push the current matrix to the current stack

        gl.glTranslate(0.0, 0.0, -5.0)          # third, translate cube to specified depth
        #gl.glScale(.5, .5, .5)                 # second, scale point cloud
        gl.glRotate(self.rotX, 1.0, 0.0, 0.0)
        gl.glRotate(self.rotY, 0.0, 1.0, 0.0)
        gl.glRotate(self.rotZ, 0.0, 0.0, 1.0)
        gl.glTranslate(-0.5, -0.5, -0.5)        # first, translate point cloud center to origin

        gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
        gl.glEnableClientState(gl.GL_COLOR_ARRAY)

        gl.glVertexPointer(3, gl.GL_FLOAT, 0, self.vertVBO)
        gl.glColorPointer(3, gl.GL_FLOAT, 0, self.colorVBO)

        gl.glPointSize(2)
        gl.glDrawElements(gl.GL_POINTS, len(self.pointsIdxArray), gl.GL_UNSIGNED_INT, self.pointsIdxArray)
        
        gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
        gl.glDisableClientState(gl.GL_COLOR_ARRAY)

        gl.glPopMatrix()    # restore the previous modelview matrix
        
    # Push geometric data to GPU
    def initGeometryPC(self):
        points, colors = load_pointcloud()
    
        self.pointsVtxArray = points
        self.vertVBO = vbo.VBO(np.reshape(self.pointsVtxArray, (1, -1)).astype(np.float32))
        self.vertVBO.bind()
        
        self.pointsClrArray = colors
        self.colorVBO = vbo.VBO(np.reshape(self.pointsClrArray, (1, -1)).astype(np.float32))
        self.colorVBO.bind()
        
        self.pointsIdxArray = np.arange(len(points))


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())

【问题讨论】:

  • 您的 VBO 用法完全不清楚,但由于 colorVBO 似乎是最后一个绑定的,您很可能会从颜色 VBO 中获取您的位置数据。您对 gl*Pointer() 函数的使用是错误的,最后一个参数是当前绑定的 GL_ARRAY_BUFFER 对象的字节偏移量,但您似乎指定了缓冲区名称。
  • 感谢@derhass 的回复!你是对的,它以颜色为位置。当我评论所有颜色的 VBO 代码时,它会以白色显示正确的点云。虽然我有点困惑。只有当我保留gl.glVertexPointer(3, gl.GL_FLOAT, 0, self.vertVBO) 时,它才有效。如果我将最后一个参数更改为 0,因为它需要一个指向第一个顶点坐标的指针,它就不再起作用了。那么可能是关于步幅参数(但 VBO 应该紧密包装)?我没有找到这些命令的示例,你能提示我正确的配置吗?

标签: python opengl pyqt5 pyopengl


【解决方案1】:

经过长时间的搜索,我发现了这个stackoverflow-post。我通过将点坐标和颜色一起存储在一个 vbo 对象 (gl.glGenBuffers(1)) 中来调整我的代码以适应该答案。然后我用特定的步幅和偏移量定义顶点和颜色指针:

  • gl.glVertexPointer(3, gl.GL_FLOAT, 6*4, None)
    • Stride= 24 字节:[x, y, z, r, g, b] * sizeof(float)
  • gl.glColorPointer(3, gl.GL_FLOAT, 6*4, ctypes.c_void_p(3*4))
    • Offset= 12 字节:rgb 颜色在 3 个坐标 x、y、z 之后开始

最后我使用gl.glDrawArrays(gl.GL_POINTS, 0, noOfVertices) 来绘制点云。

完整代码见下(标有### NEW ###cmets):

from PyQt5 import QtCore      # core Qt functionality
from PyQt5 import QtGui       # extends QtCore with GUI functionality
from PyQt5 import QtOpenGL    # provides QGLWidget, a special OpenGL QWidget
from PyQt5 import QtWidgets

import OpenGL.GL as gl        # python wrapping of OpenGL
from OpenGL import GLU        # OpenGL Utility Library, extends OpenGL functionality
from OpenGL.arrays import vbo
import numpy as np
import open3d as o3d
import ctypes 
import sys                    # we'll need this later to run our Qt application

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)    # call the init for the parent class

        self.resize(300, 300)
        self.setWindowTitle('Hello OpenGL App')
        
        self.glWidget = GLWidget(self)
        self.initGUI()
        
        timer = QtCore.QTimer(self)
        timer.setInterval(20)   # period, in milliseconds
        timer.timeout.connect(self.glWidget.updateGL)
        timer.start()
        
    def initGUI(self):
        central_widget = QtWidgets.QWidget()
        gui_layout = QtWidgets.QVBoxLayout()
        central_widget.setLayout(gui_layout)

        self.setCentralWidget(central_widget)

        gui_layout.addWidget(self.glWidget)

        sliderX = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        sliderX.valueChanged.connect(lambda val: self.glWidget.setRotX(val))

        sliderY = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        sliderY.valueChanged.connect(lambda val: self.glWidget.setRotY(val))

        sliderZ = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        sliderZ.valueChanged.connect(lambda val: self.glWidget.setRotZ(val))

        gui_layout.addWidget(sliderX)
        gui_layout.addWidget(sliderY)
        gui_layout.addWidget(sliderZ)
        
class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent=None):
        self.parent = parent
        QtOpenGL.QGLWidget.__init__(self, parent)

    def initializeGL(self):
        self.qglClearColor(QtGui.QColor(100, 100, 100))     # initialize the screen to blue
        gl.glEnable(gl.GL_DEPTH_TEST)                       # enable depth testing

        self.initGeometry()

        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0

    def setRotX(self, val):
        self.rotX = val

    def setRotY(self, val):
        self.rotY = val

    def setRotZ(self, val):
        self.rotZ = val
        
    def resizeGL(self, width, height):
        gl.glViewport(0, 0, width, height)
        gl.glMatrixMode(gl.GL_PROJECTION)
        gl.glLoadIdentity()
        aspect = width / float(height)

        GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
        gl.glMatrixMode(gl.GL_MODELVIEW)
        
    def paintGL(self):
        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)

        gl.glPushMatrix()    # push the current matrix to the current stack

        gl.glTranslate(0.0, 0.0, -3.0)    # third, translate cube to specified depth
        #gl.glScale(20.0, 20.0, 20.0)       # second, scale cube
        gl.glRotate(self.rotX, 1.0, 0.0, 0.0)
        gl.glRotate(self.rotY, 0.0, 1.0, 0.0)
        gl.glRotate(self.rotZ, 0.0, 0.0, 1.0)
        gl.glTranslate(-0.5, -0.5, -0.5)   # first, translate cube center to origin

        # Point size
        gl.glPointSize(3)
        
        ### NEW ###
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vbo)

        stride = 6*4 # (24 bates) : [x, y, z, r, g, b] * sizeof(float)

        gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
        gl.glVertexPointer(3, gl.GL_FLOAT, stride, None)

        gl.glEnableClientState(gl.GL_COLOR_ARRAY)
        offset = 3*4 # (12 bytes) : the rgb color starts after the 3 coordinates x, y, z 
        gl.glColorPointer(3, gl.GL_FLOAT, stride, ctypes.c_void_p(offset))
        
        noOfVertices = self.noPoints
        gl.glDrawArrays(gl.GL_POINTS, 0, noOfVertices)

        gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
        gl.glDisableClientState(gl.GL_COLOR_ARRAY)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
        ### NEW ###

        gl.glPopMatrix()    # restore the previous modelview matrix
        

    def initGeometry(self):
    
        vArray = self.LoadVertices()
        self.noPoints  = len(vArray) // 6
        print("No. of Points: %s" % self.noPoints)
        
        self.vbo = self.CreateBuffer(vArray)

        
    ### NEW ###

    def LoadVertices(self):
        
        pcd = o3d.io.read_point_cloud("../pointclouds/0004.ply")
        print(pcd)
        print("Pointcloud Center: " + str(pcd.get_center()))
    
        points = np.asarray(pcd.points).astype('float32')
        colors = np.asarray(pcd.colors).astype('float32')
        
        attributes = np.concatenate((points, colors),axis=1)
        print("Attributes shape: " + str(attributes.shape))
        
        return attributes.flatten()

    def CreateBuffer(self, attributes):
        bufferdata = (ctypes.c_float*len(attributes))(*attributes) # float buffer
        buffersize = len(attributes)*4                             # buffer size in bytes 

        vbo = gl.glGenBuffers(1)
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
        gl.glBufferData(gl.GL_ARRAY_BUFFER, buffersize, bufferdata, gl.GL_STATIC_DRAW) 
        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
        return vbo
        
    ### NEW ###


if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)

    win = MainWindow()
    win.show()

    sys.exit(app.exec_())

但是,我仍然没有找到上述初始方法的正确参数,其中两个独立的 VBO 用于坐标和颜色。所以我很高兴有更多的 cmets。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-07
    相关资源
    最近更新 更多