OpenCV - Confusion using calcHist
Asked Answered
R

1

6

I've read the documentation for calcHist() many times, but I think my inexperience with OpenCV and rusty programming skills are completely precluding me from understanding it.

I'm looking to count pixels in one channel of an HSV image (Hue, or channel[0]) for segmentation purposes using 10 bins that closely approximate color according to something like (let's use this as an example, I stole the ranges off the web - fwiw, it seems erroneous to omit purple-red):

Red: 0-19 & 330-360 Red-Yellow (RY): 20-49 Yellow: 50-69 YG: 70-84 Green: 85-170 GB: 171-191 Blue: 192-264 BP: 265-289 Purple: 290-329

And so on...

So how do I do this with calcHist?

I'm as far as:

#include <opencv2/opencv.hpp>
#include <vector>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    Mat scene, sceneHSV, dest, histo;
    int numImages = 1, histChannel[] = {0}, dims = 1, histSize[] = {10};

    float redRange[] = {0, 10};
    float roRange[] = {10, 25};
    float orangeRange[] = {25, 35};
    float oyRange[] = {35, 42};
    float yellowRange[] = {42, 85};
    float ygRange[] = {85, 96};
    float greenRange[] = {96, 132};
    float gbRange[] = {132, 145};
    float blueRange[] = {145, 160};
    float bpRange[] = {160, 165};
    float purpleRange[] = {165, 180};

    const float* ranges[] = {redRange, roRange, orangeRange, oyRange, yellowRange, ygRange, greenRange, gbRange, blueRange, bpRange, purpleRange};

    vector<Mat> channels;

    scene = imread("Apple.jpg", 1);
    if (scene.data == NULL)
    {
        cout<<"FAIL"<<endl;
        cin.get();
    }

    cvtColor(scene, sceneHSV, CV_BGR2HSV);
    dilate(sceneHSV, sceneHSV, Mat(), Point(-1, -1), 1, BORDER_CONSTANT, 1);
    pyrMeanShiftFiltering(sceneHSV, dest, 2, 50, 3);
    split(sceneHSV, channels); 

    calcHist(&scene, 1, histChannel, Mat(), histo, dims, histSize, ranges, false, false); 

    cout<<histo<<endl;

    waitKey(0);

    return 0;
}

Now what? What would the arguments to calcHist look like in this case, and what does the output histogram look like? Simply a 1x9 array full of ints?

Thanks very much.

Rosewood answered 4/3, 2013 at 21:22 Comment(0)
P
5

I modified the code from here

You might also want to take a look at the documentation of cvtColor here

Note that I have not tried to compile or run this code so I do not guarantee that it will work. But nonetheless, it might be useful as a reference.

Mat hist;
int nimages = 1; // Only 1 image, that is the Mat scene.
int channels[] = {0} // Index for hue channel
int dims = 1 // Only 1 channel, the hue channel
int histSize[] = {9} // 9 bins, 1 each for Red, RY, Yellow, YG etc.
float hranges[] = { 0, 180 }; // hue varies from 0 to 179, see cvtColor
const float *ranges[] = {hranges};

// Compute the histogram.
calcHist(&scene, 
nimages, 
channels, 
Mat(), // No mask
hist, dims, histSize, ranges, uniform=true)

// Now hist will contain the counts in each bin.
// Lets just print out the values. Note that you can output Mat using std::cout
cout << "Histogram: " << endl << hist << endl;

// To access the individual bins, you can either iterate over them
// or use hist.at<uchar>(i, j); Note that one of the index should be 0
// because hist is 1D histogram. Print out hist.rows and hist.cols to see if hist is a N x 1 or 1 x N matrix.
/*
MatIterator_<uchar> it, end;
int binIndex = 0;
for( it = hist.begin<uchar>(), end = hist.end<uchar>(); it != end; ++it)
{
  printf("Count in %d bin: %d\n", binIndex, *it);
  ++binIndex;
}
*/
Petrarch answered 5/3, 2013 at 1:23 Comment(8)
I will try this, though a few other things - I did notice that OpenCV's HSV scale goes from 0-180, so I'll just have to scale all of those ranges by a factor of one-half - thanks for the reminder.Rosewood
Also, how does the above account for the ranges I gave for colors being unevenly spaced?Rosewood
I think if you convert your matrix to CV_32F or CV_64F before you pass it to cvtColor(...) the hue will be in the range 0 to 360. To deal with unevenly spaced ranges, there are a few things to change. First, set "uniform" argument to false. Second, change hranges to float hranges[] = {0, 20, 20, 50, 50, 70, ..., Li, Ui, ...} where hranges contains histSize[i]+1 elements, that is 1 more than your number of bins. Li, Ui are the respective lower(inclusive) and upper(exclusive) boundary for the bin i. See the link to calcHist that I gave above for more details. Hope this helps.Petrarch
Here is how I would recommend going about testing the calcHist function. Generate random numbers from a uniform/gaussian distribution in some range say 0 to 255 inclusive. Call calcHist on this Mat. Check the output to see if it looks "ok". If you know python, plotting the histogram is easy. Additionally, OpenCV's samples has sample code on how to use calcHist which you might be able to modify to suit your needs as well as to test your understanding of the method.Petrarch
Tried the updated code contained in the edit on my original post and receive this error: OpenCV Error: Assertion Failed (ranges[i][j] < ranges[i][j+1]) in an unknown function, C:\\slave\WinInstallerMegaPack\src\opencv\modules\imageproc\src\histogram.cpp, line 203Rosewood
Hi. I think you got it wrong. ranges is still declared as ranges = {hranges}; hranges is modified to float hranges[] = {0, 10, 10, 25, 25, 35, ... , 165, 180};Petrarch
Hi Lightalchemist, I get the same error as before. Can hranges really contain that many elements? As per the documentation, for each dimension i in ranges (in this case ranges is 1D, and non-uniform), it should contain histSize+1 elements, in this case 11, which makes sense so far. My attempt above was the only way I could think of to make ranges have the correct # of elements while still being histSize+1.Rosewood
I solved it - simply remove the doubles. I will check that that's the correct way to do this - If so, the documentation is somewhat misleading in that way.Rosewood

© 2022 - 2024 — McMap. All rights reserved.