Question

I hope you can help me.

I am taking a picture in Android, generating a thumbnail and, using this thumbnail, I analyze the pixels to get the most used tones.

I thought that using RGB would be hard to group them, so I turned every pixel color into HSV color. As you see, I use a Float3, so that hsv.x is the same as hsv.h, hsv.y is hsv.s and hsv.z equals hsv.v.

scores is a int[] array, and I store there the amount of pixels of each tone. This algorithm works pretty well when finding blue tones (aquamarine, light blue and dark blue), but it's hard to recognize monochromes (it moves them to the yellow score) and warm colors (it "confuses" orange with yellow). Also, as a last question, I don't know how to recognize brown (which is the last "big color" I think I missed. Here comes the algorithm:

for (Float3 hsv: hsvs) {
    if(hsv.y < 0.15)
    {
        if(hsv.z < 0.2)
            scores[BLACK]++;
        else if(hsv.z < 0.6)
            scores[GREY]++;
        else
            scores[WHITE]++;
    }
    else if (hsv.x < 15f || hsv.x > 345f)
        scores[RED]++;
    else if (hsv.x < 40)
        scores[ORANGE]++;
    else if (hsv.x < 70)
        scores[YELLOW]++;
    else if (hsv.x < 120)
        scores[GREEN]++;
    else if (hsv.x < 160)
        scores[AQUAMARINE]++;
    else if (hsv.x < 200)
        scores[LIGHT_BLUE]++;
    else if (hsv.x < 240)
        scores[DARK_BLUE]++;
    else if (hsv.x < 300)
        scores[PURPLE]++;
    else
        scores[PINK]++;
}

Do you think I can improve the algorithm by changing some numbers, or should I start with a different approach?

Edit:

Let me put you in context. My app takes a picture, grabs a square thumbnail (for practical reasons) and analyzes the resulting picture. Because it is a thumb, it loses some detail and dimensions (say I create 200x200px thumbs), so in my particular case, there will always be 40000 pixels. What I do with this image is to check the most used tones.

By most used, I define them as tones that appear in more than 3000 pixels (about 7.5% of the image). I do not rank them, I just check some checkboxes named after an arbitrary amount of colors (which will probably be the ones shown to you plus brown). Then, the user checks or unchecks the colors he considers appropriate. In fact, and as Geobits pointed out, this is a human problem and most used colors doesn't mean most relevant colors. That is why this tool doesn't need to be perfect, just to avoid failing at selecting colors that don't fit the pack at all.

Was it helpful?

Solution

If I were doing it, instead of making each pixel fall into one of several buckets, I'd see how far each hue was from each color. For instance, you could take the difference from each "target hue", and sum up all the differences. Whichever has the lowest total after looping through all the pixels should be the "most used". Of course it's not perfect, but naming colors is not a trivial task for a computer.

For example, to get the total for "green" (hue 120 in my arbitrary world):

float runningTotalForGreen = 0;
for(Float3 hsv: hsvs) 
{
    float diff = (hsv.x - 120) % 180; // mod 180 to normalize cw/ccw on the wheel
    runningTotalForGreen += diff; // lower = closer to green
}

You'll probably want to store your target colors and totals in arrays for easier looping, but that's the general idea.

Edit:

The reason I think this will work better than "buckets" is this: Consider a picture, where about 35% of the picture is red. The other 65% is somewhere on the border of light/dark blue. So, say 34% falls into the light blue bucket, 31% falls into dark blue. Your method says it's red, since 35% is greater than both. Using the hue difference will most likely return one of the blues.

Of course, most methods will have some image that it fails for. The key is finding the one that errors least. That depends a lot on what type of image it is. I do agree you'll need special handling for certain colors(black/white/brown/etc).


The main issue here is that it's a human problem. For my example image above, some people would say light blue. Some would say dark blue. Some might even say red, depending on how vivid/contrasting it is, especially if the blue was the background with red in the fore. Saying "this picture is x color" isn't ever consistent unless it's basically monochrome.

OTHER TIPS

Can't say whether this is a good approach or not. Check out this page on Color Names. When you click on the colors in the wheel it will give you the hue range it uses to define the color name.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top