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

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