Rendering a OpenCV IplImage to a QGLWidget - qt

I am having trouble with rendering a OpenGL scene.
The background is I want to display the frames from a video capture device in a preview window. I am using OpenCV and Qt. And to test I am capturing from my MacBook webcam. The preview window is 200x200 and the frame captured is 640x480. I am not worried about maintaining aspect ratios.
Other info from the IplImage struct:
Debug: channels: 3
Debug: depth: 8
Debug: dataOrder: 0
Debug: align: 4. Alignment of image rows 4 or 8
Debug: origin: 0. 0=top left, 1=bottom left
Debug: widthStep: 2560.
Debug: colorModel: RGB
So this image shows the current state of affairs.
Current capture http://clinsoftsolutions.com/fgvc4.png
I started with using glDrawPixels, but this didn't work well. I got output, but no scaling.
Currently I am trying with textures and here is the code I am using for the GL interactions
void VideoCaptureWidget::initializeGL()
{
qDebug("initializeGL called");
qglClearColor(QColor::fromRgb(0,0,0)); // set clear colur to black
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, this->width(), this->height(), 0.0f, 0.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable(GL_TEXTURE_2D);
glGenTextures(3, &m_texture);
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glBindTexture(GL_TEXTURE_2D,m_texture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,this->width(),this->height(),0,GL_BGR,GL_UNSIGNED_BYTE,NULL);
glDisable(GL_TEXTURE_2D);
}
void VideoCaptureWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f,this->width(),this->height(),0.0f,0.0f,1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,m_texture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,m_image->width,m_image->height,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,m_image->imageData);
glBegin(GL_QUADS);
glTexCoord2i(0,1); glVertex2i(0,this->height());
glTexCoord2i(0,0); glVertex2i(0,0);
glTexCoord2i(1,0); glVertex2i(this->width(),0);
glTexCoord2i(1,1); glVertex2i(this->width(),this->height());
glEnd();
glFlush();
}
void VideoCaptureWidget::resizeGL(int width,int height)
{
qDebug("reszieGL called");
glViewport(0,0,this->width(),this->height());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f,this->width(),this->height(),0.0f,0.0f,1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
The image is captured in a timer slot and m_image is a _IplImage pointer
void VideoCaptureWidget::_captureFrame() {
m_image = cvQueryFrame(m_capture);
if(!m_image) {
qDebug("VideoCaptureWidget::_captureFrame(): Error capturing a frame...");
}
//Draw the scene
glDraw();
}
I am really hoping someone will have seen this sort of distorted image before and know what the problem is.

Another approach is to convert the BGR frames to RGBA before uploading them to the GPU with glTexImage2D(). I uploaded a complete demo in my repository, check cvQTcameraGL.
Here's the relevant code:
// Note: trying to retrieve more frames than the camera can give you
// will make the output video blink a lot.
cv_capture >> cv_frame;
if (cv_frame.empty())
{
std::cout << "GLWidget::paintGL: !!! Failed to retrieve frame" << std::endl;
return;
}
cv::cvtColor(cv_frame, cv_frame, CV_BGR2RGBA);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
// Typical texture generation using data from the bitmap
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _texture);
// Transfer image data to the GPU
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
GL_RGBA, cv_frame.cols, cv_frame.rows, 0,
GL_RGBA, GL_UNSIGNED_BYTE, cv_frame.data);
if (glGetError() != GL_NO_ERROR)
{
std::cout << "GLWidget::paintGL: !!! Failed glTexImage2D" << std::endl;
}

A bit more thinking and looking at the image I figured it must be a byte / channel alignment problem. So a bit more googling pointed mt in the direction of
glPixelStorei with GL_UNPACK_ALIGNMENT and then also GL_UNPACK_ROW_LENGTH
The final code that now works is:
glPixelStorei(GL_UNPACK_ALIGNMENT, 4 );
glPixelStorei(GL_UNPACK_ROW_LENGTH, m_image->widthStep/m_image->nChannels);
This needs to be added above the glTexImage2D call.
It's a weird format for the image data. But that is the output you get from the Apple iSight built in webcam. Hope this helps someone else.

Related

QOpenGLFrameBufferObject for 3D graphics

I am currently working on creating openGL buffers in Qt for some 3D models (cuboids of various sizes depicting buildings).
I've tried to look into some examples, but I've only found some for 2D. I tried using the same for 3D, but at best I end up with blank images when I convert my buffers to images to check.
I reached the max success (at least an image is being created) using: https://dangelog.wordpress.com/2013/02/10/using-fbos-instead-of-pbuffers-in-qt-5-2/
My current code is something like:
QOpenGLFramebufferObject* SBuildingEditorUtils::getFboForBuilding(Building bd)
{
glPushMatrix();
QSurfaceFormat format;
format.setMajorVersion(4);
format.setMinorVersion(3);
QWindow window;
window.setSurfaceType(QWindow::OpenGLSurface);
window.setFormat(format);
window.create();
QOpenGLContext context;
context.setFormat(format);
if (!context.create())
qFatal("Cannot create the requested OpenGL context!");
context.makeCurrent(&window);
// TODO: fbo size = building size
QOpenGLFramebufferObjectFormat fboFormat;
fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
//fboFormat.setAttachment(QOpenGLFramebufferObject::Depth);
auto fbo = new QOpenGLFramebufferObject(1500, 1500, fboFormat);
auto res = glGetError();
auto bindRet = fbo->bind();
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0, 1500, 0, 1500, 0, 1500);
glMatrixMode(GL_MODELVIEW);
glBegin(GL_POLYGON);
glVertex3d(500, 500, 500);
glVertex3d(500, 1000, 1000);
glVertex3d(1000, 1000, 1000);
glVertex3d(1000, 500, 500);
glEnd();
glDisable(GL_DEPTH_TEST);
fbo->toImage().save("uniqueName.png");
fbo->release();
glPopMatrix();
return fbo;
}
Here I've been using the image "uniqueName.png" to text my output. As you can guess most of the code here just for testing. Also, this code is part of a larger code base.
Any advice of what I might be missing. While I have some experience with Qt, I lack any formal education / training in openGL. Any help would be appreciated.
I wish to know how to get the code to work.
Thanks.

glReadPixels GL_DEPTH_COMPONENT does not work in mousePressEvent

I am using QT QOpenGLWidget, I want to unproject my mouse click position back into 3D, so I used glReadPixels. (I also read about the source code of Pangolin, a very good rotation, translation, zoom example, it uses glReadPixels as well)
Here's part of my simple code:
void myGLWidget::initializeGL()
{
glClearColor(0.2, 0.2, 0.2, 1.0); //background color
glClearDepthf(1.0); //set depth test
glEnable(GL_DEPTH_TEST); //enable depth test
}
void myGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //clear color and depth buffer
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(cameraView_.data()); // cameraView_ is a QMatrix4x4
drawingTeapot();
// reading pixels in paintGL works well!!! returns lots of 1s
GLfloat zs[10 * 10];
glReadPixels(0, 0, 10, 10, GL_DEPTH_COMPONENT, GL_FLOAT, &zs);
}
void myGLWidget::mousePressEvent(QMouseEvent *event)
{
// glReadBuffer(GL_FRONT); // also tried this, nothing works
GLfloat zs[10 * 10];
glReadPixels(0, 0, 10, 10, GL_DEPTH_COMPONENT, GL_FLOAT, &zs);
GLenum e = glGetError(); // this gives 1282 err code!!!
}
I'm using macOS Sierra, Pangolin works perfectly on my laptop, however, my qt project does work??!!
By saying not working, I mean the output variable zs remains random values like 0 and 123123e-315 and it never change before and after glReadPixels.
Why glReadPixels works only in PaintGL function??
I also tried python version, it gives my an error says:
File "errorchecker.pyx", line 53, in OpenGL_accelerate.errorchecker._ErrorChecker.glCheckError (src/errorchecker.c:1218)
OpenGL.error.GLError: GLError(
err = 1282,
description = b'invalid operation',
baseOperation = glReadPixels,
which might be the case that:
GL_INVALID_OPERATION is generated if format is GL_DEPTH_COMPONENT and there is no depth buffer. reference from document
But I still don't know what to do
OpenGL operations should be performed only when an OpenGL context is active. This is true in the paintGL() method because this is probably set by the framework for you. You can't assume the OpenGL is active in other methods, like in other event responding methods and callbacks as mousePressEvent(), because those methods can also be run by a different thread where the OpenGL context is not active.

OpenGL depth buffer using QOpenGLFramebufferObject not working

I have a problem where the depth of an OpenGL scene is not rendered correctly. Im doing off-screen rendering into a QOpenGLFramebufferObject. If I run the same code in a QGLWidget it renders fine. Here is the code:
// SETUP
SurfaceFormat format;
QWindow window;
window.setSurfaceType(QWindow::OpenGLSurface);
window.setFormat(format);
window.create();
QOpenGLContext context;
context.setFormat(format);
if (!context.create()) {
qFatal("Cannot create the requested OpenGL context!");
}
QOpenGLFramebufferObjectFormat fboFormat;
fboFormat.setAttachment(QOpenGLFramebufferObject::Depth);
QSize drawRectSize(640, 480);
context.makeCurrent(&window);
QOpenGLFramebufferObject fbo(drawRectSize, fboFormat);
fbo.bind();
// OPENGL CODE
glViewport(0, 0, 640, 480);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glShadeModel(GL_FLAT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLoadMatrixf(pose->getOpenGLModelView());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
/* calculate prjection parameters */
gluPerspective(fov, aspect, 0.01, 1.0f);
glColor3d(1, 0, 0);
glBegin(GL_TRIANGLES);
/* Draw triangles */
glEnd();
glFlush();
QImage result = fbo.toImage());
Any ideas what im doing wrong? I checked GL_DEPTH_BITS and it seems to be 0. Thanks in advance :)
In your code, you never request a depth attachment for your FBO. All you seem to do is
QOpenGLFramebufferObjectFormat fboFormat;
QOpenGLFramebufferObject fbo(drawRectSize, fboFormat);
According to the Qt docs, the QOpenGLFramebufferObjectFormat constructur will just do the following:
By default the format specifies a non-multisample framebuffer object
with no attachments, texture target GL_TEXTURE_2D, and internal format
GL_RGBA8. On OpenGL/ES systems, the default internal format is
GL_RGBA.
Have a look at the Qt Documentation on how to set the correct format.

OpenGL + QT: render to texture and display it back

After some trouble I've managed to correctly render to texture inside a Frame Buffer Object in a Qt 4.8 application: I can open an OpenGL context with a QGLWidget, render to a FBO, and use this one as a texture.
Now I need to display the texture rendered in a QPixmap and show it in some other widget in the gui. But.. nothing is shown.
Those are some pieces of code:
// generate texture, FBO, RBO in the initializeGL
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glGenFramebuffers(1, &fboId);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glGenRenderbuffers(1, &rboId);
glBindRenderbuffer(GL_RENDERBUFFER, rboId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// now in paintGL
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
// .... render into texture code ....
if(showTextureInWidget==false) {
showTextureInWidget = true;
char *pixels;
pixels = new char[TEXTURE_WIDTH * TEXTURE_HEIGHT * 4];
glReadPixels(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pixels);
QPixmap qp = QPixmap(pixels);
QLabel *l = new QLabel();
// /* TEST */ l->setText(QString::fromStdString("dudee"));
l->setPixmap(qp);
QWidget *d = new QWidget;
l->setParent(d);
d->show();
}
glBindFramebuffer(GL_FRAMEBUFFER, 0); // unbind
// now draw the scene with the rendered texture
I see the Widget opened but.. there is nothing inside it. If I decomment the test line.. I see the "dudee" string so I know that there is a qlabel but.. no image from the QPixmap.
I know that the original data are ´unsigned char´ and I'm using ´char´ and I've tried with some different color parameters (´GL_RGBA´, ´GL_RGB´ etc) but I don't think this is the point.. the point is that I don't see anything..
Any advice? If I have to post more code I will do it!
Edit:
I haven't posted all the code, but the fact I'd like to be clear is that the texture is correctly rendered as a texture inside a cube. I'm just not able to put it back in the cpu from gpu
Edit 2:
Thanks to the peppe answer I found out the problem: I needed a Qt object that accept as a constructor some raw pixels data. Here is the complete snippet:
uchar *pixels;
pixels = new uchar[TEXTURE_WIDTH * TEXTURE_HEIGHT * 4];
for(int i=0; i < (TEXTURE_WIDTH * TEXTURE_HEIGHT * 4) ; i++ ) {
pixels[i] = 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glReadPixels( 0,0, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
qi = QImage(pixels, TEXTURE_WIDTH, TEXTURE_HEIGHT, QImage::Format_ARGB32);
qi = qi.rgbSwapped();
QLabel *l = new QLabel();
l->setPixmap(QPixmap::fromImage(qi));
QWidget *d = new QWidget;
l->setParent(d);
d->show();
Given that that's not all of your code and -- as you say -- the texture is correctly filled, then there's a little mistake going on here:
glReadPixels(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pixels);
QPixmap qp = QPixmap(pixels);
The QPixmap(const char *) ctor wants a XPM image, not raw pixels. You need to use one of the QImage ctors to create a valid QImage. (You can also pass ownership to the QImage, solving the fact that you're currently leaking pixels...)
Once you do that, you'll figure out that
the image is flipped vertically, as OpenGL has the origin in the bottom left corner, growing upwards/rightwards, while Qt assumes origin in the top left, growing to downwards/rightwards;
the channels might be swapped -- i.e. OpenGL is returning data with the wrong endianess. I don't remember in this case if using glPixelStorei(GL_PACK_SWAP_BYTES) or GL_UNSIGNED_INT_8_8_8_8 as the type may help, eventually you need to resort to a CPU-side loop to fix your pixel data :)

glDeleteTextures, leaking?

I found a rather disguting behaviour of glDeleteTexture, deleteing only parts of the aqcuired memory (GPU side and as Textures get saved back for the sake of speed in RAM), which in my case, is a showstopper bug, my program eating up all memory.
I don't want/require you to read all of the code, it's just a demo, I'd rather know how to actually use glDeleteTextures so it does not leak any memory.
The example code requires Qt 4.5 or later to compile:
glleak.pro
QT += opengl
SOURCES += main.cpp \
glleak.cpp
HEADERS += glleak.h
main.cpp
#include <QtOpenGL>
#include <QtGui>
#include "glleak.h"
int main(int argc, char** argv){
QApplication app(argc, argv);
glleak gll(0);
gll.show();
return app.exec();
}
glleak.h
#ifndef GLLEAK_H
#define GLLEAK_H
#include <QGLWidget>
#include <QMouseEvent>
#include <QDebug>
#include <QList>
class glleak : public QGLWidget
{
Q_OBJECT
public:
glleak(QWidget* parent = 0);
virtual ~glleak();
protected:
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
void drawScene(GLenum mode);
void wheelEvent(QWheelEvent* event);
void hardcoreTexturing();
private:
QList<GLuint> texels;
};
#endif // GLLEAK_H
glleak.cpp
glleak::glleak(QWidget* parent) :
QGLWidget(parent)
{
}
glleak::~glleak()
{
}
void glleak::initializeGL(){
glClearColor(0.0f,0.0f,0.0f,0.0f);
glEnable(GL_TEXTURE_2D);
glEnable(GL_MULTISAMPLE);
glLineWidth (1.5f);
glPointSize(4.5f);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void glleak::resizeGL(int w, int h){
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-w/2.0, w/2.0, h/2.0, -h/2.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glViewport(0, 0, w, h);
glLoadIdentity();
}
void glleak::paintGL(){
glPushMatrix();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glColor3f(1.0f,1.0f,1.0f);
drawScene(GL_RENDER);
glPopMatrix();
}
void glleak::drawScene(GLenum mode){
qDebug() << "drawed #" << texels.count() << " Textures";
hardcoreTexturing();
}
void glleak::hardcoreTexturing(){
glEnable(GL_TEXTURE_2D);
for ( int i(0); i<texels.count(); ++i){
glPushMatrix();
glTranslatef(1.1f*i, 2.2f*i, 0.0f);
glBindTexture(GL_TEXTURE_2D, texels.at(i));
glBegin(GL_QUADS);
{
glTexCoord2i(0,0);
glVertex2i(-128,-128);
glTexCoord2i(0,1);
glVertex2i(-128,128);
glTexCoord2i(1,1);
glVertex2i(128,128);
glTexCoord2i(1,0);
glVertex2i(128,-128);
}
glEnd();
glPopMatrix();
}
glDisable(GL_TEXTURE_2D);
}
void glleak::wheelEvent(QWheelEvent* event){
glEnable(GL_TEXTURE_2D);
int n(50);
if (event->delta()>0){
qDebug() << "gen textures";
for (int i(0); i<n; ++i){
QImage t("./ballmer_peak.png","png");
GLuint tex(0);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D( GL_TEXTURE_2D, 0, 3, t.width(), t.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, t.bits() );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
texels.append(tex);
}
}
else{
qDebug() << "del textures";
for (QList<GLuint>::iterator i(texels.begin()); i!=texels.end();){
glDeleteTextures(1, &(*i));
i = texels.erase(i);
if (--n <= 0)
break;
}
}
glDisable(GL_TEXTURE_2D);
updateGL();
}
ballmer_peak.png
A Image to load and render
Note: Compile demo: Just put it all in a folder, rename your image to ballmer_peak.png, call qmake, make, ./glleak
Note: Demo usage: Use mousewheel to generate or delete 50 Textures at once
If I use glDeleteTextures completly wrong, please tell me how to use it.
I am way out of ideas as my usage complies to the official OpenGL glDeleteTextures usage.
This may or may not be the reason for your leak, but for starters you are using glGenTextures wrongly.
1) You should not put this inside the for loop which initializes the textures. You need to put it before the loop and call it ONCE, with the number of textures required as the first parameter. Say n == 50:
glGenTextures(50, &tex);
2) tex should be a static array of n GLuints and should be persisted (not an auto variable as you have it!) until glDeleteTextures has been called, again, ONCE - not in a loop:
glDeleteTextures(50, &tex);
Think of tex as a repository for storing texture ids. It is important you use it and not say a separate QList, as you have done, for binding textures, since (as specified in the OpenGL reference) there is no guarantee that the texture ids will be a contiguous set of integers. I should imagine your leak happens because internally OpenGL loses the original pointer to the local (auto) variable you used to generate each texture, so the texture memory becomes orphaned.
Hope this helps!
I did not run your example code, but I get a similar thing on Windows7-64bits. Using per-texture glGenTextures() and glDeleteTextures(), it might leak memory, but I'm seeing my thread's handle count increase (in TaskManager for example, but I can also check it from the source).
It seems glDeleteTextures() does not release a handle. Perhaps it'd do it later on, but 24-hour tests indicate it never releases the handle. Seems like a leak inside the driver (nVidia GTX285, driver 270.61).
Eventually indeed the program runs out of memory. I'm beginning to think it's a driver issue...
There's nothing that looks wrong in your code. So... What makes you think you have a memory leak ? What makes you think it's textures specifically that leak ?
It is possible, but highly unlikely, that the OpenGL implementation you use leaks. That would be implementation specific.
Whatever the mechanism you use to look at memory leaks, what happens once you free the OpenGL context ?
You may need to call makeCurrent() at the top of wheelEvent.
For paintEvent, resizeEvent etc, Qt provides an implementation which handles this before calling paintGL/resizeGL/etc, but for other events like wheelEvent you have to do it yourself.
I might be doing this wrong, but when I compiled and ran your code I didn't run into any problems? Going up to 650 textures (can't increase further: get a 'killed' message then) and back my ram usage goes up from 1% to 24% and back to 1%. Going up to about 200 and back down repeatedly also doesn't cause problems: ram usage is still 1% at the end. From what I understand this would've caused massive leaks on your system? Ubuntu 10.10 here (Qt 4.7.0).
Your test on my system eats memory well, and not releases it immediatly when I delete all the textures, but if I wait for some time, memory is returned to system.
It seems that OGL driver uses some lazy memory releasing algorithm.
for (QList<GLuint>::iterator i(texels.begin()); i!=texels.end();)
switch to
for (QList<GLuint>::iterator i(texels.end()); i!=texels.begin();)

Resources