2

I'm wondering how to plot a 2d histogram of an HSV Mat in opencv c++. My current code attempting to display it fails miserably. I've looked around on how to plot histograms and all the ones I've found were those plotting them as independent 1d histograms.

Here's my current output with the number of hue bins being 30 and saturation bins being 32:

Here's another output with the number of hue bins being 7 and saturaation bins being 5:

I would like it to look more like the result here

http://docs.opencv.org/doc/tutorials/imgproc/histograms/histogram_calculation/histogram_calculation.html

I also noticed whenever I do cout << Hist.size it gives me 50x50. Am I to understand that just means the first dimension of the array is 250 in size?

Also, how does one sort the histogram from highest to lowest (or vice versa) value frequency? That is another problem I am trying to solve.

My current function is as follows.

void Perform_Hist(Mat& MeanShift, Mat& Pyramid_Result, Mat& BackProj){  

  Mat HSV, Hist;

  int histSize[] = {hbins, sbins};
  int channels[] = {0, 1};

  float hranges[] = {0, 180};
  float sranges[] = {0, 256};

  const float* ranges[] = {hranges, sranges};

  cvtColor(MeanShift, HSV, CV_BGR2HSV);

  Mat PyrGray = Pyramid_Result.clone();

  calcHist(&HSV, 1, channels, Mat(), Hist, 2, histSize, ranges, true, false); 
  normalize(Hist, Hist, 0, 255, NORM_MINMAX, -1, Mat());  
  invert(Hist, Hist, 1);  
  calcBackProject(&PyrGray, 1, channels, Hist, BackProj, ranges, 1, true);

  double maxVal = 0; minMaxLoc(Hist, 0, &maxVal, 0, 0);

  int scale = 10;
  Mat histImage = Mat::zeros(sbins*scale, hbins*10, CV_8UC3);

  for(int i = 1; i < hbins * sbins; i++){
     line(histImage,
     Point(hbins*sbins*(i-1), sbins - cvRound(Hist.at<float>(i-1))),
     Point(hbins*sbins*(i-1), sbins - cvRound(Hist.at<float>(i))),
     Scalar(255,0,0), 2, 8, 0);
  }
  imshow (HISTOGRAM, histImage);
}
rayryeng
  • 102,964
  • 22
  • 184
  • 193
Zypps987
  • 404
  • 6
  • 21
  • what is it 2D histogram ? Histogram is probability (or pixel count) of each color (or any other parameter) in the image (or any dataset with any dimensionality) so plot is 2d graph x axis is the color (value,code,index...) and y axis is the probability. So you mean by 2D histogram this or you want to make histogram for each segment of image (like grid)? and plot the histograms somehow like a 3D graph? .... btw. histogram output is usually 1D array not an image ... – Spektre Mar 26 '15 at 09:48
  • also you can not sort colors by frequency because color is not light frequency (light of some frequency has color but backwards it does not work) so you can sort by hue instead. if you normalize the color intensity (value=1) then you can plot 3D graph of histogram base xy plane is the HSV color circle and Z is the probability of that color ... without normalization you would need 4D graph ... for such plot type – Spektre Mar 26 '15 at 10:08
  • It is indeed a 2d histogram, I should've made that point clear. My bad! Also, how would I sort by hue? Can I also sort by the saturation to follow each hue or no? – Zypps987 Mar 26 '15 at 16:59
  • you can sort,map,wrap the histogram how you like see my answer for an example ... so it really depends on what you want to output and in how many dimensions .... – Spektre Mar 26 '15 at 19:10
  • I would really like to just sort it so the highest hue and saturation in the histogram are sorted and follow one another. I tried out the sort() function in opencv and tried sort(hist, hist, by rows) and sort (hist, hist, by columns) but unfortunately it seems it's not that simple. – Zypps987 Mar 27 '15 at 01:07
  • see [edit1] at my answer also you should add the histogram matrix content here and some example of the wanted histogram output form. – Spektre Mar 27 '15 at 07:13

1 Answers1

11

Did you mean something like this?

HSV histogram as 3D graph

  • it is HSV histogram showed as 3D graph
  • V is ignored to get to 3D (otherwise it would be 4D graph ...)

if yes then this is how to do it (I do not use OpenCV so adjust it to your needs):

  1. convert source image to HSV
  2. compute histogram ignoring V value
    • all colors with the same H,S are considered as single color no matter what the V is
    • you can ignore any other but the V parameter looks like the best choice
  3. draw the graph

    • first draw ellipse with darker color (HSV base disc)
    • then for each dot take the corresponding histogram value and draw vertical line with brighter color. Line size is proportional to the histogram value

Here is the C++ code I did this with:

picture pic0,pic1,pic2,zed;

int his[65536];
DWORD w;

int h,s,v,x,y,z,i,n;
double r,a;
color c;

// compute histogram (ignore v)
pic2=pic0;                      // copy input image pic0 to pic2
pic2.rgb2hsv();                 // convert to HSV
for (x=0;x<65536;x++) his[x]=0; // clear histogram
for (y=0;y<pic2.ys;y++)         // compute it
 for (x=0;x<pic2.xs;x++)
    {
    c=pic2.p[y][x];
    h=c.db[picture::_h];
    s=c.db[picture::_s];
    w=h+(s<<8);                 // form 16 bit number from 24bit HSV color
    his[w]++;                   // update color usage count ...
    }
for (n=0,x=0;x<65536;x++) if (n<his[x]) n=his[x];   // max probability

// draw the colored HSV base plane and histogram
zed =pic1; zed .clear(999); // zed buffer for 3D
           pic1.clear(0);   // image of histogram
for (h=0;h<255;h++)
 for (s=0;s<255;s++)
    {
    c.db[picture::_h]=h;
    c.db[picture::_s]=s;
    c.db[picture::_v]=100;      // HSV base darker
    c.db[picture::_a]=0;
    x=pic1.xs>>1;               // HSV base disc position centers on the bottom
    y=pic1.ys-100;
    a=2.0*M_PI*double(h)/256.0; // disc -> x,y
    r=double(s)/256.0;
    x+=120.0*r*cos(a);          // elipse for 3D ilusion
    y+= 50.0*r*sin(a);
    z=-y;
    if (zed.p[y][x].dd>=z){ pic1.p[y][x]=c; zed.p[y][x].dd=z; } x++;
    if (zed.p[y][x].dd>=z){ pic1.p[y][x]=c; zed.p[y][x].dd=z; } y++;
    if (zed.p[y][x].dd>=z){ pic1.p[y][x]=c; zed.p[y][x].dd=z; } x--;
    if (zed.p[y][x].dd>=z){ pic1.p[y][x]=c; zed.p[y][x].dd=z; } y--;

    w=h+(s<<8);                 // get histogram index for this color
    i=((pic1.ys-150)*his[w])/n;
    c.db[picture::_v]=255;      // histogram brighter
    for (;(i>0)&&(y>0);i--,y--)
        {
        if (zed.p[y][x].dd>=z){ pic1.p[y][x]=c; zed.p[y][x].dd=z; } x++;
        if (zed.p[y][x].dd>=z){ pic1.p[y][x]=c; zed.p[y][x].dd=z; } y++;
        if (zed.p[y][x].dd>=z){ pic1.p[y][x]=c; zed.p[y][x].dd=z; } x--;
        if (zed.p[y][x].dd>=z){ pic1.p[y][x]=c; zed.p[y][x].dd=z; } y--;
        }
    }
pic1.hsv2rgb();                 // convert to RGB to see correct colors
  • input image is pic0 (rose), output image is pic1 (histogram graph)
  • pic2 is the pic0 converted to HSV for histogram computation
  • zed is the Zed buffer for 3D display avoiding Z sorting ...

I use my own picture class for images so some members are:

  • xs,ys size of image in pixels
  • p[y][x].dd is pixel at (x,y) position as 32 bit integer type
  • clear(color) - clears entire image
  • resize(xs,ys) - resizes image to new resolution
  • rgb2hsv() and hsv2rgb() ... guess what it does :)

[edit1] your 2D histogram

It looks like you have color coded into 2D array. One axis is H and second is S. So you need to calculate H,S value from array address. If it is linear then for HSV[i][j]:

  • H=h0+(h1-h0)*i/maxi
  • S=s0+(s1-s0)*j/maxj
  • or i,j reversed
  • h0,h1,s0,s1 are the color ranges
  • maxi,maxj are the array size

As you can see you also discard V like me so now you have H,S for each cell in histogram 2D array. Where probability is the cell value. Now if you want to draw an image you need to know how to output this (as a 2D graph, 3D, mapping,...). For unsorted 2D graph draw graph where:

  • x=i+maj*i
  • y=HSV[i][j]
  • color=(H,S,V=200);

If you want to sort it then just compute the x axis differently or loop the 2D array in sort order and x just increment

[edit2] update of code and some images

I have repaired the C++ code above (wrong Z value sign, changed Z buffer condition and added bigger points for nicer output). Your 2D array colors can be as this:

HS colors

Where one axis/index is H, the other S and Value is fixed (I choose 200). If your axises are swapped then just mirror it by y=x I think ...

The color sorting is really just an order in which you pick all the colors from array. for example:

v=200; x=0;
for (h=0;h<256;h++)
 for (s=0;s<256;s++,x++)
 {
 y=HSV[h][s];
 // here draw line (x,0)->(x,y) by color hsv2rgb(h,s,v); 
 }

This is the incrementing way. You can compute x from H,S instead to achieve different sorting or swap the fors (x++ must be in the inner loop)

If you want RGB histogram plot instead see:

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • @rayryeng thx, but if added some better scaling like discard the most occurent background color it will be even better. also the dots should be more then 1 pixel to cover the holes and may be logarithimic y will be even better ... – Spektre Mar 27 '15 at 07:48
  • When you say "if you want to sort it then just compute the x axis differently" is cv::sort(Hist, Hist, CV_SORT_DESCENDING); what you meant? http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#sort or should i be using http://stackoverflow.com/questions/17831753/sorting-cvmat-in-opencv several times? EDIT : I've been good results. However, it's still a bit off. http://i.imgur.com/WElGR4l.png http://i.imgur.com/fmlivjo.png – Zypps987 Mar 27 '15 at 13:50
  • @Zypps987 you can not sort the array by that function (unless you have also color information inside) the sorting in this case is just a sorted way/order how pick the cells from the 2D array and present them ... No I ma not active in CV field (I did some commercial projects but I am no expert in the field) The output image you linked is not histogram ... histogram has no visual reference to the source image yours looks like filtered/transformed image instead of histogram – Spektre Mar 27 '15 at 17:45
  • I used the inverted 2d histogram from Luv to suppress the background and enhance the foreground (or at least tried too...). This is a short paper, but the output images might make clear what I am trying to do. I also need to implement or find a function in opencv that can calculate the euclidian distance within a Mat. http://breckon.eu/toby/publications/papers/sokalski10uavsalient.pdf – Zypps987 Mar 27 '15 at 23:34
  • http://i.imgur.com/jD6FQUb.png Is the result I'm getting thus far using only the hue of the hsv (now a 1d histogram). I'm thinking that I should only sort the first 2-3 bins now... but it's still coming out inverted where black is suppressed and white is enhanced. – Zypps987 Mar 31 '15 at 02:19
  • @Zypps987 I do not see any histograms at the images there histogram is a graph of a function not image !!! what I see is source image in RGB in left bottom, left top seems like HSV image viewed as RGB and the right side is just some kind of filter not a histogram either (still the image features are visible there) – Spektre Mar 31 '15 at 06:24
  • I disabled the window showing the histogram but it is there... The bottom right is a 2 level gauss pyramid of Luv that's down sampled / up sampled to the original input size. What I'm trying to do is take the histogram of Hue and Saturation of the HSV mat, invert it, and use these inverted probabilities of Hue and Saturation to suppress the background / enhance foreground in Luv. The paper I listed above uses Hue and Saturation so my 1d histogram of Hue only is just a cheap bandaid solution and not the real solution.... http://pastebin.com/QEPfJryj Please help I want to learn how to fix this – Zypps987 Apr 01 '15 at 15:39
  • @MarkSetchell added link to similar RGB histogram QA at the end if youre interested. – Spektre Mar 29 '17 at 09:26
  • You are a star - thank you for the heads-up! I'm always interesred. – Mark Setchell Mar 29 '17 at 09:30