使用现代 OpenGL 绘图时的点云失真
Point Cloud Distortion when Drawing with modern OpenGL
我发现 nice tutorial 使用 PyQt 和现代 OpenGL 绘制和旋转立方体。我的 objective 是通过执行以下操作来调整点云的脚本(另请参见下面的代码):
- 使用 Open3D 加载点云并将坐标和颜色提取为 numpy 数组
- 从数组创建顶点缓冲区对象 (VBO)
- 将绘图功能更改为
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 对象 (gl.glGenBuffers(1)
) 中来调整我的代码以适应该答案。然后我用特定的步幅和偏移量定义顶点和颜色指针:
gl.glVertexPointer(3, gl.GL_FLOAT, 6*4, None)
- 步幅= 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 ###
注释):
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 用于坐标和颜色。所以我很高兴收到进一步的评论。
我发现 nice tutorial 使用 PyQt 和现代 OpenGL 绘制和旋转立方体。我的 objective 是通过执行以下操作来调整点云的脚本(另请参见下面的代码):
- 使用 Open3D 加载点云并将坐标和颜色提取为 numpy 数组
- 从数组创建顶点缓冲区对象 (VBO)
- 将绘图功能更改为
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_())
经过长时间的搜索,我找到了这个 gl.glGenBuffers(1)
) 中来调整我的代码以适应该答案。然后我用特定的步幅和偏移量定义顶点和颜色指针:
gl.glVertexPointer(3, gl.GL_FLOAT, 6*4, None)
- 步幅= 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 ###
注释):
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 用于坐标和颜色。所以我很高兴收到进一步的评论。