Showing posts with label QWT. Show all posts
Showing posts with label QWT. Show all posts

Tuesday, 12 March 2013

Plot Multi-Channel Histogram in QWT -Part 2

This is the second part in a series on how to do multi-channel plots in QWT. These kinds of plots can be done very simply in Matlab but are more challenging to implement in C++. Armed with the QWT library, we have investigated how to overlay multiple channel histograms on each other. Here I will show you how to make a plot similar to what you would do with the subplot() function in Matlab. This method simply puts multiple channels into one window in a grid form; this method is more presentable and makes it easier to compare plots. Lets get started...

Code

The most important part of the code is the PlotMatrix class which uses a QGridLayout to arrange the sub-plots into a matrix with specified number of rows and cols.The class is defined in the plotmatix.h header file and implemented in plotmatrix.cpp. In main.cpp we define and implement a MainWindow class which inherits from the PlotMatrix class, the histograms are created from an image and displayed in the MainWindow.

The Main function and others

The MainWindow class is a derived class from the PlotMatrix class. Here, we define a 2 by 3 matrix of plots in a static manner(Although, we only want to plot 3 channels). Grid lines are included in the plots for fun but this can be removed or edited as needed.
#include <QApplication>
#include <plotmatrix.h>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;

class MainWindow: public PlotMatrix
{
public:
   MainWindow():PlotMatrix( 2, 3 )
   {
     for ( int row = 0; row < numRows(); row++ )
     {
        for ( int col = 0; col < numColumns(); col++ )
        {
            //set default scale if any.
            setAxisScale( QwtPlot::yLeft, row, col, 0, 100 );
            setAxisScale( QwtPlot::xBottom, row, col, 0, 255 );
            QwtPlot *plt = plot( row, col );
            plt->setCanvasBackground( QColor( Qt::darkGray ) );
            //Allows the Axis to scale automatically. 
            plt->setAxisAutoScale(QwtPlot::yLeft, true);
            //Show plot grid lines just for aesthetics.
            QwtPlotGrid *grid = new QwtPlotGrid();
            grid->enableXMin( true );
            grid->setMajorPen( Qt::white, 0, Qt::DashLine);
            grid->setMinorPen( Qt::gray, 0 , Qt::DotLine );
            grid->attach( plt );
        }
     }
   }
};

void getPoints(MatND& hist, int* histSize, QPolygonF& points)
{
    points.clear();
    for( int h = 0; h < histSize[0]; ++h) {
      float bin_value = hist.at<float>(h);
      points << QPointF((float)h, bin_value);
    }
}

class Curve: public QwtPlotCurve
{
public:
    Curve( const QString &title, const QColor &colour, int penSize, 
           QPolygonF &points ):QwtPlotCurve( title )
    {
        QColor c = colour;
        c.setAlpha( 150 );//handle colour transparency
        setPen(c, penSize);
        setBrush( c );
        setSamples( points );
        setRenderHint( QwtPlotItem::RenderAntialiased );
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    if (argc < 2)
        return 1;

    //Read input image
    Mat img = imread(argv[1]);

    //Convert to grayscale
    if (img.data && img.channels() != 3)
       return 1;

    MainWindow mainWindow;
    mainWindow.resize( 600, 400 );

    int histSize[] = {256}; // number of bins
    float hranges[] = {0.0, 255.0}; // min and max pixel value
    const float* ranges[] = {hranges};
    int channels[] = {0}; // only 1 channel used

    std::vector<Mat> rgbChannels(3);
    split(img, rgbChannels);

    MatND hist;
    QPolygonF points;

    calcHist(&rgbChannels[2], 1, channels, Mat(), hist, 1, histSize,ranges);
    /*Insert the points that should be plotted on the graph in a
    Vector of QPoints or a QPolgonF */
    getPoints(hist, histSize, points);
    Curve *curve = new Curve("Red Channel", Qt::red, 2, points);
    curve->attach( mainWindow.plot(0,0) ); // Attach curve to the plot

    calcHist(&rgbChannels[1], 1, channels, Mat(), hist, 1, histSize,ranges);
    getPoints(hist, histSize, points);
    curve = new Curve("Green Channel", Qt::green, 2, points);
    curve->attach( mainWindow.plot(0,1) );

    calcHist(&rgbChannels[0], 1, channels, Mat(), hist, 1, histSize,ranges);
    getPoints(hist, histSize, points);
    curve = new Curve("Blue Channel", Qt::blue, 2, points);
    curve->attach( mainWindow.plot(0,2) );


    mainWindow.show(); //Show plot
    return a.exec();
}

PlotMatrix class

This implementation uses a QVector of pointers to QwtPlot instances to represent the matrix of subplots. It provides a function plot(row, col) which returns the pointer to the desired plot.
//PlotMatrix.h

#ifndef _PLOT_MATRIX_H_
#define _PLOT_MATRIX_H_

#include <qframe.h>
#include <qwt_plot.h>

class PlotMatrix: public QFrame
{
    Q_OBJECT

public:
    PlotMatrix( int rows, int columns, QWidget * parent = NULL );
    virtual ~PlotMatrix();

    int numRows() const;
    int numColumns() const;

    QwtPlot* plot( int row, int column );
    const QwtPlot* plot( int row, int column ) const;

    void setAxisScale(int axisId, int row, int col,
        double min, double max, double step = 0 );

private:
    QVector<QwtPlot *> plotWidgets;
};

#endif

//PlotMatrix.cpp
//This is based on the PlotMatrix implementation in the Qwt Samples. 
#include <qlayout.h>
#include <qpen.h>
#include <qwt_plot.h>
#include <qwt_scale_widget.h>
#include <qwt_scale_draw.h>
#include "plotmatrix.h"

PlotMatrix::PlotMatrix( int numRows, int numColumns, QWidget *parent ):
    QFrame( parent )
{
    plotWidgets.resize( numRows * numColumns );

    QGridLayout *layout = new QGridLayout( this );
    for ( int row = 0; row < numRows; row++ )
    {
        for ( int col = 0; col < numColumns; col++ )
        {
            QwtPlot *plot = new QwtPlot( this );
            layout->addWidget( plot, row, col );
            plotWidgets[row * numColumns + col] = plot;
        }
    }
}

PlotMatrix::~PlotMatrix()
{
    foreach(QwtPlot* p, plotWidgets)
        delete p;
}

int PlotMatrix::numRows() const
{
    const QGridLayout *l = qobject_cast<const QGridLayout *>( layout() );
    if ( l )
        return l->rowCount();

    return 0;
}

int PlotMatrix::numColumns() const
{
    const QGridLayout *l = qobject_cast<const QGridLayout *>( layout() );
    if ( l )
        return l->columnCount();
    return 0;
}

QwtPlot* PlotMatrix::plot( int row, int column )
{
    const int index = row * numColumns() + column;
    if ( index < plotWidgets.size() )
        return plotWidgets[index];

    return NULL;
}

const QwtPlot* PlotMatrix::plot( int row, int column ) const
{
    const int index = row * numColumns() + column;
    if ( index <plotWidgets.size() )
        return plotWidgets[index];

    return NULL;
}

void PlotMatrix::setAxisScale( int axis, int row, int col,
    double min, double max, double step )
{

    QwtPlot *plt = plot( row, col );
    if ( plt )
    {
        plt->setAxisScale( axis, min, max, step );
        plt->updateAxes();
    }
}

Result

Conclusion

The subplot() is one of the most useful matlab functions if you plot several graphs or images. Now you can have something similar in QWT although not as simple. It is also possible to plot multi-channel histograms in a 3 dimensional plot, we leave this for another time. Happy Coding!.

Saturday, 2 March 2013

Plot Multi-Channel Histogram in QWT -Part 1

Here I will show you how to plot a multi-channel/colour histogram using QWT. I generate the histograms for the different channels in a RGB image using OpenCV, then I plot the histogram of each channel overlaid on each other on the same axis. This method is suitable for visualization but not very suitable for peak/valley histogram analysis as the overlaid colours would merge making it difficult to see what peaks/valleys belong to which channel.
The set-up for this tutorial is similar to the one in my previous tutorial, only this time I also include the OpenCV library. Again, we can just dive straight into the code.

Code

#include <QApplication>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;

void getPoints(MatND& hist, int* histSize, QPolygonF& points)
{
    for( int h = 0; h < histSize[0]; ++h) {
      float bin_value = hist.at<float>(h);
      points << QPointF((float)h, bin_value);
    }
}


class Curve: public QwtPlotCurve
{
public:
    Curve( const QString &title ):
        QwtPlotCurve( title )
    {
        setRenderHint( QwtPlotItem::RenderAntialiased );
    }

    void setColour(const QColor &color, int penSize)
    {
        QColor c = color;
        c.setAlpha( 150 );
        setPen(c, penSize);
        setBrush( c );
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    if (argc < 2)
        return 1;

    //Read input image
    Mat img = cv::imread(argv[1]);

    //Convert to grayscale
    if (img.data && img.channels() != 3)
       return 1;

    QwtPlot plot; //Create plot widget
    plot.setTitle( "Plot Demo" ); //Name the plot
    plot.setCanvasBackground(Qt::white ); //Set the Background colour
    plot.setAxisScale(QwtPlot::xBottom,0,255); //Scale the x-axis
    plot.insertLegend(new QwtLegend()); //Insert a legend

    int histSize[] = {256}; // number of bins
    float hranges[] = {0.0, 255.0}; // min and max pixel value
    const float* ranges[] = {hranges};
    int channels[] = {0}; // only 1 channel used

    std::vector<cv::Mat> rgbChannels(3);
    split(img, rgbChannels);

    MatND hist;
    QPolygonF points;

    calcHist(&rgbChannels[2], 1, channels, cv::Mat(), hist, 1, histSize,ranges);
    Curve *curve = new Curve("Red Channel");
    curve->setColour(Qt::red , 2);//Set colour and thickness for drawn curve.
    /*Insert the points that should be plotted on the graph in a
    Vector of QPoints or a QPolgonF */

    getPoints(hist, histSize, points);
    curve->setZ( curve->z() - 1 );
    curve->setSamples(points); //pass points to be drawn on the curve
    curve->attach( &plot ); // Attach curve to the plot


    calcHist(&rgbChannels[1], 1, channels, cv::Mat(), hist, 1, histSize,ranges);
    curve = new Curve("Green Channel");
    curve->setColour(Qt::green , 2);
    points.clear();
    getPoints(hist, histSize, points);
    curve->setZ( curve->z() - 2 );
    curve->setSamples( points );
    curve->attach( &plot );


    calcHist(&rgbChannels[0], 1, channels, cv::Mat(), hist, 1, histSize,ranges);
    curve = new Curve("Blue Channel");
    curve->setColour(Qt::blue, 2);
    points.clear();
    getPoints(hist, histSize, points);
    curve->setZ( curve->z() - 3 );
    curve->setSamples( points );
    curve->attach( &plot );

    plot.resize( 600, 400 ); //Resize the plot
    plot.show(); //Show plot
    return a.exec();

}

Result

Figure 1: A) The input image B) The resulting plot 
Note: It is possible to remove the coloured area under the curves and reduce the plot to a simple line plot. This would make each channels' peaks and valleys more visible. This could be done by commenting out line:36 in the code. The result is shown below.
Figure 2: The result with the setBrush() property turned off.

Conclusion

That concludes this part of the tutorial, it is also possible to plot each histogram in its separate axis (in a matrix format) similar to what using the subplot() function in matlab does. This would be seen in our next tutorial. Happy Coding!.

Saturday, 2 February 2013

Draw an OpenCV histogram using QWT

The QWT Library gives us the ability to create graphs, scale axes, insert legend and do a whole lot of graphing stuff, in a very easy manner. I wanted to show how easy it was to use so in this tutorial I plot an openCV histogram using QWT.
The set-up for this tutorial is similar to the one in my previous tutorial, only this time I also include the OpenCV library. So we can dive straight into the code.

Code

#include <QApplication>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    if (argc < 2)
        return 1;
    
    //Read input image
    cv::Mat img = cv::imread(argv[1]);
    
    //Convert to grayscale
    if (img.data && img.channels() == 3)
        cv::cvtColor(img, img, CV_BGR2GRAY);
    else
        return 1;

    int histSize[1] = {256}; // number of bins
    float hranges[2] = {0.0, 255.0}; // min and max pixel value
    const float* ranges[1] = {hranges};
    int channels[1] = {0}; // only 1 channel used

    cv::MatND hist;
    // Compute histogram
    cv::calcHist(&img, 1, channels, cv::Mat(), hist, 1, histSize,ranges);

    double minVal, maxVal;
    cv::minMaxLoc(hist, &minVal, &maxVal);//Locate max and min values
   
    QwtPlot plot; //Create plot widget
    plot.setTitle( "Plot Demo" ); //Name the plot
    plot.setCanvasBackground( Qt::black ); //Set the Background colour
    plot.setAxisScale( QwtPlot::yLeft, minVal, maxVal ); //Scale the y-axis
    plot.setAxisScale(QwtPlot::xBottom,0,255); //Scale the x-axis
    plot.insertLegend(new QwtLegend()); //Insert a legend

    QwtPlotCurve *curve = new QwtPlotCurve(); // Create a curve
    curve->setTitle("Count"); //Name the curve
    curve->setPen( Qt::white, 2);//Set colour and thickness for drawing the curve 
    //Use Antialiasing to improve plot render quality
    curve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
    /*Insert the points that should be plotted on the graph in a 
    Vector of QPoints or a QPolgonF */
    QPolygonF points;
    for( int h = 0; h < histSize[0]; ++h) {
        float bin_value = hist.at<float>(h);
        points << QPointF((float)h, bin_value);
    }

    curve->setSamples( points ); //pass points to be drawn on the curve
    curve->attach( &plot ); // Attach curve to the plot 
    plot.resize( 600, 400 ); //Resize the plot
    plot.show(); //Show plot

    return a.exec();

}
The code is well commented and is therefore self explanatory - no need for extra explanations. The result can be seen in the image below.

Wednesday, 30 January 2013

Getting started with QWT

I have been looking for a graphing API for QT and eventually found one that suits my needs called QWT ( Qt Widgets for Technical Applications). I ran into some issues while setting it up but finally got it running so here is the resulting tutorial on how to set-up QWT in QT creator (for Linux 12.0.4).
Now we can begin, first step is to download the source files from here. The version used for this tutorial is QWT 6.1. In the terminal, navigate to the location of the downloaded .tar.bz2 file, then type the following commands:
$ tar -xjvf qwt-6.1-rc3.tar.bz2
$ cd qwt-6.1-rc3
$ qmake qwt.pro
$ make
$ make install
Launch QT creator and then Go to File > New File or Project. Select a QT Gui Application give the project a name e.g. “FirstQwtProject”.You could leave the other settings in the wizard as they are or change them to suit your own projects. I have kept the 'FirstQwtProject.pro' ( this will vary depending on the Project Name you have chosen) and 'main.cpp' but deleted the remaining files (MainWindow class) as they would not be need for this simple introduction. To run QWT programs in QT creator we need to let the IDE know where to find the QWT libraries.
So open the .pro file associated with the project and append the following lines at the end of the file. This should be included in every QWT Project you create but remember to change the include Path "/usr/local/qwt-6.1.0-rc3/.." to the location of the QWT install directory on your PC.
CONFIG += qwt
INCLUDEPATH +="/usr/local/qwt-6.1.0-rc3/include"
LIBS += -L/usr/local/qwt-6.1.0-rc3/lib -lqwt
Now go to the 'main.cpp' file and type in the following lines of code and press Ctrl+R to run it.
#include <QApplication>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QwtPlot plot;
    plot.setTitle( "Plot Demo" );
    plot.setCanvasBackground( Qt::white );
    plot.setAxisScale( QwtPlot::yLeft, 0.0, 10.0);
    plot.insertLegend( new QwtLegend() );

    QwtPlotGrid *grid = new QwtPlotGrid();
    grid->attach( &plot );

    QwtPlotCurve *curve = new QwtPlotCurve();
    curve->setTitle( "Pixel Count" );
    curve->setPen( Qt::blue, 4 ),
    curve->setRenderHint( QwtPlotItem::RenderAntialiased, true );

    QwtSymbol *symbol = new QwtSymbol( QwtSymbol::Ellipse,
        QBrush( Qt::yellow ), QPen( Qt::red, 2 ), QSize( 8, 8 ) );
    curve->setSymbol( symbol );

    QPolygonF points;
    points << QPointF( 0.0, 4.4 ) << QPointF( 1.0, 3.0 )
        << QPointF( 2.0, 4.5 ) << QPointF( 3.0, 6.8 )
        << QPointF( 4.0, 7.9 ) << QPointF( 5.0, 7.1 );
    curve->setSamples( points );

    curve->attach( &plot );

    plot.resize( 600, 400 );
    plot.show();

    return a.exec();
}
This is what you should see.

Conclusion

QWT Libraries are one of many options for plotting graphs in QT. In the next tutorial, I will show you how to plot histograms calculated via OpenCV with QWT.