0

Let's say I have the following image:

enter image description here

What I am looking for is a way to programatically determine that red is the most common color in the picture.

So far, I have tried a few approaches, with various results of poor to abysmal to accomplish this. My current approach is to first reduce the colors in the image.

enter image description here

This is done with the following code:

Mat samples(src.rows * src.cols, 3, CV_32F);
for( int y = 0; y < src.rows; y++ )
for( int x = 0; x < src.cols; x++ )
for( int z = 0; z < 3; z++)
samples.at<float>(y + x * src.rows, z) = src.at<Vec3b>(y,x)[z];

int clusterCount = 16;
Mat labels;
int attempts = 2;
Mat centers;
kmeans(samples, clusterCount, labels, TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 10000, 0.0001), attempts, KMEANS_PP_CENTERS, centers );

Mat reduced( src.size(), src.type() );
for( int y = 0; y < src.rows; y++ )
for( int x = 0; x < src.cols; x++ )
{
    int cluster_idx = labels.at<int>(y + x * src.rows,0);
    reduced.at<Vec3b>(y,x)[0] = centers.at<float>(cluster_idx, 0);
    reduced.at<Vec3b>(y,x)[1] = centers.at<float>(cluster_idx, 1);
    reduced.at<Vec3b>(y,x)[2] = centers.at<float>(cluster_idx, 2);
}

There is one annoying issue with it where it has an issue with scaling that leaves out a portion of the right, but I can live with that for now.

Next, I tried a few approaches where I want to get the colors mapped out, such as with histograms.

Mat image_hsv;

cvtColor(src, image_hsv, CV_BGR2HSV);

// Quanta Ratio
int scale = 10;

int hbins = 36, sbins = 25, vbins = 25;
int histSize[] = {hbins, sbins, vbins};

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

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

int channels[] = {0, 1, 2};

calcHist( &image_hsv, 1, channels, Mat(), // do not use mask
         hist, 3, histSize, ranges,
         true, // the histogram is uniform
         false );

int maxVal = 0;

int hue = 0;
int saturation = 0;
int value = 0;

for( int h = 0; h < hbins; h++ )
for( int s = 0; s < sbins; s++ )
for( int v = 0; v < vbins; v++ )
{
    int binVal = hist.at<int>(h, s, v);
    if(binVal > maxVal)
    {
        maxVal = binVal;

        hue = h;
        saturation = s;
        value = v;
    }
}

hue = hue * scale * scale; // angle 0 - 360
saturation = saturation * scale; // 0 - 255
value = value * scale; // 0 - 255

The problem is, for this image I get the following values:

  • Hue: 240
  • Saturation: 0
  • Value: 0

However, I am expecting HSV values closer to this:

  • Hue: 356
  • Saturation: 94
  • Value: 58

Hopefully, someone can point out where I went wrong.

CodeBender
  • 35,668
  • 12
  • 125
  • 132
  • too lazy to analyze your code but just a hint to test do you handle your image pixel format properly? My bet is you got RGB and handle it as BGR or vice versa somewhere as hue 240 is blue instead of red (I am used to that GDI/Canvas has usually reverse order of channels than raw image data for some pixel formats) But the S,V set to zero is weird indeed. Also Take a look at this: [HSV histogram](https://stackoverflow.com/a/29286584/2521214) – Spektre Oct 10 '17 at 07:06
  • Just curious, how about computing the color histogram using [calcHist](https://docs.opencv.org/2.4/modules/imgproc/doc/histograms.html#calchist) and finding the peak? Of course, not the answer to your question with HSV values. calcHist will be much faster than kmeans. – dhanushka Oct 10 '17 at 12:12
  • @dhanushka the speed depends on the color histogram implementation, number of bins etc ... so it might nor be necessarily faster than k-means with the same precision. But yes I would use Histogram too , that is why I suggest the link in previous comment as There is My C++ implementation that does RGB -> HSV conversion ,compute and render HSV histogram (no openCV) – Spektre Oct 11 '17 at 07:42

1 Answers1

0

This is indeed a classical problem in computer graphics. Just finding literally the most frequent color wrt histogram is not a good idea, surprisingly you may discover that it's a shade of yellow/green rather than the red, because the values of "red" contain variations and almost never fall in the same histogram bin.

A proper algorithm is based on the least squares. You need to find a color whose sum of squared "distances" is minimal. You may define your square distance as dr^2+dg^2+db^2 (r/b/g stand for red/green/blue components), or you may play with weights to reflect the different sensitivity to those components. You may also represent it in different bases (i.e. yuv or etc.)

Please write in comments if you need help in implementing least squares.

Update:

The solution of the least squares in your case is just the average value level with weights given by the histogram.

result = sum(n * hist[n]) / sum(hist[n]).

Note that this formula holds for each color component, i.e. they are independent.

valdo
  • 12,632
  • 2
  • 37
  • 67