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