Analyse Image Colors using PHP

December 4th, 2011 | Posted by ChrisG - (3 Comments)

I was asked to develop a search extension for open cart that would allow customers to search product images by color. This is similar to the search by color that Google images uses.

In order to make this extension work I needed an algorithm that would analyse the colors in an image and match it to the closest color in a set of colour swatches such as those shown in the Advanced Search image below.

Color Swatch for searching images by color

The basic program I developed uses the PHP GD library to process the images and follows these general steps.

  1. Define the colors in the color swatches
  2. Create an image object using the image that is to be analysed, resizing the image if it is too large
  3. Create an image object and load the colors from the the color swatches into image palette. This is the palette that a GIF image uses. This is the comparison palette that the colors in the image being analysed will be compared to.
  4. Loop through all the pixels in the image and tally it against the closest match to the pixel in the comparison palette.
  5. Sort the tally results in descending order
  6. Display the top results

There are 2 files here. The ColorCompare does all the work while the file following demonstrates the program by analysing some test images.

<?php

// Filename: ColorCompare.php
// Copyright (C) 2011 Chris Gosling
// Licence: GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html

class ColorCompare
{
    // max width and height for testing and resampling
    static private $resize_dim = 512;

    // define colour swatches
    static public $swatches = array(    
        "WHITE"             =>     "FFFFFF",
        "CREAM"             =>     "F2EAC6",
        "YELLOW"            =>    "F8E275",
        "ORANGE"            =>    "FA9A4D",
        "PEACH"             =>    "F9A986",    
        "PINK"              =>    "FAACA8",
        "MANGENTA"          =>    "FC6D99",
        "RED"               =>    "FF0000",
        "BURGANDY"          =>    "AC2424",
        "PURPLE"            =>    "A746B1",
        "LAVENDER"          =>    "C791F1",
        "LIGHT_BLUE"        =>    "A4A6FD",
        "DARK_BLUE"         =>    "1D329D",
        "TURQUIOSE"         =>    "2CCACD",
        "AQUA"              =>    "9CD8F4",
        "DARK_GREEN"        =>    "62854F",
        "LIGHT_GREEN"       =>    "A9CB6C",
        "TAN"               =>    "CAB775",
        "BROWN"             =>    "815B10",
        "GRAY"              =>    "777777",
        "BLACK"             =>    "000000"
    );

    // this will be the swatch index for each color        
    // eg: $swatch_index[0] = "WHITE" and so on  
    static private $swatch_index;

    // this image contains the palette the test image will be compared to 
    static private $comp_palette = false;        

    // percentage that colour needs to reach of total
    // pixels for colour to be considered significant
    static public $threshold_filter = 5;

    // ------------------------------------------------------
    // ACCEPTS: max number of colours to return
    //                     filename of image
    //                    comparison method id
    //                        1 = resize method
    // RETURNS: array (up to max_colors) containing indexed with the color
    //                        name and
    //                    the value will be the pixel count in order of highest to
    //                        lowest pixels
    //                    eg: "TAN" => 1000, "PINK" => 800, "MAGENTA" => 600
    //                    or false if failed
    static public function compare($max_colors, $filename)
    {
        $tally = array();

        // size image to something managable (256 x 256)
        $image_data = getimagesize($filename);

        // if small image then use its current size
        if ($image_data[0] < self::$resize_dim
            && $image_data[1] < self::$resize_dim)
        {
            $image = self::createImage($filename, $image_data[2]);    
            $width = $image_data[0];
            $height = $image_data[1];
        }    
        else     // resize the image
        {
            $res = self::createResizedImage($filename, $image_data[0],
                $image_data[1], $image_data[2]);

            if ($res == false)
            {
                print "[failed on resize]";
                return false;
            }    
            else
            {
                $image = $res[0];
                $width = $res[1];
                $height = $res[2];
            }                    
        }    

        // create the comparison palette
        self::createComparisonPalette();

        // loop through x axis
        for ($x = 0; $x < $width; $x++)
        {        
            // loop through y axis
            for ($y = 0; $y < $height; $y++)
            {        
                // compare to find colest match and tally
                list($red, $green, $blue) = self::getRGBFromPixel($image, $x, $y);
                $index = imagecolorclosest(self::$comp_palette, $red, $green, $blue);
                $tally[$index] = $tally[$index] + 1;
            }    
        }

        // sort the tally results
        arsort($tally);
        $ret_array = array();        
        $i = 0;    
        $threshold = ($width * $height) * (self::$threshold_filter / 100);

        // build the return array of the top results
        foreach($tally as $index => $count)
        {
            // make sure the count is high enough to be considered significant
            if ($count >= $threshold)
            {
                $ret_array[self::$swatch_index[$index]] = $count;
                $i++;
            }
            else
                break;    

            if ($i >= $max_colors)
                break;
        }

        return $ret_array;
    }

    // --------------------------------------------------------
    // ACCEPTS: image resource
    //                    x/y coordinate
    // RETURNS: array contain red, green, blue value of pixel    
    static private function getRGBFromPixel($image, $x, $y)
    {
        $rgb = imagecolorat($image, $x, $y);
        $red = ($rgb >> 16) & 0xFF;
        $green = ($rgb >> 8) & 0xFF;
        $blue = $rgb & 0xFF;
        return array ($red, $green, $blue);
    }    

    // -------------------------------------------------------
    // Creates the comparison palette if not already created
    static private function createComparisonPalette()
    {
        if (self::$comp_palette === false)
        {
            $swatch_index = array();
            self::$comp_palette = imagecreate(16, 16);

            foreach(self::$swatches as $name => $hex)
            {
                $color = self::hexToRGB($hex);
                $index = imagecolorallocate(self::$comp_palette,
                    $color['red'], $color['green'], $color['blue']);
                self::$swatch_index[$index] = $name;
            }
        }
    }

    // ------------------------------------------------------
    // ACCEPTS: hex color value without the # (eg: FF0000)
    // RETURNS: associative array with values for ref, green and blue
    static private function hexToRGB($hexStr)
    {
        $colorVal = hexdec($hexStr);
        $rgbArray['red'] = 0xFF & ($colorVal >> 0x10);
        $rgbArray['green'] = 0xFF & ($colorVal >> 0x8);
        $rgbArray['blue'] = 0xFF & $colorVal;
        return $rgbArray;
    }

    // ------------------------------------------------------
    // ACCEPTS: filename of input image,
    //                    original width and height of image
    //                    type of image
    // RETURNS: resized image or false if failed
    static private function createResizedImage($filename, $width, $height, $type)
    {        
        // create image from file
        $image = self::createImage($filename, $type);

        if (!$image)
            return false;

        //calculate dimensions based on smallest size
        $new_width = $width < self::$resize_dim ? $width : self::$resize_dim;
        $new_height = $height < self::$resize_dim ? $height : self::$resize_dim;

        // create resampled image
        $new_image = imagecreatetruecolor($new_width, $new_height);

        if ($new_image === false)
            return false;

        if (imagecopyresampled($new_image, $image, 0, 0, 0, 0, $new_width,
            $new_height, $width, $height))
            return array($new_image, $new_width, $new_height);
        else
            return false;
    }

    // ---------------------------------------------------
    // Creates an image object for the supplied image file and type
    // ACCEPTS: the filename of the image and the image type
    // RETURNS: image object
    static private function createImage($filename, $image_type)
    {
        // determine image type
        switch ($image_type)
        {
            case IMAGETYPE_GIF:
                $image = imagecreatefromgif($filename);
                break;

            case IMAGETYPE_JPEG:
                $image = imagecreatefromjpeg($filename);
                break;

            case IMAGETYPE_PNG:
                $image = imagecreatefrompng($filename);
                break;

            default:
                return false;    
        }    

        return $image;
    }
}    

?>

 

This is the code to demonstrate the ColorCompare class

<?php

date_default_timezone_set("Australia/Sydney");
require_once("ColorCompare.php");

// files to be tested
$filenames = array(    
    "test1.png",
    "test2.png",
    "test3.png",
    "test4.png",
    "test5.png",
    "test6.png");

// max colors
$max_colors = 5;

// display the palette
print "<table width='600' border='1'><tr>
    <td colspan=\"2\"><b>Color Swatches</b></td></tr>";

foreach (ColorCompare::$swatches as $name => $color)
    print "<tr><td width='50%'>$name</td><td bgcolor='#$color'>&nbsp;</td></tr>\n";

print "</table><p><b>Test Images</b></p>
    <p>Max Colors = $max_colors
    <br>Threshold Filter = ".ColorCompare::$threshold_filter."%</p>";    

// loop through each test pattern
for ($i = 0; $i < count($filenames); $i++)
{
    print "<table><tr><td><img src='{$filenames[$i]}'></td>\n";
    $result = ColorCompare::compare($max_colors, $filenames[$i]);
    print "<td><table width='200' border='1'>";

    if ($result == false)
        print "<tr><td>[failed]</td></tr>\n";
    else
    {    
        print "<tr><td><b>Color</b></td><td><b>Pixel Count</b></td></tr>";

        foreach ($result as $color => $count)
        {
            print "<tr><td width='50' bgcolor='#".ColorCompare::$swatches[$color]
                ."'>&nbsp;</td><td>".$count."</td></tr>\n";
        }        
    }        

    print "</table></td></tr></table>\n";    
}

?>

 

This is output from the above code.

Color Swatches
WHITE
CREAM
YELLOW
ORANGE
PEACH
PINK
MANGENTA
RED
BURGANDY
PURPLE
LAVENDER
LIGHT_BLUE
DARK_BLUE
TURQUIOSE
AQUA
DARK_GREEN
LIGHT_GREEN
TAN
BROWN
GRAY
BLACK

Test Images

Max Colors = 5

Threshold Filter = 5%

Analyse image colours
Color Pixel Count
61307
12720
9704
8631
8065

 

Analyse image colours
Color Pixel Count
31241
19689
10129
6974
6122

 

Analyse image colours
Color Pixel Count
18659
16575
12235
4525

 

Analyse image colours
Color Pixel Count
37866
32480
15940
5640

 

Analyse image colours
Color Pixel Count
113067
24583
22371

 

Color Pixel Count
52907
24062
9368

 

 How to improve the analysis results and create a color search function

The code above does the basic color analysis. To build a color search program you’ll need to store the analysed data in a database against the image. I also discovered through a bit experimentation that you get better results if each color in the color swatches is defined by multiple colors.

This image shows part of the color definition interface. The color below the name is the color that is displayed in the color swatch. The multiple colors on the right are loaded into the color palette and associate with the color name on the left.

Multiple color definitions

 

This is part of the DB diagram showing how the color swatches (color names) are associate with colors and the images.

  • cc_color_name holds the name of the colors in the swatches
  • cc_color are the individual colors that define the color in cc_color_name
  • cc_product_color_name is where the data from the analysed images is stored

Color Compare Database Diagram

 

Some tips for calibrating the color swatches

  • You will also need to spend time selecting the colors for the color swatches and tweaking them.
  • Make sure that colors from one color name don’t overlap the colors in another.
  • You may need to experiment picking whites, blacks and greys as they can interfere with the identification of other colors.

 

Download the zip file containing the php code and test images here

3 Responses



Leave a Reply