12 November 2012

OpenGL screenshot using OpenCV

OpenGL renders on the buffer which is then shown on the screen. It can render on the Back Buffer or the Front Buffer as specified. We can copy this buffer and save it as screenshot or continuous video. The basic opengl functions which are used for such purposes are glDrawBuffer, glReadBuffer and glReadPixels. glDrawBuffer to specify to which color buffer the image is rendered. By default it is front(GL_FRONT) for single-buffered and back(GL_BACK) for double-buffered applications. glReadBuffer is used to determine from whch color buffer the image is read. This function is generally called after the rendering function and before calling glReadPixels which is used to read the color buffer and to store the image in memory.

After copying the image data into a memory location, you can use any image loading/saving library to do rest of the work for you. Some of such libraries are FreeImage, Corona, Devil(Developer's Image Library). I'll be using OpenCV for this purpose as my project was already using OpenCV for some other tasks.

 My OpenGL viewport was of 640x480 so my image buffer was of same dimensions. In OpenGL frame, data is arranged from left to right and rows from bottom to top, while in OpenCV, data is from left to right and rows from top to bottom. So this formatting has to be kept in mind while using any other image library for copying data from OpenGL.

The code for capturing OpenGL frame and saving it using OpenCV -

    cv::Mat img(480, 640, CV_8UC3);
    glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3)?1:4);
    glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
    glReadPixels(0, 0, img.cols, img.rows, GL_BGR_EXT, GL_UNSIGNED_BYTE, img.data);
    cv::Mat flipped(img);
    cv::flip(img, flipped, 0);
    cv::imwrite("snapshot.png", img);

Explanation -
  • First we create an empty matrix 'img' for our data to be read into. 
  • Then you have to account for cv::Mat not neccessarily storing image rows contiguously. There  might be small padding value at the end of each row to make rows 4-byte aligned (or 8?). So you need to mess with the pixels storage modes. glPixelStorei sets pixel storage modes that affect the operation of subsequent glReadPixels. GL_PACK_ALIGNMENT - specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1, 2, 4, 8. GL_PACK_ROW_LENGTH defines the number of pixels in a row.
  • The type of matrix influences the format and type parameters of glReadPixels. If you want color images you have to keep in mind that OpenCV usually stores color values in BGR order while OpenGL stores in RGB. glReadPixels(x, y, width, height, format, type, data) - returns the pixel data from the frame bufer, starting witht eh pixel whose lower left corner is at location x, y into client memory starting at location data.
  • Finally OpenCV stores the images from top to bottom. So you may need to either flip them after getting them or render them flipped in OpenGL in the first place. To flip a cv::Mat vertically, you can use cv::flip.


1 comment:

  1. You did a good job. Screen shots are the best way to explain the important thing. It help us to the sender as well the receiver of the message in the same form.The briefing, codes and explanation makes your blog effective.
    digital signature software