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.
  1. #include <QApplication>
  2. #include <plotmatrix.h>
  3. #include <qwt_plot.h>
  4. #include <qwt_plot_curve.h>
  5. #include <qwt_plot_grid.h>
  6. #include <qwt_symbol.h>
  7. #include <qwt_legend.h>
  8. #include <opencv2/core/core.hpp>
  9. #include <opencv2/imgproc/imgproc.hpp>
  10. #include <opencv2/highgui/highgui.hpp>
  11.  
  12. using namespace cv;
  13.  
  14. class MainWindow: public PlotMatrix
  15. {
  16. public:
  17. MainWindow():PlotMatrix( 2, 3 )
  18. {
  19. for ( int row = 0; row < numRows(); row++ )
  20. {
  21. for ( int col = 0; col < numColumns(); col++ )
  22. {
  23. //set default scale if any.
  24. setAxisScale( QwtPlot::yLeft, row, col, 0, 100 );
  25. setAxisScale( QwtPlot::xBottom, row, col, 0, 255 );
  26. QwtPlot *plt = plot( row, col );
  27. plt->setCanvasBackground( QColor( Qt::darkGray ) );
  28. //Allows the Axis to scale automatically.
  29. plt->setAxisAutoScale(QwtPlot::yLeft, true);
  30. //Show plot grid lines just for aesthetics.
  31. QwtPlotGrid *grid = new QwtPlotGrid();
  32. grid->enableXMin( true );
  33. grid->setMajorPen( Qt::white, 0, Qt::DashLine);
  34. grid->setMinorPen( Qt::gray, 0 , Qt::DotLine );
  35. grid->attach( plt );
  36. }
  37. }
  38. }
  39. };
  40.  
  41. void getPoints(MatND& hist, int* histSize, QPolygonF& points)
  42. {
  43. points.clear();
  44. for( int h = 0; h < histSize[0]; ++h) {
  45. float bin_value = hist.at<float>(h);
  46. points << QPointF((float)h, bin_value);
  47. }
  48. }
  49.  
  50. class Curve: public QwtPlotCurve
  51. {
  52. public:
  53. Curve( const QString &title, const QColor &colour, int penSize,
  54. QPolygonF &points ):QwtPlotCurve( title )
  55. {
  56. QColor c = colour;
  57. c.setAlpha( 150 );//handle colour transparency
  58. setPen(c, penSize);
  59. setBrush( c );
  60. setSamples( points );
  61. setRenderHint( QwtPlotItem::RenderAntialiased );
  62. }
  63. };
  64.  
  65. int main(int argc, char *argv[])
  66. {
  67. QApplication a(argc, argv);
  68.  
  69. if (argc < 2)
  70. return 1;
  71.  
  72. //Read input image
  73. Mat img = imread(argv[1]);
  74.  
  75. //Convert to grayscale
  76. if (img.data && img.channels() != 3)
  77. return 1;
  78.  
  79. MainWindow mainWindow;
  80. mainWindow.resize( 600, 400 );
  81.  
  82. int histSize[] = {256}; // number of bins
  83. float hranges[] = {0.0, 255.0}; // min and max pixel value
  84. const float* ranges[] = {hranges};
  85. int channels[] = {0}; // only 1 channel used
  86.  
  87. std::vector<Mat> rgbChannels(3);
  88. split(img, rgbChannels);
  89.  
  90. MatND hist;
  91. QPolygonF points;
  92.  
  93. calcHist(&rgbChannels[2], 1, channels, Mat(), hist, 1, histSize,ranges);
  94. /*Insert the points that should be plotted on the graph in a
  95. Vector of QPoints or a QPolgonF */
  96. getPoints(hist, histSize, points);
  97. Curve *curve = new Curve("Red Channel", Qt::red, 2, points);
  98. curve->attach( mainWindow.plot(0,0) ); // Attach curve to the plot
  99.  
  100. calcHist(&rgbChannels[1], 1, channels, Mat(), hist, 1, histSize,ranges);
  101. getPoints(hist, histSize, points);
  102. curve = new Curve("Green Channel", Qt::green, 2, points);
  103. curve->attach( mainWindow.plot(0,1) );
  104.  
  105. calcHist(&rgbChannels[0], 1, channels, Mat(), hist, 1, histSize,ranges);
  106. getPoints(hist, histSize, points);
  107. curve = new Curve("Blue Channel", Qt::blue, 2, points);
  108. curve->attach( mainWindow.plot(0,2) );
  109.  
  110.  
  111. mainWindow.show(); //Show plot
  112. return a.exec();
  113. }

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.
  1. //PlotMatrix.h
  2.  
  3. #ifndef _PLOT_MATRIX_H_
  4. #define _PLOT_MATRIX_H_
  5.  
  6. #include <qframe.h>
  7. #include <qwt_plot.h>
  8.  
  9. class PlotMatrix: public QFrame
  10. {
  11. Q_OBJECT
  12.  
  13. public:
  14. PlotMatrix( int rows, int columns, QWidget * parent = NULL );
  15. virtual ~PlotMatrix();
  16.  
  17. int numRows() const;
  18. int numColumns() const;
  19.  
  20. QwtPlot* plot( int row, int column );
  21. const QwtPlot* plot( int row, int column ) const;
  22.  
  23. void setAxisScale(int axisId, int row, int col,
  24. double min, double max, double step = 0 );
  25.  
  26. private:
  27. QVector<QwtPlot *> plotWidgets;
  28. };
  29.  
  30. #endif
  31.  
  32. //PlotMatrix.cpp
  33. //This is based on the PlotMatrix implementation in the Qwt Samples.
  34. #include <qlayout.h>
  35. #include <qpen.h>
  36. #include <qwt_plot.h>
  37. #include <qwt_scale_widget.h>
  38. #include <qwt_scale_draw.h>
  39. #include "plotmatrix.h"
  40.  
  41. PlotMatrix::PlotMatrix( int numRows, int numColumns, QWidget *parent ):
  42. QFrame( parent )
  43. {
  44. plotWidgets.resize( numRows * numColumns );
  45.  
  46. QGridLayout *layout = new QGridLayout( this );
  47. for ( int row = 0; row < numRows; row++ )
  48. {
  49. for ( int col = 0; col < numColumns; col++ )
  50. {
  51. QwtPlot *plot = new QwtPlot( this );
  52. layout->addWidget( plot, row, col );
  53. plotWidgets[row * numColumns + col] = plot;
  54. }
  55. }
  56. }
  57.  
  58. PlotMatrix::~PlotMatrix()
  59. {
  60. foreach(QwtPlot* p, plotWidgets)
  61. delete p;
  62. }
  63.  
  64. int PlotMatrix::numRows() const
  65. {
  66. const QGridLayout *l = qobject_cast<const QGridLayout *>( layout() );
  67. if ( l )
  68. return l->rowCount();
  69.  
  70. return 0;
  71. }
  72.  
  73. int PlotMatrix::numColumns() const
  74. {
  75. const QGridLayout *l = qobject_cast<const QGridLayout *>( layout() );
  76. if ( l )
  77. return l->columnCount();
  78. return 0;
  79. }
  80.  
  81. QwtPlot* PlotMatrix::plot( int row, int column )
  82. {
  83. const int index = row * numColumns() + column;
  84. if ( index < plotWidgets.size() )
  85. return plotWidgets[index];
  86.  
  87. return NULL;
  88. }
  89.  
  90. const QwtPlot* PlotMatrix::plot( int row, int column ) const
  91. {
  92. const int index = row * numColumns() + column;
  93. if ( index <plotWidgets.size() )
  94. return plotWidgets[index];
  95.  
  96. return NULL;
  97. }
  98.  
  99. void PlotMatrix::setAxisScale( int axis, int row, int col,
  100. double min, double max, double step )
  101. {
  102.  
  103. QwtPlot *plt = plot( row, col );
  104. if ( plt )
  105. {
  106. plt->setAxisScale( axis, min, max, step );
  107. plt->updateAxes();
  108. }
  109. }

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

  1. #include <QApplication>
  2. #include <qwt_plot.h>
  3. #include <qwt_plot_curve.h>
  4. #include <qwt_plot_grid.h>
  5. #include <qwt_symbol.h>
  6. #include <qwt_legend.h>
  7. #include <opencv2/core/core.hpp>
  8. #include <opencv2/imgproc/imgproc.hpp>
  9. #include <opencv2/highgui/highgui.hpp>
  10.  
  11. using namespace cv;
  12.  
  13. void getPoints(MatND& hist, int* histSize, QPolygonF& points)
  14. {
  15. for( int h = 0; h < histSize[0]; ++h) {
  16. float bin_value = hist.at<float>(h);
  17. points << QPointF((float)h, bin_value);
  18. }
  19. }
  20.  
  21.  
  22. class Curve: public QwtPlotCurve
  23. {
  24. public:
  25. Curve( const QString &title ):
  26. QwtPlotCurve( title )
  27. {
  28. setRenderHint( QwtPlotItem::RenderAntialiased );
  29. }
  30.  
  31. void setColour(const QColor &color, int penSize)
  32. {
  33. QColor c = color;
  34. c.setAlpha( 150 );
  35. setPen(c, penSize);
  36. setBrush( c );
  37. }
  38. };
  39.  
  40. int main(int argc, char *argv[])
  41. {
  42. QApplication a(argc, argv);
  43.  
  44. if (argc < 2)
  45. return 1;
  46.  
  47. //Read input image
  48. Mat img = cv::imread(argv[1]);
  49.  
  50. //Convert to grayscale
  51. if (img.data && img.channels() != 3)
  52. return 1;
  53.  
  54. QwtPlot plot; //Create plot widget
  55. plot.setTitle( "Plot Demo" ); //Name the plot
  56. plot.setCanvasBackground(Qt::white ); //Set the Background colour
  57. plot.setAxisScale(QwtPlot::xBottom,0,255); //Scale the x-axis
  58. plot.insertLegend(new QwtLegend()); //Insert a legend
  59.  
  60. int histSize[] = {256}; // number of bins
  61. float hranges[] = {0.0, 255.0}; // min and max pixel value
  62. const float* ranges[] = {hranges};
  63. int channels[] = {0}; // only 1 channel used
  64.  
  65. std::vector<cv::Mat> rgbChannels(3);
  66. split(img, rgbChannels);
  67.  
  68. MatND hist;
  69. QPolygonF points;
  70.  
  71. calcHist(&rgbChannels[2], 1, channels, cv::Mat(), hist, 1, histSize,ranges);
  72. Curve *curve = new Curve("Red Channel");
  73. curve->setColour(Qt::red , 2);//Set colour and thickness for drawn curve.
  74. /*Insert the points that should be plotted on the graph in a
  75. Vector of QPoints or a QPolgonF */
  76.  
  77. getPoints(hist, histSize, points);
  78. curve->setZ( curve->z() - 1 );
  79. curve->setSamples(points); //pass points to be drawn on the curve
  80. curve->attach( &plot ); // Attach curve to the plot
  81.  
  82.  
  83. calcHist(&rgbChannels[1], 1, channels, cv::Mat(), hist, 1, histSize,ranges);
  84. curve = new Curve("Green Channel");
  85. curve->setColour(Qt::green , 2);
  86. points.clear();
  87. getPoints(hist, histSize, points);
  88. curve->setZ( curve->z() - 2 );
  89. curve->setSamples( points );
  90. curve->attach( &plot );
  91.  
  92.  
  93. calcHist(&rgbChannels[0], 1, channels, cv::Mat(), hist, 1, histSize,ranges);
  94. curve = new Curve("Blue Channel");
  95. curve->setColour(Qt::blue, 2);
  96. points.clear();
  97. getPoints(hist, histSize, points);
  98. curve->setZ( curve->z() - 3 );
  99. curve->setSamples( points );
  100. curve->attach( &plot );
  101.  
  102. plot.resize( 600, 400 ); //Resize the plot
  103. plot.show(); //Show plot
  104. return a.exec();
  105.  
  106. }
  107.  

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