Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Rounding" colour values to the nearest of a small set of colours

Preamble

As a part of a project I'm working on I am trying to provide a convenient way to search for images in our system. We currently provide searching by various types of user added metadata (e.g. title, description, keywords) and by various metadata which we extract (e.g. EXIF, IPTC, XMP, etc). I would also like to add a "colour search" similar to what you can see in google's image search.

The project uses PHP and we can use the Imagemagick extension to segment and quantize the image and extract the most "significant" colours from the image; I'm not entirely certain of the results I'm getting here, but they seem reasonably accurate and certainly better than nothing.

The Problem

The bit that I'm having difficulty on is converting these significant colours into a set of representative colours, e.g. when you look at Google's image search there is a set of 12 colours there. I'd like to mathematically "round" my colour value to the nearest representative colour, so that I can index the image with the colours that I detect and then facet my search results that way.

Any suggestions?

like image 975
El Yobo Avatar asked Oct 30 '10 06:10

El Yobo


People also ask

What does RGB 255 255 255 mean?

RGB Colors. All colors on a computer are made up by combining the light from three colors (red, blue, and green). Black is [0,0,0], and White is [255, 255, 255]; Gray is any [x,x,x] where all the numbers are the same.

What do RGB values mean?

The first and second digits represent the red level; the third and fourth digits represent the green level; the fifth and sixth digits represent the blue level. In order to actually display the colors for all possible values, the computer display system must have 24 bits to describe the color in each pixel.

How many colors are in RGB?

RGB Color Values Each parameter (red, green, and blue) defines the intensity of the color with a value between 0 and 255. This means that there are 256 x 256 x 256 = 16777216 possible colors!

Why 255 is white?

The most common pixel format is the byte image, where this number is stored as an 8-bit integer giving a range of possible values from 0 to 255. Typically zero is taken to be black, and 255 is taken to be white.


2 Answers

The first step would be to define the colors you want to compare to.

The second step is to find the smallest distance from your color to one of the colors you chose in the previous step. In order to be able to measure that distance you need an Euclidian space in which to model colors.

Of course the simple choice would be the RGB space

alt text

And the distance between two colors C1(r1, g1, b1) and C2(r2, g2, b2) would be

sqrt( (r1 - r2)2 + (g1 - g2)2 + (b1 - b2)2 ).

But if you need more precision it would be better to use the Hue-Chroma-Lightness bicone space, a derivative of the HSL cylinder.

alt text

In the RGB space things were straight forward as R, G and B where each on a separate axis. In HCL we need to compute the coordinates on each of the axis.

First of all we compute the chroma (which is a bit different from saturation) as:

Chroma = max(Red, Green, Blue) - min(Red, Green, Blue)

Then we normalize our H, C and L value so that H goes from 0 to 2 (to cover a circle if we multiply by PI and take radians as the unit), C goes from 0 to 1 (the radius of the trigonometric circle) and L goes from -1 (Black) to 1 (White).

Next we take z = L without any transformations as it is clear from the image that it goes along the vertical axis.

We can easily observe that for a color, Chroma is the distance from the z axis and Hue is the angle. So we get

x = C * cos(H*PI) and
y = C * sin(H*PI)

At this point x, y and z will all be in [-1, 1] and the distance between two colors will be, using the same formula as above,

sqrt( (x1 - x2)2 + (y1 - y2)2 + (z1 - z2)2 ).


To get even more precision and find the closest color according to human color perception you could use the CIE-L*ab modeling space and compute the distance with one of these algorithms. The principles are the same as for the two cases presented above, only the algorithms are more complex.


Update (7 years later)

Finally xkcd featured a comic that I can use in this post!

enter image description here

https://xkcd.com/1882/

like image 111
Alin Purcaru Avatar answered Sep 21 '22 23:09

Alin Purcaru


function getSimilarColors (color) {     var base_colors=["660000","990000","cc0000","cc3333","ea4c88","993399","663399","333399","0066cc","0099cc","66cccc","77cc33","669900","336600","666600","999900","cccc33","ffff00","ffcc33","ff9900","ff6600","cc6633","996633","663300","000000","999999","cccccc","ffffff"];      //Convert to RGB, then R, G, B     var color_rgb = hex2rgb(color);     var color_r = color_rgb.split(',')[0];     var color_g = color_rgb.split(',')[1];     var color_b = color_rgb.split(',')[2];      //Create an emtyp array for the difference betwwen the colors     var differenceArray=[];      //Function to find the smallest value in an array     Array.min = function( array ){            return Math.min.apply( Math, array );     };       //Convert the HEX color in the array to RGB colors, split them up to R-G-B, then find out the difference between the "color" and the colors in the array     $.each(base_colors, function(index, value) {          var base_color_rgb = hex2rgb(value);         var base_colors_r = base_color_rgb.split(',')[0];         var base_colors_g = base_color_rgb.split(',')[1];         var base_colors_b = base_color_rgb.split(',')[2];          //Add the difference to the differenceArray         differenceArray.push(Math.sqrt((color_r-base_colors_r)*(color_r-base_colors_r)+(color_g-base_colors_g)*(color_g-base_colors_g)+(color_b-base_colors_b)*(color_b-base_colors_b)));     });      //Get the lowest number from the differenceArray     var lowest = Array.min(differenceArray);      //Get the index for that lowest number     var index = differenceArray.indexOf(lowest);      //Function to convert HEX to RGB     function hex2rgb( colour ) {         var r,g,b;         if ( colour.charAt(0) == '#' ) {             colour = colour.substr(1);         }          r = colour.charAt(0) + colour.charAt(1);         g = colour.charAt(2) + colour.charAt(3);         b = colour.charAt(4) + colour.charAt(5);          r = parseInt( r,16 );         g = parseInt( g,16 );         b = parseInt( b ,16);         return r+','+g+','+b;     }      //Return the HEX code     return base_colors[index]; } 
like image 30
passatgt Avatar answered Sep 23 '22 23:09

passatgt