[Tutorial OpenCV Qt] OpenGL Widget per visualizzare immagini da OpenCV in una GUI con Qt (Prima Parte)

Logo Qt
Logo Qt
Logo Qt

In questo tutorial sarà realizzato un Widget Qt derivato da QGLWidget per visualizzare in un’interfaccia grafica le immagini OpenCV (cv::Mat).
Il widget permette una miglior visualizzazione dell’immagine mantenendo il rapporto “larghezza/altezza” della stessa se il Widget viene ridimensionato.

Il tutorial presuppone la conoscenza di base del linguaggio C++, del framework Qt5, dell’ambiente di sviluppo QtCreator e della libreria OpenCV2. Il codice di esempio è stato realizzato per Windows7, ma i file di progetto allegati possono essere adattati per Linux o MacOS senza grossa difficoltà, unicamente settando i corretti “path” per le librerie nel file “.pro” seguendo gli appositi commenti.

La prima parte del tutorial descriverà la realizzazione del widget.

CQtOpenCVViewerGl: realizzazione

In ambiente QtCreator:

  • File -> New File or Project -> C++ -> C++ Class -> Choose
  • Class name: CQtOpenCVViewerGl
  • Base Class: QGLWidget [attenzione a maiuscole e minuscole!]

A questo punto troverete tra i file del progetto che state realizzando due nuovi file: cqtopencvviewergl.cpp e cqtopencvviewergl.h

Aprite il file cqtopencvviewergl.h e aggiungete le seguenti variabili private:

  • mSceneChanged: indica se il frame openGL deve essere renderizzato
  • mRenderQtImg: contiene l’immagine da visualizzare in formato Qt
  • mOrigImage: contiene l’immagine da visualizzare in formato OpenCV
  • mBgColor: è il colore dello sfondo del widget non occupato dall’immagine
  • mOutH & mOutW: sono le dimensioni dell’immagine scalata per poter essere interamente visualizzata nel widget mantenendo il giusto rapporto tra altezza e larghezza
  • mImgratio: mantiene informazioni sul rapporto larghezza/altezza dell’immagine
  • mPosX & mPosY: sono le coordinate del punto in alto a sinistra dell’immagine in modo che sia renderizzata al centro del widget

A questo punto è necessario aggiungere la dichiarazione di alcune funzioni.
Innanzitutto aggiungiamo una funzione di signal e una di slot:

  • void imageSizeChanged( int outW, int outH ): serve a comunicare al “mondo Qt” che il widget ha subito un “resize” e che l’immagine renderizzata avrà dimensioni “outW e outH”. Questo è utile se si vuole fornire al widget un’immagine già riscalata in modo da non obbligare il widget ad effettuare le operazioni di “rescaling” descritte in seguito
  • bool showImage( cv::Mat image ): è la funzione utilizzata per “passare” al widget l’immagine da visualizzare. E’ uno slot in modo da poter essere utilizzata nel meccanismo signal/slot di Qt

Infine inseriamo la dichiarazione delle cinque funzioni utilizzate per il rendering dell’immagine:

  • void initializeGL(): reimplementa la funzione di inizializzazione dell’ambiente openGL
  • void paintGL(): reimplementa la funzione di rendering openGL
  • void resizeGL(int width, int height): reimplementa la funzione di “resize” del “layout” openGL
  • void updateScene(): forza il rendering del widget
  • void renderImage(): è chiamata da paintGL per renderizzare l’immagine

Finite le dichiarazioni si può passare all’inizializzazione delle variabili e alla definizione delle funzioni nel file cqtopencvviewergl.cpp.
Inizializzazione delle variabili nel costruttore:

Definizione delle funzioni:

Imposta il colore di background della scena OpenGL.
Questa funzione, come tutte le successive inizia con una chiamata a makeCurrent utile nel caso la GUI che conterrà il widget contenga più di un widget OpenGL.

Questa funzione è chiamata ogni volta il widget viene ridimensionato.

  • Righe 3-12: inizializzazione del layout openGL per il rendering dell’immagine in 2D
  • Righe 14-21: calcolo delle dimensioni dell’immagine renderizzata in modo che sia rispettato il rapporto larghezza/altezza e che l’immagine venga visualizzata tutta
  • Riga 23: emissione del segnale “imageSizeChanged” per comunicare l’informazione al “mondo Qt” come descritto in precedenza
  • Righe 26-27: calcolo della posizione del vertice in alto a sinistra dell’immagine in modo che sia centrata nel widget
  • Righe 29-31: comunicazione al widget che c’è una nuova immagine da visualizzare

updateScene serve a “forzare” il rendering della scena dopo che l’immagine è stata aggiornata. Per forzare il rendering è necessaria una chiamata a updateGL che viene però effettuata solo se la scena è effettivamente cambiata e se il widget è visibile, in modo da evitare l’inutile esecuzione di codice “pesante”.

paintGL è la funzione chiamata da Qt quando si rende necessario un aggiornamento dell’area di un widget OpenGL.
Nel nostro caso la funzione si occupa solo di “pulire” la scena e chiamare renderImage che si occuperà dell’effetivo rendering della nostra immagine.

renderImage è la funzione principale del widget, infatti si occupa del disegno dell’immagine memorizzata.

  • Righe 19-32: questa parte di codice si occupa del ridimensionamento dell’immagine in modo che venga visualizzata completamente nel widget. Da notare che se le dimensioni sono giuste l’operazione è saltata, con un notevole risparmio di potenza di calcolo. Per questo esiste il signal imageSizeChanged per comunicare al resto dell’applicazione quali sono le dimensioni effettive dell’immagine renderizzata.
  • Riga 36: indica la posizione del pixel in alto a sinistra dell’immagine in modo che la stessa sia sempre posizionata al centro del widget
  • Riga 42: chiamata alla funzione openGL glDrawPixels per il rendering effettivo dell’immagine

Ultima, ma non meno importante, è la funzione necessaria a comunicare al widget quale immagine visualizzare.
showImage accetta in ingresso immagini OpenCV (cv::Mat) a 8 bit con 1 o 3 canali e provvede a convertirle nel formato accettato da QGLWidget.
La funzione calcola e memorizza anche il rapporto larghezza/altezza (riga 5) utilizzato durante il ridimensionamento della stessa.

Il codice del widget è disponibile su Github:

Un esempio di utilizzo è disponibile nella seconda parte del tutorial.

11 Responses

  1. Chris says:

    Hi, Is it possible to publish the article which is about “how to compress image in OpenCV and transmit it”? It is a very good tutorial, however, I cannot find it since the server got an issue. Many thanks

  2. mike says:

    Hi Myzhar,

    many thanks for this tutorial. For my hobby project I use an Raspberry Pi 2 with Qt5.5. All the QCamera (along with gstreamer-1.x) doesn’t work, therefore I try your approach. The problem is now that I have only GLES available and not full OpenGl (by the way I have no idea about opengl, I only want to display a (HW accel.) video from opencv).
    The problem is now that it works fine on the PC but not on the Pi, because some functions are not available, e.g. glDrawPixels(). Can I replace these functions or what is the best solution?

    • Myzhar says:

      Hi Mike,
      I noticed the same problem on my Nvidia Jetson TK1. I had no time to solve it yet. Please let me know if you find a solution. Myzhar

  3. Chi-Jen Lin says:

    I found today that using QLabel can also achieve the same goal. The benefit of using QLabel is that I do not have to struggle with the OpenGL linking errors.

  4. giuseppedes says:

    Please have a look to this question: http://stackoverflow.com/questions/34318360/cqtopencvviewergl-image-size-in-different-screen

    I have a problem with the widget on a particular screen

  5. Sandip says:

    Hello Myzhar,

    I have used your code with QT 5.4.1 with OpenGL ES 2 but unable to use the OpenGLFunction_2_0 class so missing their function like glortho, glClearColor, glOrtho, glRasterPos2i and glDrawPixels. So would you please help me to port this code to QT OpenGL ES 2.0.

  6. Meinert Jordan says:

    Hi, I found a bug for monochrome images, which was introduced currently:
    https://github.com/Myzhar/QtOpenCVViewerGl/commit/307c65809bc8fa7d59d63ad7bf5df72c151c1fd8#diff-2c8e2d448877b31d92d7650cfd24df7eR116

    Could you please apply this change:

    $ diff cqtopencvviewergl.cpp*
    116c116
    else if (image.channels() == 1)

Lascia un Commento

This site uses cookies. Find out more about this site’s cookies.
%d bloggers like this: