I am working on an interactive application which needs to read and manipulate several very large images at once (25 images at a time, roughly 350 Mb total size). OpenCV is quite speedy and handles the algorithms with relative ease. But drawing them with Qt is proving to be a problem. Here are two less-than-ideal solutions I have tried.
Solution 1 (too slow)
Every time you need to draw a different OpenCV image, convert it to a
QImage and draw that. The conversion, unfortunately, takes a while and
we cannot switch between images at interactive speeds.
Solution 2 (too memory-intensive)
Maintain two stacks of images, one for OpenCV and one for Qt. Use the
appropriate one at the appropriate time.
I have direct access to the OpenCV pixel data. I know the width and height of the image, and I know that pixels are 3-byte RGB values. It seems like it should be possible to draw the OpenCV image quickly without copying it to a QImage container that (as far as I can tell) just contains a duplicate of the data.
Where do I need to look to get this kind of capability out of Qt?
I don't know if this might be useful to you now after 3 months. But I am having the same kind of application where I have to manipulate a stream of images using OpenCV and display it on a QT interface. After googling around quite a bit, I came across a very slick solution. Use opengl's glDrawPixels to draw raw image data directly on the Qt interface. Best part, u don't have to write any extra conversion code. Just the basic code for opengl for setting up a viewport and coordinate. Check out the code which has a function which takes an IplImage* pointer and uses that data to draw the image. You might need to tweak the parameters(especially the WIDTH and HEIGHT variables) a bit to display an image with a specific size.
And yeah, I don't know what build system you are using. I used cmake and had to setup dependencies for opengl although I am using Qt's opengl libraries.
I have implemented a class QIplImage which derives from QGLWidget and overriden its paintGL method to draw the pixel data on to the frame.
//File qiplimage.h
class QIplImage : public QGLWidget
{
Q_OBJECT
public:
QIplImage(QWidget *parent = 0,char *name=0);
~QIplImage();
void paintGL();
void initializeGL();
void resizeGL(int,int);
bool drawing;
public slots:
void setImage(IplImage);
private:
Ui::QIplImage ui;
IplImage* original;
GLenum format;
GLuint texture;
QColor bgColor;
char* name;
bool hidden;
int startX,startY,endX,endY;
QList<QPointF*> slopes;
QWidget* parent;
int mouseX,mouseY;
};
//End of file qiplimage.h
//file qiplimage.cpp
#include "qiplimage.h"
#include <Globals.h>
QIplImage::QIplImage(QWidget *parent) :
QGLWidget(parent)
{
}
QIplImage::QIplImage(QWidget *parent,char* name): QGLWidget(parent)
{
ui.setupUi(this);
//This is required if you need to transmit IplImage over
// signals and slots.(That's what I am doing in my application
qRegisterMetaType<IplImage>("IplImage");
resize(384,288);
this->name=name;
this->parent=parent;
hidden=false;
bgColor= QColor::fromRgb(0xe0,0xdf,0xe0);
original=cvCreateImage(cvSize(this->width(),this->height()),IPL_DEPTH_8U,3);
cvZero(original);
switch(original->nChannels) {
case 1:
format = GL_LUMINANCE;
break;
case 2:
format = GL_LUMINANCE_ALPHA;
break;
case 3:
format = GL_BGR;
break;
default:
return;
}
drawing=false;
setMouseTracking(true);
mouseX=0;mouseY=0;
initializeGL();
}
void QIplImage::initializeGL()
{
qglClearColor(bgColor);
//glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
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,&texture);
glBindTexture(GL_TEXTURE_2D,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,texture); glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,this->width(),this->height(),0,GL_BGR,GL_UNSIGNED_BYTE,NULL);
glDisable(GL_TEXTURE_2D);
}
void QIplImage::setImage(IplImage image){
original=ℑ
//cvShowImage(name,original);
updateGL();
}
void QIplImage::paintGL (){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
if(!hidden){
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,texture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,original->width,original->height,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,original->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 QIplImage::resizeGL(int width,int height){
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();
}
Hope that helps.
You can share the data between QImage and openCV - both of them have a ctor which uses existing data - supplied by a pointer.
cv::Mat(int _rows, int _cols, int _type, void* _data, size_t _step=AUTO_STEP)
QImage ( uchar * data, int width, int height, int bytesPerLine, Format format)
There might be an issue with the padding if the rows don't end up being multiples of 4bytes but I would expect the padding to align on both types with the same pixel size - at least on the same hardware
One issue is that openCV uses BGR by default which isn't very optimal for QImage (or any other display). Although I'm not sure that QImage::Format_ARGB32_Premultiplied is necessarily that much quicker anymore on Qt which use accelerated openGL for rendering QImage.
An alternative is to use opencv then copy the resulting data direct to an openGL texture and then use QGlWidget to display the image without another copy.
Related
I am trying to develop a working EFIS display written in C++, OpenGL and the X-Plane SDK for an aircraft in X-Plane. I do not have very much experience with C++ and OpenGL. However, I do know X-Plane fairly well and I know how to use the X-Plane data for moving each element. What I do not know is how to code the EFIS display to draw all of the elements in an efficient way. I only know the very basics of drawing an OpenGL GL_QUAD and binding a texture to it however this seems to be a very low-level way of doing things.
What I would like to be able to do is create the GUI for the EFIS in a more efficient way as there are a lot of texture elements that need to be drawn.
This is an example of what I would like to build for X-Plane:
Here is the code I have currently written that loads in 1 image texture and binds it to a GL_QUAD.
static int my_draw_tex(
XPLMDrawingPhase inPhase,
int inIsBefore,
void* inRefcon)
{
// Note: if the tex size is not changing, glTexSubImage2D is faster than glTexImage2D.
// The drawing part.
XPLMSetGraphicsState(
0, // No fog, equivalent to glDisable(GL_FOG);
1, // One texture, equivalent to glEnable(GL_TEXTURE_2D);
0, // No lighting, equivalent to glDisable(GL_LIGHT0);
0, // No alpha testing, e.g glDisable(GL_ALPHA_TEST);
1, // Use alpha blending, e.g. glEnable(GL_BLEND);
0, // No depth read, e.g. glDisable(GL_DEPTH_TEST);
0); // No depth write, e.g. glDepthMask(GL_FALSE);
//---------------------------------------------- HORIZON -----------------------------------------//
glPushMatrix();
// Bind the Texture
XPLMBindTexture2d(texName[HORIZON], 0);
glColor3f(1, 1, 1);
glBegin(GL_QUADS);
// Initial coordinates for the horizon background
int arry[] = { 838, 465, 838, 2915, 2154, 2915, 2154 ,465 };
// Coordinates for the image
glTexCoord2f(0, 0); glVertex2f(arry[0], arry[1]);
glTexCoord2f(0, 1); glVertex2f(arry[2], arry[3]);
glTexCoord2f(1, 1); glVertex2f(arry[4], arry[5]);
glTexCoord2f(1, 0); glVertex2f(arry[6], arry[7]);
glEnd();
/*glDisable(GL_SCISSOR_TEST);*/
glPopMatrix();
return 1;
}
If someone could help with this, I would greatly appreciate it.
Anybody knows how to keep a triangle without deformations and always at the middle of the windows whatever is his size?
I know I have to do one callback with reshape function and then define it, but I'm not sure what is going inside resize function:
void resize(int width, int height) {
viewport(0,0,width,height);
...?
}
I have this main help. glutInitWindowSize(600, 600);
Since the GL calls use normalised vertex coordinates ranging from -1 to +1, it is possible to keep any object in the center of the screen by using the right coordinates independent of the screen pixel sizes.
However, the same independency also brings in the behaviour that, depending on the screen aspect ratio (or window aspect ratio, as the case may be) the object will also change, unless explicitly accounted for. See the discussions in How can i convert an oval to circle openGL ES2.0 Android
Here's a important hint: Don't use the resize callback to do anything with OpenGL.
I know I have to do one callback with reshape function and then define it, but I'm not sure what is going inside resize function:
Then you knew wrong.
It leads to a lot of confusion. OpenGL is a state based drawing API and like all state machines it should be reset into a well known state before you use it. That includes projection and viewport. With that in mind your problem becomes trivial
void display()
{
/* draw some stuff */
glViewport(...);
setup_projection();
setup_modelview();
draw_stuff();
/* draw some other stuff with different projection and modelview */
glViewport(...);
setup_other_projection();
setup_other_modelview();
draw_other_stuff();
/* ... */
swapBuffers();
}
If you're using GLUT you can use glutGet(GLUT_WINDOW_WIDTH) and glutGet(GLUT_WINDOW_HEIGHT) to retrieve the window's size for the viewport calls.
So in your case you'd use a glViewport that covers your whole window and a projection that always maps a certain view space into that viewport. For example
void display()
{
int const win_width = glutGet(GLUT_WINDOW_WIDTH);
int const win_height = glutGet(GLUT_WINDOW_HEIGHT);
float const win_aspect = (float)win_width / (float) win_height;
glViewport(0, 0, win_width, win_height);
/* Using fixed function pipeline for brevity */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
/* map the vertical range -1…1 to the window height and
a symmetric range -aspect … 0 … +aspect to the viewport */
glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
draw_triangle();
/* ... */
glutSwapBuffers();
}
My task is to render a set of 50 RGB frames using openGL's glut library.
I tried: In 3D cube rotation, i have a set of vertices using which i render it to the window. However, in case of rendering the RGB frames what should be done? Below is the code using which i render my 3d cube:
#include <glut.h>
GLfloat vertices[24]={-1.0,-1.0,-1.0,1.0,-1.0,-1.0,1.0,1.0,-1.0,-1.0,1.0,-1.0,-1.0,-1.0,1.0,1.0,-1.0,1.0,1.0,1.0,1.0,-1.0,1.0,1.0};
GLfloat colors[24]={-1.0,-1.0,-1.0,1.0,-1.0,-1.0,1.0,1.0,-1.0,-1.0,1.0,-1.0,-1.0,-1.0,1.0,1.0,-1.0,1.0,1.0,1.0,1.0,-1.0,1.0,1.0};
GLubyte cubeIndices[24]={0,3,2,1,2,3,7,6,0,4,7,3,1,2,6,5,4,5,6,7,0,1,5,4};
static GLfloat theta[3]={0,0,0};
static GLint axis=2;
void display()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glRotatef(theta[0],1.0,0.0,0.0);
glRotatef(theta[1],0.0,1.0,0.0);
glRotatef(theta[2],0.0,0.0,1.0);
glDrawElements(GL_QUADS,24,GL_UNSIGNED_BYTE,cubeIndices);
glutSwapBuffers();
glFlush();
}
void spinCude()
{
theta[axis]+=2.0;
if(theta[axis]>360.0)
theta[axis]-=360.0;
display();
}
void init()
{
glMatrixMode(GL_PROJECTION);
glOrtho(-2.0,2.0,-2.0,2.0,-10.0,10.0);
glMatrixMode(GL_MODELVIEW);
}
void mouse(int btn,int state, int x,int y)
{
if(btn==GLUT_LEFT_BUTTON&& state==GLUT_DOWN) axis=0;
if(btn==GLUT_MIDDLE_BUTTON&& state==GLUT_DOWN) axis=1;
if(btn==GLUT_RIGHT_BUTTON&& state==GLUT_DOWN) axis=2;
}
void main(int argc, char **argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB|GLUT_DEPTH);
glutInitWindowSize(500,500);
glutCreateWindow("Simple YUV Player");
init();
glutDisplayFunc(display);
glutIdleFunc(spinCude);
glutMouseFunc(mouse);
glEnable(GL_DEPTH_TEST);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,GL_FLOAT,0,vertices);
//glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3,GL_FLOAT,0,colors);
glutMainLoop();
}
Can anyone suggest me some example or tutorial such that i can modify above code to display RGB frames.
Once you have your RGB-Frame as raw-data in memory things are pretty straight-forward. Create a texture using glGenTextures, bind it using glBindTexture and upload the data via glTexImage2D or glTexSubImage2D. Then render a fullscreen quad or whatever you like with that texture. The benefit of that is that you could render multiple 'virtual' TVs in your scene just by rendering multiple quads with that same texture, imagine a TV-Store where the same video runs on dozen of TVs.
glDrawPixels might also work but it is much less versatile.
I don't know if uploading via texture is the way to go (hardware accelerated movie playback programs like VLC are most likely doing something far more advanced), but it should be a good start.
As Marius already suggested, implement texture mapping first. It's rather straigth forward any texture mapping tutorial will do.
Rendering frames are not the best with OpenGL you should try to avoid them as much as you can since it may involve a client -> host memory copy which is really costy ( takes too much time ) or simply it just takes up too much memory. Anyways if you really have to do it just generate as much textures as you need with glGenTextures load them up with the textures by glTexImage2D and then flip over the frames with a simple loop in each frame.
P.S. Judging by your application's name "YUV Player" you may also need to convert the input data since OpenGL mostly uses RGB not YUV.
I want to achieve the following result in OpenGL (and C++): I have a plot I want to use as background of an animation and I want it to be fix when some points move on its surface.
For example (see the image): I want to calculate the level plot of 3 variables function (black and white in the photo), I want to set it as background and show an animation of points moving on the surface (red points in the photo).
What is the best method to achieve it and have good performances?
One possibility would be to draw your background image as a texture on a quad behind the other points (e.g., points at Z=0.1, quad at Z=0.2). Since you (presumably) don't want the quad scaled compared to the points, you probably want to use an orthographic projection, not perspective.
It really depends on which version of OpenGL you are using.
If you are using SDL and OpenGL 2.x, it is very easy, but if you are using something like GLFW and OpenGL 3.x, it will be quite hard.
But this is how you would do it with SDL and OpenGL 2.1(not all code is included, you will have to write some of it yourself):
void plot(SDL_Surface *surf, int x, int y, int r, int g, int b)
{
int color = SDL_MapRGB(surf->format, r, g, b);
Uint32 *framebuffer = (Uint32*) surf->pixels;
framebuffer[y * surf->w + x] = color;
}
int main()
{
/* set up SDL and OpenGL */
SDL_Surface *img = SDL_LoadBMP("base_background.bmp");
/* plot functions like this: */
plot(img, x, f(x), 255, 0, 0); // example colours
/* create an OpenGL surface(code can be found on google easily) */
GLuint tex = create_GL_texture(img);
/* draw it like you would normally */
glBindTexture(GL_TEXTURE_2D, tex);
glBegin(GL_QUADS);
...
glEnd();
return 0;
}
For OpenGL 3.x, it is a bit harder. You can find tutorials here.
I am using an openGL to drag an image (loaded bitmap) and wondering if there some methods/function to transform the image on the screen.
so far i have done this code to load an image:
void CDisplayControlPanelView::OnDraw(CDC* /*pDC*/)
{
CDisplayControlPanelDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if(!pDoc)
return;
wglMakeCurrent(m_hDC , m_hRC);
RenderScene();
SwapBuffers(m_hDC);
wglMakeCurrent(m_hDC,NULL);
}
void CDisplayControlPanelView::RenderScene()
{
AUX_RGBImageRec* pRGBImage;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
pRGBImage = auxDIBImageLoadA("D:\\map.bmp");
glDrawPixels(pRGBImage->sizeX, pRGBImage->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pRGBImage->data);
glFlush();
}
Use glTranslate. There are many other ways but this is the most simple. Check out some tutorials if you are new to OpenGL, it could help.
The first thing you must understand is, that OpenGL is not a scene graph. It's a drawing API, very much like Windows GDI. The function glDrawPixels is not very unlike a BitBlt from a MemDC.
Anyway: You shouldn't use glDrawPixels. It's slow and deprecated. The way to draw images in OpenGL is uploading the image into a texture and drawing a textured quad. The quad you can freely move around as you like.