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

1 comment:

  1. In order to get this to compile under qwt 6.0.1, I had to change line 35 to

    setPen(QPen(c, penSize));

    ReplyDelete