8

I'm trying to come up with a script that will programmatically run through an image and tell me it's primary color(s).

Currently the script gets the RGB value of each pixel. Compares them against predefined rules and attempts to count up the number of pixels of each colour.

My problem is the script is a little hit and miss. Does anyone know of a better way of doing this (maybe using a different colour coding system that's easier to translate to english) or an existing set of rules defining colours via their RGB?

<?php
$file = "8629.jpg";

$colors = array("Red" => array("rel" => true, "r" => 0.65, "g" => 0.09, "b" => 0.25, "var" => 0.3),
                "Blue" => array("rel" => true, "r" => 0.21, "g" => 0.32, "b" => 0.46, "var" => 0.3),
                "Green" => array("rel" => true, "r" => 0, "g" => 0.67,"b" =>  0.33, "var" => 0.3),
                "Black" => array("rel" => false, "r" => 0, "g" => 0,"b" =>  0, "var" => 30),
                "White" => array("rel" => false, "r" => 255, "g" => 255,"b" =>  255, "var" => 30));                 

$total = 0;

$im = imagecreatefromjpeg($file);
$size = getimagesize($file);

if (!$im) {
    exit("No image found.");
}

for ($x = 1; $x <= $size[0]; $x++) {
    for($y = 1; $y <= $size[1]; $y++) {
        $rgb = imagecolorat($im, $x, $y);
        $r = ($rgb >> 16) & 0xFF;
        $g = ($rgb >> 8) & 0xFF;
        $b = $rgb & 0xFF;

        $colorTotal = $r + $g + $b;

        $rRatio = $r > 0 ? $r / $colorTotal : 0;
        $gRatio = $g > 0 ? $g / $colorTotal : 0;
        $bRatio = $b > 0 ? $b / $colorTotal : 0;

        foreach($colors as $key => $color) {
            if ($color["rel"]) {
                if ((($color["r"] - $color["var"]) <= $rRatio && $rRatio <= ($color["r"] + $color["var"])) &&
                    (($color["g"] - $color["var"]) <= $gRatio && $gRatio <= ($color["g"] + $color["var"])) &&
                    (($color["b"] - $color["var"]) <= $bRatio && $bRatio <= ($color["b"] + $color["var"]))) {

                    $colourCount[$key]++;
                    $total++;
                }
            } else {
                if ((($color["r"] - $color["var"]) <= $r && $r <= ($color["r"] + $color["var"])) &&
                    (($color["g"] - $color["var"]) <= $g && $g <= ($color["g"] + $color["var"])) &&
                    (($color["b"] - $color["var"]) <= $b && $b <= ($color["b"] + $color["var"]))) {

                    $colourCount[$key]++;
                    $total++;
                }
            }
        }
    }
}

var_dump($colourCount);

foreach($colourCount as $key => $color) {
    $colourPrecent[$key] = $color / $total;
}

arsort($colourPrecent);
var_dump($colourPrecent);

foreach($colourPrecent as $key => $color) {
    if ($prevVal) {
        if ($color < ($prevVal - 0.1)) {
            break;
        }
    }

    $primary[] = $key;
    $prevVal = $color;
}

echo("The primary colours in this image are " . implode(" and ", $primary));

?>
Dan
  • 2,212
  • 20
  • 29
  • 2
    Hmm, interesting. I'm afraid my only contribution is [this warning](http://www.thedoghousediaries.com/?p=1406) about defining "human-readable" colours! – Widor Sep 21 '11 at 14:46
  • Considering that a truecolor image could potentially have every pixel being unique, you're not going to gain much unless you start "batching" similar colors together. Perhaps it'd be simpler to just shrink the image to a 1x1 pixel and figure out its predominant color. – Marc B Sep 21 '11 at 14:46
  • RGB seems like the [least useful choice of color model here](http://www.wowarea.com/english/help/color.htm). @Widor, surely the script will take an IsGuy parameter. ;) – bzlm Sep 21 '11 at 14:48
  • The script already batches colours together. For example this image: http://www.techmynd.com/wp-content/uploads/2009/11/windows-seven-wallpaper-39.jpg gives the results: ('Blue' => int 1451915 'White' => int 46988 'Green' => int 313499 'Black' => int 69763 'Red' => int 5377) – Dan Sep 21 '11 at 14:52
  • @bzlm - LAB looks like it would be much easier to work with since there's essentially only 2 colour values and one luminosity. Now I just need to figure out how to convert from RGB to LAB... – Dan Sep 21 '11 at 15:04
  • @Dan: [Useful color equations: RGB to LAB converter](http://cookbooks.adobe.com/post_Useful_color_equations__RGB_to_LAB_converter-14227.html) Looks like its in Javascript, but you can convert the algorithm to PHP. – Herbert Sep 22 '11 at 08:45
  • 1
    It seems to me that [HSL](http://130.113.54.154/~monger/hsl-rgb.html) would be the most useful model because you can separate the Hue from the Saturation and Luminance. H: 0-120 = red, 120-240 = green, 240-360 = blue. L: 0% = black, L: 100% S: 100% = white. – Herbert Sep 22 '11 at 08:51
  • Nothing is going to be easier than another ... you may have simpler processing because you have less variables but in the end, the only hard part here is getting a good human readable color list, no matter the color encoding -- – Morg. Sep 26 '11 at 10:19
  • @Herbert I went with HSL and pretty much have the script working now. Check S and L for grays, black, white. H for the color. It's telling me with reasonable accuracy the percentage of each color in an image. Thanks for the input everyone. – Dan Sep 26 '11 at 12:45
  • 1
    Glad I could help Dan. I've done some work with a variety of color models. HSL/HSV is a more natural way of thinking about color components than RGB. HSL also has the add advantage that you can use it to convert colors to their graysale equivalent, and even calculate _related_ and complimentary colors. Try that with RGB. :-) – Herbert Sep 26 '11 at 13:20

2 Answers2

2

Solution was to convert the RGB to HSL as suggested by Herbert. Function for converting to human still needs a little tweaking / finishing off but here it is:

function hslToHuman($h, $s, $l) {

$colors = array();

// Gray
if ($s <= 10 && (9 <= $l && $l <= 90)) {
    $colors[] = "gray";
}

$l_var = $s / 16;

// White
$white_limit = 93;
if (($white_limit + $l_var) <= $l && $l <= 100) {
    $colors[] = "white";
}

// Black
$black_limit = 9;
if (0 <= $l && $l <= ($black_limit - $l_var)) {
    $colors[] = "black";
}

// If we have colorless colors stop here
if (sizeof($colors) > 0) {
    return $colors;
}

// Red
if (($h <= 8 || $h >= 346)) {
    $colors[] = "red";
}

// Orange && Brown
// TODO

// Yellow
if (40 <= $h && $h <= 65) {
    $colors[] = "yellow";
}

// Green
if (65 <= $h && $h <= 170) {
    $colors[] = "green";
}

// Blue
if (165 <= $h && $h <= 260) {
    $colors[] = "blue";
}

// Pink && Purple
// TODO

return $colors;
}
Dan
  • 2,212
  • 20
  • 29
1

Alright so you've got a graphics library, there must be an average thingy in there, you average your picture, take any pixel and tadaam you're done ?

And the simplest solution found on here is : resize to 1x1px, get colorat :

Get image color

After that it's pretty easy, find somewhere a detailed list of rgb to human readable (for example html colors), encode that as an array and use it in your script -> round() your r,g,b, vals to the precision of your data and retrieve the color.

You should determine what color granularity you want and go from there -> find your set of named colors (I think all 8bit colors have a name somewhere) and then reduce your rgb information to that - either by reducing color information of the image before reading it (faster) or by reducing color information at read time (more flexible in terms of color list.

Basic example of some rgb-human readable resources :

http://www.w3schools.com/html/html_colornames.asp

Others :

http://chir.ag/projects/name-that-color/#2B7C97

http://r0k.us/graphics/SIHwheel.html

Community
  • 1
  • 1
Morg.
  • 697
  • 5
  • 7
  • The hard part at the moment is finding that detailed list of RGB to human readable. The script also needs to be a bit more intelligent than just the average colour. If I've got an image thats 50/50 red/blue I need to know it's red and blue, not be told that it's purple. – Dan Sep 26 '11 at 07:54
  • The RGB list you can find anywhere on the web by searching for "HTML colors", you will get a subset with a specific granularity (more than enough for what you seem to want) with all named colors - added an example. About your multiple colors you could just do the following : resize to 4x4, foreach colorat, rank and test if it suits . if not try a better res. – Morg. Sep 26 '11 at 10:08
  • Ended up using HSL color space. It's much easier to define a color within a hue range than try to track 3 different variables and decide what color it is. Thanks for the input though. – Dan Sep 26 '11 at 12:48