Draw a cv::Mat inside a QGLWidget? - c++

I am wondering how to draw a cv::Mat into a QGLWidget. I´ve found here a method and tried to implement it. But My widget is black.
This is the code of my widget:
h file
#ifndef GLVIEWER_H
#define GLVIEWER_H
#include <QtOpenGL/QGLWidget>
#include <QtOpenGL/QtOpenGL>
#include <opencv2/core/core.hpp>
class GLViewer : public QGLWidget {
Q_OBJECT
public:
GLViewer(QWidget *parent = NULL);
void setNewFrame(cv::Mat newframe);
protected:
void initializeGL();
void resizeGL(int w, int h);
void paintGL();
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
cv::Mat frame;
};
#endif // GLVIEWER_H
cpp file
#include "glviewer.h"
#include <iostream>
#include <QtGui/QMouseEvent>
GLViewer::GLViewer(QWidget *parent) : QGLWidget(parent) {
setMouseTracking(true);
frame = cv::Mat::ones(25, 50, CV_8UC3) * 255;
}
void GLViewer::setNewFrame(cv::Mat newframe) {
frame = newframe;
paintGL();
}
void GLViewer::initializeGL() {
std::cout << "initializeGL" << std::endl;
glViewport(0,0,this->width(), this->height());
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-this->width()/2, this->width()/2, this->height()/2, -this->height()/2, -1, 1);
}
void GLViewer::resizeGL(int w, int h) {
glViewport(0, 0, w, h);
// glMatrixMode(GL_PROJECTION);
// glLoadIdentity();
// gluOrtho2D(0, w, 0, h); // set origin to bottom left corner
// glMatrixMode(GL_MODELVIEW);
// glLoadIdentity();
}
void GLViewer::paintGL() {
//std::cout << "paint" << std::endl;
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1.0f, 0, 0, 1.0f);
glDrawPixels(frame.rows, frame.cols,
GL_RGB,
GL_UNSIGNED_BYTE,
frame.data);
}
As I have initialized the cv::Mat with ´cv::Mat::ones(25, 50, CV_8UC3) * 255;´ I expect to see a little blue rect but.. nothing is happening, full black widget.
Anyone can point me out to a solution?
EDIT
I have tried to change the background to red and now things are red.. this is the output:
the background is correct but the blue rect.. is not blue even if it seems to a rectangle.. what is happening? i have tried to invert frame.cols and frame.cols, and GL_BGR instead of GL_RGB and some random trials with data types (GL_UNSIGNED_SHORT etc) but nothing is getting a better result..

glDrawPixels is not a wise choice. 2D pixel drawing is amazingly slow in OpenGL. What you really want to do is load the matrix into a texture, then draw a primitive with the texture on it.
However for your simple use case:
I would recommend you use QGLWidget, but still QPainter to draw stuff. It will be fully accelerated, but you don't have to deal with all this transformation stuff etc.!
The code is rather simple. Have a look at:
https://sourceforge.net/p/gerbil/svn/HEAD/tree/trunk/gerbil-gui/scaledview.cpp
(an example of a widget that automatically scales the content)
To convert from Mat to QImage, see:
https://sourceforge.net/p/gerbil/svn/HEAD/tree/trunk/common/qtopencv.cpp
The conversion QImage <-> QPixmap can be done implicitely by constructing one from the other. Note that it essentially will only store the image data on the graphics card as a texture.
The conversion cv::Mat to QImage is a "redundant" data copy. However it is really negligible, try it!

Your resizeGL() is wiping out the matrix setup you're doing in initializeGL(), the glOrtho() call in particular.
Try moving the matrix setup to paintGL():
...
void GLViewer::initializeGL()
{
std::cout << "initializeGL" << std::endl;
glViewport(0,0,width(), height());
}
void GLViewer::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
}
void GLViewer::paintGL() {
std::cout << "paint" << std::endl;
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-width()/2.0, width()/2.0, height()/2.0, -height()/2.0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRasterPos2i(width()/2.0,-height()/2.0);
// also try glRasterPos2i(0,0)
glPixelZoom(-1.0f,-1.0f);
glDrawPixels
(
frame.rows, frame.cols,
GL_RGB,
GL_UNSIGNED_BYTE,
frame.data
);
}
...
EDIT: Try issuing a glPixelStore( GL_UNPACK_ALIGNMENT, 1 ) before the glDrawPixels() call. Or switch to GL_RGBA and a four-component cv::Mat format.

Related

QT5 with OpenGL and Kinect v2: wrong image presentation

I would like to show a color image from the kinect v2 sensor over openGl in a Qt widget. The problem is, that the presentation of this image is incorrect.
Here is the code where I get the color frame:
class:
cv::Mat ColorMap;
std::vector<BYTE> colorBuffer;
int color_width;
int color_height;
code:
if(colorBuffer.empty())
colorBuffer.resize(color_height * color_width * 4 * sizeof(unsigned char));
hr = ColorFrame->CopyConvertedFrameDataToArray((UINT)colorBuffer.size(),
&colorBuffer[0], ColorImageFormat::ColorImageFormat_Bgra );
ColorMap = cv::Mat( color_height, color_width, CV_8UC4, &colorBuffer[0]);
That means, I get the color information as BGR Format with an alpha channel and copy it to a matrix with 4 channels, (BRGA) and each channel has 8Bit from 0 to 255. Correct?
In the next step, I resize it as the same size as the widget:
Kinect->CreateFrame();
cv::Mat GUIColorImage;
Kinect->ColorMap.copyTo(GUIColorImage);
cv::resize(GUIColorImage,GUIColorImage,
cv::Size(ui->Left_Widget_Color->width(),ui->Left_Widget_Color->height()));
Than I've tried two convert methods:
1. convert to BGR
2. convert to 8UC3 (8-Bit, unsigned char, 3 channels (the same as BGR?))
1: GUIColorImage.convertTo(GUIColorImage,CV_BGRA2BGR);
2: GUIColorImage.convertTo(GUIColorImage,CV_8UC3);
But no solutions works.
After the conversion, I try to display it over openGl with:
LeftWidgetColor.UpdateImage(GUIColorImage);
LeftWidgetColor is a QOpenGLWidget:
header:
class RenderWidget : public QOpenGLWidget
{
Q_OBJECT
public:
RenderWidget(QWidget *parent);
~RenderWidget();
void UpdateImage(cv::Mat newimage);
void initializeGL();
void paintGL();
private:
int width;
int height;
GLuint texture;
cv::Mat image;
signals:
void info(QString msg);
void error(QString msg);
void DepthValueAt(cv::Point2i DepthPosition);
protected:
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
};
code:
RenderWidget::RenderWidget(QWidget *parent) : QOpenGLWidget(parent)
{
width = this->size().width();
height = this->size().height();
texture = 0;
initializeGL();
}
RenderWidget::~RenderWidget()
{
glDeleteTextures(1, &texture);
}
void RenderWidget::initializeGL()
{
//Background Color is black
glClearColor(0,0,0,1);
//Storage of Pixelmode for two-dimensional textures
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
//Create texture
glGenTextures(1, &texture);
glViewport(0, 0, width, height);
}
void RenderWidget::paintGL()
{
// Clear the screen and depth buffer (with black)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Select the model view matrix and reset it
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, 0.0f);
// Abort drawing if OpenCV was unable to open the camera
if (image.empty())
{
return;
}
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,
3, image.cols, image.rows, 0,
GL_BGR, GL_UNSIGNED_BYTE, image.data);
if (glGetError() != GL_NO_ERROR)
{
}
// Draw a 2D face with texture
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex2f(1, 1);
glTexCoord2f(image.cols, 0); glVertex2f(-1, 1);
glTexCoord2f(image.cols, image.rows); glVertex2f(-1, -1);
glTexCoord2f(0, image.rows); glVertex2f(1, -1);
glEnd();
glDisable(GL_TEXTURE_RECTANGLE_ARB);
}
void RenderWidget::UpdateImage(cv::Mat newimage)
{
newimage.copyTo(image);
update();
}
I guess the problem is in:
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
3, image.cols, image.rows, 0,
GL_BGR, GL_UNSIGNED_BYTE, image.data);
But I can't find it. I've declared 3-channels, BGR, 8-Bit = 1 Byte as unsigned. Does someone know where the mistake is?
If i show it over imshow (openCv class), it works fine.
My Mistake was, that I used cv::Mat::convertTo(): but here
they wrote, that convertTo() should not used to convert the format (Only to change the data type). cv::cvtColor is the right function and it works well for me. So I've changed the conversion from:
GUIColorImage.convertTo(GUIColorImage,CV_BGRA2BGR);
to
cv::cvtColor(GUIColorImage,GUIColorImage,CV_BGRA2BGR);

QPixmap runs over my glScissor(...) setting

I apologize if this isn't exact. I'm doing the best I can to copy code by hand from one computer to another, and the destination computer doesn't have a compiler (don't ask).
Header file
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <qopenglwidget.h>
class MyOpenGlWidget : public QOpenGLWidget
{
Q_OBJECT
public:
explicit MyOpenGlWidget(QWidget *parent = 0, Qt::WindowFlags f = Qt::WindowFlags());
virtual ~MyOpenGlWidget();
protected:
// these are supposed to be overridden, so use the "override" keyword to compiler check yourself
virtual void initializeGL() override;
virtual void resizeGL(int w, int h) override;
virtual void paintGL() override;
private:
QPixmap *_foregroundPixmap;
}
#endif
Source file
QOpenGLFunctions_2_1 *f = 0;
MyOpenGlWidget::MyOpenGlWidget(QWidget *parent, Qt::WindowFlags f) :
QOpenGLWidget(parent, f)
{
_foregroundPixmap = 0;
QPixmap *p = new QPixmap("beveled_texture.tiff");
if (!p->isNull())
{
_foregroundPixmap = p;
}
}
MyOpenGlWidget::~MyOpenGlWidget()
{
delete _foregroundPixmap;
}
void MyOpenGlWidget::initializeGL()
{
// getting a deprecated set of functions because such is my work environment
// Note: Also, QOpenGLWidget doesn't support these natively.
f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_1>();
f->glClearColor(0.0f, 1.0f, 0.0f, 1.0f); // clearing to green
f->glEnable(GL_DEPTH_TEST);
f->glEnable(GL_CULL_FACE); // implicitly culling front face
f->glEnable(GL_SCISSOR_TEST);
// it is either copy the matrix and viewport code from resizeGL or just call the method
this->resizeGL(this->width(), this->height());
}
void MyOpenGlWidget::resizeGL(int w, int h)
{
// make the viewport square
int sideLen = qMin(w, h);
int x = (w - side) / 2;
int y = (h - side) / 2;
// the widget is 400x400, so this random demonstration square will show up inside it
f->glViewport(50, 50, 100, 100);
f->glMatrixMode(GL_PROJECTION);
f->glLoadIdentity();
f->glOrtho(-2.0f, +2.0f, -2.0f, +2.0f, 1.0f, 15.0f); // magic numbers left over from a demo
f->glMatrixMode(GL_MODELVIEW);
// queue up a paint event
// Note: QGLWidget used updateGL(), but QOpenGLWidget uses update().
this->update();
}
void MyOpenGlWidget::paintGL()
{
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// I want to draw a texture with beveled edges the size of this widget, so I can't
// have the background clearing all the way to the edges
f->glScissor(50, 50, 200, 200); // more magic numbers just for demonstration
// clears to green in just scissored area (unless QPainter is created)
f->glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
// loading identity matrix, doing f->glTranslatef(...) and f->glRotatef(...)
// pixmap loaded earlier in another function
if (_foregroundPixmap != 0)
{
// QPixmap apparently draws such that culling the back face will cull the entire
// pixmap, so have to switch culling for duration of pixmap drawing
f->glCullFace(GL_FRONT);
QPainter(this);
painter.drawPixmap(0, 0, _foregroundPixmap->scaled(this->size()));
// done, so switch back to culling the front face
f->glCullFace(GL_BACK);
}
QOpenGLFunctions_2_1 *f = 0;
void MyOpenGlWidget::initializeGL()
{
// getting a deprecated set of functions because such is my work environment
// Note: Also, QOpenGLWidget doesn't support these natively.
f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_1>();
f->glClearColor(0.0f, 1.0f, 0.0f, 1.0f); // clearing to green
f->glEnable(GL_DEPTH_TEST);
f->glEnable(GL_CULL_FACE); // implicitly culling front face
f->glEnable(GL_SCISSOR_TEST);
// it is either copy the matrix and viewport code from resizeGL or just call it directly
this->resizeGL(this->width(), this->height());
}
void MyOpenGlWidget::resizeGL(int w, int h)
{
// make the viewport square
int sideLen = qMin(w, h);
int x = (w - side) / 2;
int y = (h - side) / 2;
// the widget is 400x400, so this random demonstration square will show up inside it
f->glViewport(50, 50, 100, 100);
f->glMatrixMode(GL_PROJECTION);
f->glLoadIdentity();
f->glOrtho(-2.0f, +2.0f, -2.0f, +2.0f, 1.0f, 15.0f); // magic numbers left over from a demo
f->glMatrixMode(GL_MODELVIEW);
// queue up a paint event
// Note: QGLWidget used updateGL(), but QOpenGLWidget uses update().
this->update();
}
void MyOpenGlWidget::paintGL()
{
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// I want to draw a texture with beveled edges the size of this widget, so I can't
// have the background clearing all the way to the edges
f->glScissor(50, 50, 200, 200); // more magic numbers just for demonstration
// clears to green in just scissored area (unless QPainter is created)
f->glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
// loading identity matrix, doing f->glTranslatef(...) and f->glRotatef(...), drawing triangles
// done drawing, so now draw the beveled foreground
if (_foregroundPixmap != 0)
{
// QPixmap apparently draws such that culling the back face will cull the entire
// pixmap, so have to switch culling for duration of pixmap drawing
f->glCullFace(GL_FRONT);
QPainter(this);
painter.drawPixmap(0, 0, _foregroundPixmap->scaled(this->size()));
// done, so switch back to culling the front face
f->glCullFace(GL_BACK);
}
}
The problem is this code from paintGL():
QPainter(this);
As soon as a QPainter object is created, the glScissor(...) call that I made earlier in the function is overrun and some kind of glClearColor(...) call is made (possibly from QPainter's constructor) that clears the entire viewport to the background color that I set just after glScissor(...). Then the pixmap draws my beveled texture just fine.
I don't want QPainter to overrun my scissoring.
The closest I got to an explanation was two QPainter methods, beginNativePainting() and endNativePainting(). According to the documentation, scissor testing is disabled between these two, but in their example they re-enable it. I tried using this "native painting" code, but I couldn't stop QPainter's mere existence from ignoring GL's scissoring and clearing my entire viewport.
Why is this happening and how do I stop this?
Note: This work computer has network policies to prevent me from going to entertainment sites like imgur to upload "what I want" and "what I get" pictures, so I have to make due with text.
Why is this happening
The OpenGL context is a shared resource and you have to share it with other players.
and how do I stop this?
You can't. Just do the proper thing and set viewport, scissor rectangle and all the other drawing related state at the right moment: Right before you are going to draw something that relies on these settings. Don't set them aeons (in computer terms) before, somewhere in some "initialization" or a reshape handler. And be expected that in drawing code any function you call that makes use of OpenGL will leave some garbage behind.

How to get default FBO between QPainter native painting?

I want to get the default FBO between QPainter's native painting area. such as:
void QGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{
painter->beginNativePainting();
unsign int defaultFBO = getDefaultFBO(); // how ?
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO );
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO );
painter->endNativePainting();
}
Can anybody help me with this ?

OpenGL sine wave amplitude and frequency change

I wrote an OpenGL program to draw a sine wave:
#include <QtGui/QtGui>
#include <QtOpenGL/QtOpenGL>
#include <math.h>
#include "globj2.h"
#include <iostream>
using namespace std;
GLobj2::GLobj2(QWidget *parent)
: QGLWidget(parent)
{
}
GLobj2::~GLobj2()
{
}
//Initialize the GL settings
void GLobj2::initializeGL()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glPointSize(2.0);
glLineWidth(1.0);
}
//Set up the viewport based on the screen dimentions
//Function is called implicitly by initializeGL and when screen is resized
void GLobj2::resizeGL( int w, int h )
{
//algorithm to keep scene "square" (preserve aspect ratio)
//even if screen is streached
if(w>h)
glViewport((w-h)/2, 0, h, h);
else
glViewport(0, (h-w)/2, w, w);
//setup the projection and switch to model view for transformations
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1, 1, -1, 1, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//implicit call to paintGL after resized
}
//Paints the GL scene
void GLobj2::paintGL()
{
glClear (GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);
glBegin(GL_LINE_STRIP);
double amp=1;
double freq = 4;
double t=0.0;
for (double x=-1.0; x<=1; x+=0.001)
{
double y = amp * cos(2 * 3.14 * freq * x );
glVertex2f(x, y);
t+=10.0;
}
glEnd();
glFlush ();
}
I have frequency and volume sliders defined as below:
QSlider *volume = new QSlider(Qt::Horizontal);
QLCDNumber *lcd1 = new QLCDNumber;
connect(volume,SIGNAL(valueChanged(int)),lcd1,SLOT(display(int)));
QLabel *label1 = new QLabel;
label1->setText("Volume:");
QDial *frequency = new QDial;
QLCDNumber *lcd2 = new QLCDNumber;
connect(frequency,SIGNAL(valueChanged(int)),lcd2,SLOT(display(int)));
QLabel *label2 = new QLabel;
label2->setText("Frequency:");
Now I want to connect frequency and volume signals from slider to sine wave freq and volume. Can you please help me figure out how to do this connection?

QGLWidget: overpainting with QPainter

I would like to draw some overlay on my image viewer : a dashed rectangle around the image, indicating the bounding box.
Here 's what I do in the paintEvent function:
void ViewerGL::paintEvent(QPaintEvent* event){
makeCurrent();
QPainter p(this);
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT); // << If I don't do this it clears out my viewer white
// and I want it black
p.setBackgroundMode(Qt::TransparentMode); // < was a test, doesn't seem to do anything
p.setBackground(QColor(0,0,0,0));
//loading the appropriate shader before texture mapping
if(rgbMode() && _shaderLoaded){
shaderRGB->bind();
}else if(!rgbMode() && _shaderLoaded){
shaderLC->bind();
}
paintGL(); // render function (texture mapping)
//drawing a rect around the texture on the viewer
QPen pen(Qt::DashLine);
pen.setColor(QColor(233,233,233));
p.setPen(pen);
QPoint btmRight=mousePosFromOpenGL(dataWindow().w(),dataWindow().h() +transY*2 + ( zoomY-dataWindow().h()/2)*2);
QPoint btmLeft=mousePosFromOpenGL(0,dataWindow().h() +transY*2 + ( zoomY-dataWindow().h()/2)*2);
QPoint topLeft=mousePosFromOpenGL(0,0 +transY*2 + ( zoomY-dataWindow().h()/2)*2);
QPoint topRight=mousePosFromOpenGL(dataWindow().w(), 0+ +transY*2 + ( zoomY-dataWindow().h()/2)*2);
p.drawLine(topLeft,topRight);
p.drawLine(topRight,btmRight);
p.drawLine(btmRight,btmLeft);
p.drawLine(btmLeft,topLeft);
QPoint pos = mousePosFromOpenGL( (dataWindow().w()) + 10 ,
(dataWindow().h()) +transY*2 + ( zoomY-dataWindow().h()/2)*2 +10); // bottom right of the texture +10
p.drawText(pos, _resolutionOverlay);
p.end();
}
And here is what I do in the paintGL function:
glPushAttrib(GL_ALL_ATTRIB_BITS);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
... my drawing code
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
And the initializeGL function :
initAndCheckGlExtensions();
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glGenTextures (1, texId);
glGenTextures (1, texBlack);
glGenBuffersARB(1, &texBuffer);
shaderBlack=new QGLShaderProgram(context());
shaderBlack=new QGLShaderProgram(context());
if(!shaderBlack->addShaderFromSourceCode(QGLShader::Vertex,vertRGB))
cout << shaderBlack->log().toStdString().c_str() << endl;
if(!shaderBlack->addShaderFromSourceCode(QGLShader::Fragment,blackFrag))
cout << shaderBlack->log().toStdString().c_str() << endl;
if(!shaderBlack->link()){
cout << shaderBlack->log().toStdString().c_str() << endl;
}
So far it works, I have what I want, but my program is flooding stderr on exit with :
'QGLContext::makeCurrent: Cannot make invalid context current'.
I know this is coming from the QPainter and not from something else in my program.
I tried to move the code in the paintGL function to another function that is not virtual but that did not change anything.
You should first perform OpenGL calls, clear your OpenGL state, then wrap ALL QPainter draw calls between QPainter::begin(QGLWidget *) and QPainter::end(QGLWidget *).
Your program is most likely causing a problem because you interleave QPainter and OpenGL drawing calls. When you instantiate the QPainter object with QPainter(QPaintDevice *) you tell the QPainter to use the native drawing facilities of the QGLWidget to perform QPainter operations. So... QPainter uses OpenGL fixed-function calls to perform 2D drawing and it may interfere with your OpenGL rendering calls when your state has not been cleared.
I suggest following this guide:
https://doc.qt.io/archives/qt-4.8/qt-opengl-overpainting-example.html