5

I've been looking for a way to convert an OpenGL texture into a OpenCV matrix type. I have found many guides which shows the conversion from OpenCV matrix to OpenGL texture, but sadly not the other way around. I have also read through this and its answer but it did not make me much wiser. I am writing in C++ and using OpenCV3.1 and OpenGL4.4.

EDIT: UPDATED CODE

main.cpp:

#include "CameraCapture.h"
#include "GUIMainWindow.h"
#include "glfw3.h"
#include "Texture.h"

using namespace std;

int main(int argc, char* argv[]) {

    if (!glfwInit()) {
        fprintf(stderr, "Failed to initialize GLFW \n");
        return -1;
    }

    glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
    GLFWwindow* window = glfwCreateWindow(1929, 1341, "OpenGL", NULL, NULL);

    if (window == NULL) {
        fprintf(stderr, "Failed to make GLFW window.");
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    CameraCapture *cc = new CameraCapture();
    cc->CameraCapture::AvailableCameras();
    GLuint texture = cc->CameraCapture::OpenCamera(0);

    glEnable(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, texture);

    drawGLTexture(window);

    Mat out = textureToMat(texture);
    namedWindow("Raytrix feed", WINDOW_AUTOSIZE);
    imshow("Raytrix feed", out);

    waitKey(0);

    return 0;

}

Texture.cpp:

Mat textureToMat(GLuint texture_id) {

    glBindTexture(GL_TEXTURE_2D, texture_id);
    GLenum texture_width, texture_height;

    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint*)&texture_width);
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint*)&texture_height);

    unsigned char* texture_bytes = (unsigned char*)malloc(sizeof(unsigned char)*texture_width*texture_height * 4);

    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_bytes);

    return Mat(texture_height, texture_width, CV_8UC4, texture_bytes);

}

void drawGLTexture(GLFWwindow *window) {

    glColor3f(1.0f, 1.0f, 1.0f);

    glBegin(GL_TRIANGLES);
    glTexCoord2f(0, 1);
    glVertex2f(-1, -1);

    glTexCoord2f(1, 1);
    glVertex2f(1, -1);

    glTexCoord2f(0, 0);
    glVertex2f(-1, 1);

    glTexCoord2f(1, 1);
    glVertex2f(1, -1);

    glTexCoord2f(1, 0);
    glVertex2f(1, 1);

    glTexCoord2f(0, 0);
    glVertex2f(-1, 1);
    glEnd();

    glfwSwapBuffers(window);
    glfwPollEvents();

    glFlush();
    glFinish();

}

Header:

#ifndef TEXTURE_H
#define TEXTURE_H

#include "glew.h"
#include "glfw3.h"

#include <string>
#include <iostream>
#include <vector>
#include <opencv\highgui.h>
#include <opencv\cv.h>
#include <opencv2\opencv.hpp>

using namespace std;
using namespace cv;

Mat textureToMat(GLuint textureID);
void drawGLTexture(GLFWwindow *window);

#endif /*!TEXTURE_H*/
Community
  • 1
  • 1
T. Soemod
  • 91
  • 2
  • 7
  • 1
    Also the answer you linked pretty much shows you the correct way to do it, which part of it did you find hard to understand? – Emily L. Jul 26 '16 at 08:10
  • Right you are. It's early morning and have yet to drink my coffee, i'll update the post. – T. Soemod Jul 26 '16 at 08:11
  • For me it is hard to see where the texture id is used. Say that I load an image in OpenGL and get its texture id, how can I convert this to an OpenCV matrix? – T. Soemod Jul 26 '16 at 08:13
  • @ThorbjørnSømod: By going back two steps, before even loading the texture to OpenGL and put the data right into a OpenCV matrix. OpenGL does not deal with image file formats and as a matter of fact many people actually use OpenCV to load image files (OpenCV, unlike OpenGL, **does** know how to deal with image files and formats) and load the resulting OpenCV mat to OpenGL. Of course if you want to generate images with OpenGL to process them with OpenCV then you'll have to read back the image data, that's what `glReadPixels` is for. But that usually *does not* involve a texture. – datenwolf Jul 26 '16 at 08:48
  • Thanks for the input, but that is not quite what I want. Basically what I have is a system that will output an image given by a texture id. What I want is to load this image into OpenCV so that I can perform various operations on it there. I cannot load it directly into OpenCV, it has to go through the texture id. – T. Soemod Jul 26 '16 at 08:54

2 Answers2

3

I've tested the code below and it seems to work. (Note the usage of GL_BGR in glGetTexImage()).

cv::Mat get_ocv_img_from_gl_img(GLuint ogl_texture_id)
{
    glBindTexture(GL_TEXTURE_2D, ogl_texture_id);
    GLenum gl_texture_width, gl_texture_height;

    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, (GLint*)&gl_texture_width);
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, (GLint*)&gl_texture_height);

    unsigned char* gl_texture_bytes = (unsigned char*) malloc(sizeof(unsigned char)*gl_texture_width*gl_texture_height*3);
    glGetTexImage(GL_TEXTURE_2D, 0 /* mipmap level */, GL_BGR, GL_UNSIGNED_BYTE, gl_texture_bytes);

    return cv::Mat(gl_texture_height, gl_texture_width, CV_8UC3, gl_texture_bytes);
}
sarasvati
  • 792
  • 12
  • 30
  • THANK YOU. You just saved my day! – T. Soemod Jul 26 '16 at 13:15
  • @T.Soemod good to hear that :). If your problem was solved, please consider accepting one of the (currently) two answers. – sarasvati Jul 26 '16 at 13:30
  • I am having some trouble with this again. Was this all of the code you used for the conversion, assuming a proper context? When I try to display the converted image it returns blank (white). – T. Soemod Aug 02 '16 at 12:32
  • @T.Soemod The following is the whole source. https://gist.github.com/anonymous/50cd7f9b51f8f550024bcaec89726494 – sarasvati Aug 02 '16 at 13:11
  • Thank you, I'll try to follow that more closely. – T. Soemod Aug 02 '16 at 13:18
  • @T.Soemod It seems that Zoidberg got malformed by github :). The reference image I used is [here](http://imgur.com/a/FrXnQ). – sarasvati Aug 02 '16 at 13:23
  • Unfortunately it is still not working. All i get is a black screen. Are there some pictures that can not be displayed in this way, given a 2D texture? As in, would i need a shader setup? – T. Soemod Aug 02 '16 at 14:52
  • @T.Soemod it'd be helpful if you'd post (at least part of) your code, otherwise it's hard to tell where's the error. If you've successfully loaded an image into an OpenGL texture, there should be no problem with reading that data with `glGetTexImage()`, so you might try to verify that your image is properly loaded into OpenGL. I don't think you'd need shaders to display your image if you cannot display it in the direct mode. Two questions: (1) Where do you get black screen – when displaying the image via CL or GL? (2) Does my reference image work in your program? (Code example would be useful) – sarasvati Aug 02 '16 at 15:20
  • I've updated my code to sort of mirror the code you posted. The only difference is how I get my texture. The texture i want to convert is given by a 3rd party function in the CameraCapture class. The call used in that class to achieve the texture is: glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8UI, iWidth, iHeight, 0, GL_RGBA_INTEGER,GL_UNSIGNED_BYTE, pImageData). I get a black screen in GL and a gray/white screen in CV. Its kind of hard to check whether your picture works in my program, as I'm having linker problems with SOIL. Thanks again for your patience! – T. Soemod Aug 02 '16 at 16:08
  • @T.Soemod If you cannot display the captured image in GL, there's probably problem with the library (just a guess). What exactly does `cc->CameraCapture::OpenCamera(0)` return (is it really an OpenGL texture ID)? If I assume the library you're using is [CaptureLib](https://github.com/achintsetia/CaptureLib/blob/master/src/cameraCapture.cpp), then you could test if there was a problem manipulating the camera by checking the return value of `cc->CameraCapture::OpenCamera(0)`. You might also want to call `cc->CameraCapture::getStatus()` to see what specifically went wrong. – sarasvati Aug 02 '16 at 17:13
  • The OpenCamera function returns a GLuint ID to a texture, and yes it is a proper (non-empty) texture I'm not using CaptureLib unfortunately, but I'll try some more debugging on the areas you mentioned! I'll update it if I get it to work! Thanks – T. Soemod Aug 02 '16 at 17:51
  • I finally got SOIL to work (it was a piece of work in itself, seeing as I am running on VS15), and i get the Zoidberg output as I should. This in turn leads me to believe that there is something wrong with the texture i receive from the CameraCapture class. In the end this wont help me much, as all I know about the texture i receive is that it is a 2D picture in RGBA. Just thought I'd give you an update. One last thing. Could it be that malloc() clears texture_bytes after setting it? – T. Soemod Aug 03 '16 at 09:31
  • @T.Soemod ...it turns out that the problem might be in the use of direct mode for rendering an `INT` texture format (i.e. the problem isn't int your camera library). You'll need to use fragment shader to properly load and render the texture. I've made [an example for the texture displaying in OpenGL](https://gist.github.com/anonymous/eeaa7f768e0a41467b02b1ab7e36fce7). See [this](http://www.raytrix.de/Rx.ApiLF.4.0/group___rx_n_e_t___a_p_i_l_f___g_l.html) for more details. – sarasvati Aug 03 '16 at 13:53
  • I have now incorporated your shaders + some of the your code into my own and I am pleased to say that I now get an output on the OpenGL side of things. As in, my texture gets displayed in the OpenGL window! :D I am still having issues with the conversion over to Mat though, but this is a big step forward. Thank you again for your patience with me! – T. Soemod Aug 03 '16 at 17:25
  • @T.Soemod you're welcome :). I'm glad that at least something does work. For GL to CV texture conversion: I can only suggest to try to fiddle with [glGetTexImage()](https://www.opengl.org/sdk/docs/man2/xhtml/glGetTexImage.xml)'s `format` and `type` parameters and with [cv::Mat](http://docs.opencv.org/3.1.0/d3/d63/classcv_1_1Mat.html#gsc.tab=0)'s `type` parameter (the third one). It'd be great if you could eventually write about how did you resolve your problem. Good luck! – sarasvati Aug 03 '16 at 18:16
  • That's what I've been trying to fiddle with now, and I guess I just have to find the right combination. One quick question though! For now I've been working with a color texture. Would there be any reason for a black display in OpenGL if the texture was grayscale? – T. Soemod Aug 04 '16 at 08:30
  • @T.Soemod right now I can think only about 2 cases; 1) Assume the texture is uploaded to OpenGL and is all right. If the display is black, the problem might be dividing the fetched texel value (e.g. `/255.0`), or wrong coordinates used (either too large or too small - e.g. `[0;1]` instead of [0;640] and vice versa). 2) Try loading only the first texel value (`.x`), look [here](http://www.raytrix.de/Rx.ApiLF.4.0/group___rx_n_e_t___a_p_i_l_f___g_l.html) (shader for luminance images). – sarasvati Aug 04 '16 at 14:59
  • Thanks for the tips! Its a minor detail, as I can choose whether to use color input or grayscale. I am still having issues with displaying my programs textures in OpenCV though. I have tried to find relevant information on which formats to use for this, but it seems that I am using the right ones and still getting a blank output. For some reason the data in my texture gets cleared before the glGetTexImage() call, which seems to be the reason, but I have no idea why – T. Soemod Aug 05 '16 at 10:59
  • @T.Soemod try checking contents of `texture_bytes` array after `glGetTexImage()` (see if there are just zeros, or actual correct values). You can also try calling `glFinish()` before and after `glGetTexImage()`. – sarasvati Aug 05 '16 at 11:20
  • That's what I did. If I do init_GL -> load texture -> drawImage -> displayGL -> textomat -> displayCV it works fine, and I can see that texture_bytes is set correctly. If I on the other hand do init_GL -> capture texture (from 3rd party app) -> textomat -> displayCV the texture_bytes get loaded to " ", which is blank. The calls to finalize the texture is; glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8UI, iWidth, iHeight, 0, GL_RGBA_INTEGER,GL_UNSIGNED_BYTE, pImageData) in both cases, so they should have been loaded in the same way. Am i right in thinking this? – T. Soemod Aug 05 '16 at 11:34
  • @T.Soemod you wrote `texture_bytes get loaded to " "` (even after `glFlush()`?), what does it mean? What is the actual numerical value of the bytes? Also, is the code for `textureToMat()` in your question still the same? How do you call `glGetTexImage()`? It seems to me like the "format" and/or "type" parameters of `glGetTexImage()` are incorrect. – sarasvati Aug 05 '16 at 13:04
  • Oh, sorry if I was unclear. What I meant was that when I do my glGetTexImage call I follow the status of texture_bytes in VisualStudio, and I can see that it sets it to " " instead of being set correctly. I'd assume this influences the way OpenCV later makes the image. Yes the code is the same except for my calls to glGetTexImage and that I've changed it to work for alpha levels too, i.e added 4 instead of 3 to the malloc and I'm using BGRA. glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA_INTEGER, GL_UNSIGNED_BYTE, texture_bytes); – T. Soemod Aug 05 '16 at 13:17
  • The reason for my use of BGRA_INTEGER is because the texture I am trying to display in OpenCV is made using the call; glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8UI, iWidth, iHeight, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, pImageData); in the 3rd party application. – T. Soemod Aug 05 '16 at 13:21
  • @T.Soemod just a quick question: what happens when you use **exactly** (without **any** modifications) the code in my answer – i.e. `get_ocv_img_from_gl_img()`? – sarasvati Aug 05 '16 at 14:07
  • If I do that the texture dispays just fine in OpenGL, but when i do my call to textureToMat() texture_bytes is set to all "ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ" which displays as a fully gray image in OpenCV. I'd assume this is because the format is wrong, and this is a default value. – T. Soemod Aug 05 '16 at 14:18
  • One thing! As I wrote earlier all test images which were loaded and then run through the program have been displayed correctly in both OpenGL and OpenCV after I implemented your suggestions. I now tried to take a raw test image from the 3rd party application, save it as a .jpg and run it through the program and that DIDNT work, which leads me to think that there is something with the images we get from the app that OpenCV doesnt like. If i take the .jpg image, open it in paint, save it with another name.jpg and then run it through the program it works correctly :S Odd stuff! – T. Soemod Aug 05 '16 at 14:30
  • @T.Soemod try calling [`glGetError()`](https://www.opengl.org/sdk/docs/man/docbook4/xhtml/glGetError.xml) after `glGetTexImage()`. What does it return? – sarasvati Aug 05 '16 at 14:32
  • I get a 1280 "GL_INVALID_ENUM" error if I do that, but I think its a hidden "Unknown error". – T. Soemod Aug 05 '16 at 14:50
  • @T.Soemod does `glGetError()` return the same error when you call it before `glGetTexImage()`? More specifically: can you trace the line where `glGetTexImage()` gives that error first? (Just to be sure it's caused by `glGetTexImage()`) – sarasvati Aug 05 '16 at 14:58
  • I traced glGetError() from the bottom of my function to the top, and it still returns 1280 without a single line being run in the textureToMat function. – T. Soemod Aug 05 '16 at 15:13
  • @T.Soemod So basically the error is present immediately after OpenGL initialization? ...is the error present before and/or after you load the texture via the 3rd party library? – sarasvati Aug 05 '16 at 15:15
  • This is a test image that I have not been able to display using OpenCV: http://img.4k-wallpaper.net/wide_1610/beautiful-winter-sunrise_685.jpeg. If I open it in paint and save it with another name (same filetype) and run it again through the program it displays just fine – T. Soemod Aug 05 '16 at 15:22
  • @T.Soemod I've just tried to open that image in the last version of my code that I uploaded to github (the version with shaders) and the image gets displayed without any errors – both in GL and CL. ...do you use SOIL to open the image? – sarasvati Aug 05 '16 at 15:29
  • The error persists up until the point where i call init_GL, It is even there when I correctly output "Zoidberg" after running the program. I dont really know what to make of that. – T. Soemod Aug 05 '16 at 15:32
  • Thats very weird! Yes I use SOIL. If i try to load that image in my program I get a memory exception! – T. Soemod Aug 05 '16 at 15:33
  • @T.Soemod exception from the SOIL-loading-function or from `glGetTexImage()`? Well, I have to say that I really don't know what could be wrong. (Maybe something with data padding causing the exception – but this is just guess.) – sarasvati Aug 05 '16 at 15:35
  • It appears that it originates in the SOIL- loading function, so I guess we can safely assume that there is some error there. But this shouldn't affect the way a texture is displayed from my 3rd party application in OpenCV, as rendering and displaying a texture in OpenGL doesnt change the texture, or does it? – T. Soemod Aug 05 '16 at 15:41
  • @T.Soemod AFAIK rendering a texture in OpenGL doesn't change the texture. One more question: do you have rendering loop in your code? – sarasvati Aug 05 '16 at 15:49
  • Thats what I thought. Hmm maybe SOIL doesn't like .jpg(progressive) or something? No, not at the moment. My goal was just to display the one image first, and then work on continuous frames (a stream). Also, it couldn't have something to do with the pack alignment? Or would that just distort the image? – T. Soemod Aug 05 '16 at 15:51
  • @T.Soemod I've tried loading progressive and non-progressive version of an image. OpenGL loads and renders both images well, but OpenCV wouldn't display the progressive one (`error: (-215) size.width>0 && size.height>0 in function imshow`). – sarasvati Aug 05 '16 at 16:05
  • Aha! So it is a problem with the formatting then! Well, I don't know how to format it so it would work hehe – T. Soemod Aug 05 '16 at 16:08
  • @T.Soemod have you resolved your problem yet? I've found that the problem was actually in the SOIL library – it cannot load progressive JPEG and when loading such an image it returns height and width `0`, thus no memory is allocated, so instead I used [stb_image](https://github.com/nothings/stb/blob/master/stb_image.h). It's very easy to use: `#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h"` (**in that order!!**). After that just replace the `SOIL` function with: `unsigned char* zoidata_data = stbi_load(image_location, &t_width, &t_height, &t_channels, STBI_rgb_alpha);`. That's all! – sarasvati Aug 05 '16 at 22:18
  • Well, sort of?. I've come to the same conclusion as you regarding the SOIL library, and I've made it work using STB instead as you suggested. The problem of getting textures from my 3rd party program that getTexImage() can read still remains though. I dont know how my 3rd party program arranges the bytes before giving me the texture, so I dont really know how to solve that. – T. Soemod Aug 08 '16 at 07:17
  • @T.Soemod I'd actually refer you to @EmilyL.'s answer - try to utilitze the `GL_PACK_ALIGNMENT` bytes alignment. – sarasvati Aug 08 '16 at 08:34
  • I've played around with GL_UNPACK/PACK_ALIGNMENT aswell, but I guess I'll give it another go :) I'll post a comment if there are any breakthroughs – T. Soemod Aug 08 '16 at 09:31
  • 1
    Thought I'd just post and tell you that the issue is resolved. In the end I had to resort to writing the data to a framebuffer and then glReadPixels() it out from there. Appearently glGetTexImage has as valid behaviour to output blank data when it tries to read a format that it doesnt understand, and there is no known workaround. Pretty stupid imho, but I guess thats how it is. Thanks for all your patience and help! – T. Soemod Aug 09 '16 at 07:29
0
  1. First you need to bind the texture you want to retrieve and then get its dimensions.
  2. Figure out a contiguous memory region to put the data into that matches OpenGL's requirements.
  3. Finally use glGetTexImage to obtain the pixels.

Pseudocode:

GLint width,height,alignment;
glBindTexture(GL_TEXTURE_2D, textureID);
glGetIntegerv(GL_PACK_ALIGNMENT, &alignment);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
// Fiddle with alignment to make sure you get properly aligned buffer/width.
byte* imagedata = new byte[width*height*3];
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, imagedata);
// move data to cv::Mat or use cv::Mat to allocate to original image buffer (imagedata)
// but be mindful that the memory region in cv::Mat is contiguous and the right
// size and all.

https://www.opengl.org/sdk/docs/man2/xhtml/glGetTexImage.xml

https://www.opengl.org/sdk/docs/man2/xhtml/glGet.xml

https://www.opengl.org/sdk/docs/man2/xhtml/glGetTexLevelParameter.xml

genpfault
  • 51,148
  • 11
  • 85
  • 139
Emily L.
  • 5,673
  • 2
  • 40
  • 60
  • Thank you very much! This along with @sarasvati's answer made it work :) – T. Soemod Jul 26 '16 at 13:22
  • I've come a long way on this with some help from @sarasvati, but I'm having some issues with displaying my output from glGetTexImage() in OpenCV, i.e the function returns with the data blank. I have reason to believe that this is because of some alignment issue with the texture im getting from glTexImage2D(), and I've tried to set pack/unpack alignment to correct this, but with no success. Do you have any pointers? My calls areglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, t_width, t_height, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, data); along with the rest of the code above, – T. Soemod Aug 08 '16 at 10:47