Tag Archives: Qt

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

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 Qt4, 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:

private:
   bool mSceneChanged; /// Indicates when OpenGL view is to be redrawn

   QImage mRenderQtImg; /// Qt image to be rendered
   cv::Mat mOrigImage; /// original OpenCV image to be shown

   QColor mBgColor; /// Background color

   int mOutH; /// Resized Image height
   int mOutW; /// Resized Image width
   float mImgratio; /// height/width ratio

   int mPosX; /// Top left X position to render image in the center of widget
   int mPosY; /// Top left Y position to render image in the center of widget
  • 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:

signals:
   void imageSizeChanged( int outW, int outH ); /// Used to resize the image outside the widget

public slots:
   bool showImage( cv::Mat image ); /// Used to set the image to be viewed
  • 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:

protected:
   void initializeGL(); /// OpenGL initialization
   void paintGL(); /// OpenGL Rendering
   void resizeGL(int width, int height); /// Widget Resize Event

   void updateScene(); /// Forces a scene update
   void renderImage(); /// Render image on openGL frame
  • 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:

CQtOpenCVViewerGl::CQtOpenCVViewerGl(QWidget *parent) :
QGLWidget(parent)
{
   mSceneChanged = false;
   mBgColor = QColor::fromRgb(150, 150, 150);

   mOutH = 0;
   mOutW = 0;
   mImgratio = 4.0f/3.0f; // Default image ratio

   mPosX = 0;
   mPosY = 0;
}

Definizione delle funzioni:

void CQtOpenCVViewerGl::initializeGL()
{
   makeCurrent();
   qglClearColor(mBgColor.darker());
}

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.

void CQtOpenCVViewerGl::resizeGL(int width, int height)
{
   makeCurrent();
   glViewport(0, 0, (GLint)width, (GLint)height);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();

   glOrtho(0, width, 0, height, 0, 1); // To Draw image in the center of the area

   glMatrixMode(GL_MODELVIEW);

   // ---> Scaled Image Sizes
   mOutH = width/mImgratio;
   mOutW = width;

   if(mOutH>height)
   {
      mOutW = height*mImgratio;
      mOutH = height;
   }

   emit imageSizeChanged( mOutW, mOutH );
   // < --- Scaled Image Sizes

   mPosX = (width-mOutW)/2;
   mPosY = (height-mOutH)/2;

   mSceneChanged = true;

   updateScene();
}

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
void CQtOpenCVViewerGl::updateScene()
{
   if( mSceneChanged && this->isVisible() )
   updateGL();
}

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”.

void CQtOpenCVViewerGl::paintGL()
{
   makeCurrent();

   if( !mSceneChanged )
      return;

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   renderImage();

   mSceneChanged = false;
}

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.

void CQtOpenCVViewerGl::renderImage()
{
    makeCurrent();

    glClear(GL_COLOR_BUFFER_BIT);

    if (!mRenderQtImg.isNull())
    {
        glLoadIdentity();

        QImage image; // the image rendered

        glPushMatrix();
        {
            int imW = mRenderQtImg.width();
            int imH = mRenderQtImg.height();

            // The image is to be resized to fit the widget?
            if( imW != this->size().width() &&
                    imH != this->size().height() )
            {

                image = mRenderQtImg.scaled( //this->size(),
                                             QSize(mOutW,mOutH),
                                             Qt::IgnoreAspectRatio,
                                             Qt::SmoothTransformation
                                             );

                //qDebug( QString( "Image size: (%1x%2)").arg(imW).arg(imH).toAscii() );
            }
            else
                image = mRenderQtImg;

            // --->Centering image in draw area

            glRasterPos2i( mPosX, mPosY );
            // < --- Centering image in draw area

            imW = image.width();
            imH = image.height();

            glDrawPixels( imW, imH, GL_RGBA, GL_UNSIGNED_BYTE, image.bits());
        }
        glPopMatrix();

        // end
        glFlush();
    }
}

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
bool CQtOpenCVViewerGl::showImage( cv::Mat image )
{
    image.copyTo(mOrigImage);

    mImgratio = (float)image.cols/(float)image.rows;

    if( mOrigImage.channels() == 3)
        mRenderQtImg = QImage((const unsigned char*)(mOrigImage.data),
                              mOrigImage.cols, mOrigImage.rows,
                              mOrigImage.step, QImage::Format_RGB888).rgbSwapped();
    else if( mOrigImage.channels() == 1)
        mRenderQtImg = QImage((const unsigned char*)(mOrigImage.data),
                              mOrigImage.cols, mOrigImage.rows,
                              mOrigImage.step, QImage::Format_Indexed8);
    else
        return false;

    mRenderQtImg = QGLWidget::convertToGLFormat(mRenderQtImg);

    mSceneChanged = true;

    updateScene();

    return true;
}

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 completo del widget è scaricabile tramite questo link.

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

%d bloggers like this: