Saving 3D rendering images without displays on python + OpenGL
Sometimes, you need to create 3D models of the world and assume lights in order to render the view image.
OpenGL, probably the most famous 3D-vision library, has PyOpenGL, which is a python API. However, OpenGL is not designed to render images without displays. This is sometimes not the case that we want to do. You probably want to get matrices of the image or to save them to files, WITHOUT display.
There seem to be another option, blender, which has python API as well. However again, blender does not allow the users to touch the API outside blender SDK. In other words, it is not possible to type python *.py
without starting blender itself.
The Internet has several blogs of stackoverflows for such an attempt for both of the options. But I could not make it because of the dead link or old information, at least when this post is written on December 2018.
Environment
- macOS: Mojave version 10.14.
- OpenGL: preinstalled on Mac. version 2.1 INTEL-12.2.17
- python 3 on anaconda 4.0.0
How-to
- Install PyOpenGL (version 3.1.0 in my environment)
pip install PyOpenGL PyOpenGL_accelerate
2. Install GLFW
brew tap homebrew/versions
brew install glfw3
3. Install glfw, a python library for GLFW. (version 1.7.0 in my environment)
pip install glfw
Code example
Now it’s ready. We can access the data through glReadPixels
without any displays (VISIBLE flag is False)
import cv2
import numpy as np
from OpenGL.GL import *
from OpenGL.GLU import *
import glfwlight_ambient = [0.25, 0.25, 0.25]
light_position = [-10, 5, 0, 2]def main():
DISPLAY_WIDTH = 900
DISPLAY_HEIGHT = 900# Initialize the library
if not glfw.init():
return
# Set window hint NOT visible
glfw.window_hint(glfw.VISIBLE, False)
# Create a windowed mode window and its OpenGL context
window = glfw.create_window(DISPLAY_WIDTH, DISPLAY_HEIGHT, "hidden window", None, None)
if not window:
glfw.terminate()
return# Make the window's context current
glfw.make_context_current(window)gluPerspective(90, (DISPLAY_WIDTH / DISPLAY_HEIGHT), 0.01, 12)glEnable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient) # 環境光
glLightfv(GL_LIGHT0, GL_POSITION, light_position) # 光源の位置
glEnable(GL_LIGHT0)
glEnable(GL_LIGHTING)glRotatef(-90, 1, 0, 0) # Straight rotation
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glRotatef(285, 0, 0, 1) # Rotate yaw
glTranslatef(-5, -3, -2) # Move to position# Draw rectangle
glBegin(GL_QUADS)
glColor3f(1, 0, 0)
glVertex3f(2, 2, 0)
glVertex3f(2, 2, 2)
glVertex3f(2, 6, 2)
glVertex3f(2, 6, 0)
glEnd()image_buffer = glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, OpenGL.GL.GL_RGB, OpenGL.GL.GL_UNSIGNED_BYTE)
image = np.frombuffer(image_buffer, dtype=np.uint8).reshape(DISPLAY_WIDTH, DISPLAY_HEIGHT, 3)cv2.imwrite("image.png", image)glfw.destroy_window(window)
glfw.terminate()if __name__ == "__main__":
main()
Thanks to this stackoverflow.
Disclaimer
Maybe this is not going to work in the future.