laravelalgorithmcolorsdetectioncolor-detection

Detect unique colors in an image visible to eye


Aoa,

I am currently working on a project and got stuck.Actually i want an algorithm to detect number of colors in an image that are visible to human eye. I used some packages over the internet but they are providing even small color changes.

Example : The below provided image has two visible colors but the package I use showing 4 or more than 4 and if some package gives accurate color for this image than it shows wrong count for image having multiple colors. Example Image i used

Some Packages that I used

http://www.coolphptools.com/color_extract#demo

https://github.com/brianmcdo/ImagePalette


Solution

  • Not a perfect implementation (this is going to be slow, particularly on big images), but we could do something like:

    $resource = imagecreatefrompng("test.png");  // Load our image (use imagecreatefromjpeg if using jpeg)
    $width = imagesx($resource); // Get image width
    $height = imagesy($resource); // Get image height
    $results = []; // Create empty array to hold our "unique" color results
    
    $tolerance = 5; // The amount of variation allowed to consider a color the same
    
    // Loop each pixel in the image
    for($x = 0; $x < $width; $x++) {
      for($y = 0; $y < $height; $y++) {
    
        $add = true;
        $color_index = imagecolorat($resource, $x, $y);
        $color = imagecolorsforindex($resource, $color_index); // Get the color as an array
    
        // Push the first pixel into our results so we have something to start the comparison against
        if (count($results) == 0){
          $results[] = $color;
        } else {
          // Compare the current colour to our results using the tolerance
          foreach ($results as $i => &$result):
            $near_red = ($color['red'] > ($result['red'] - $tolerance)) && ($color['red'] < ($result['red'] + $tolerance));
            $near_green = ($color['green'] > ($result['green'] - $tolerance)) && ($color['green'] < ($result['green'] + $tolerance));
            $near_blue = ($color['blue'] > ($result['blue'] - $tolerance)) && ($color['blue'] < ($result['blue'] + $tolerance));
    
            if ($near_red && $near_green && $near_blue){
              // This colour is similar to another result
              $add = false;
            }
    
            if (!$add){
              break;
            }
          endforeach;
    
          if ($add){
            $results[] = $color;
          }
        }
      }
    }
    
    // Output the unique colors visually. You would probably just count($results) here
    foreach($results as $item):
      $color = $item['red'] . "," . $item['green'] . "," . $item['blue'] . "," . "1";
      echo "<div style='width: 10px; height: 10px; background-color: rgba($color); display: inline-block;'></div>";
    endforeach;
    

    Using this image (which contains 8 unique colors):

    image 1

    and a tolerance of 75 we get the an output of the following 4 colors:

    75

    With a tolerance of 10 we get the following 8 colors:

    10

    As we loop each pixel in the image we get the color values in RGBA format like:

    [
      "red"   => 255,
      "green" => 255,
      "blue"  => 255,
      "alpha" => 255,
    ]
    

    We can then compare the current pixel to each item in our $results array by adding and subtracting the tolerance and checking if the current channel (red, green and blue) falls within the range:

    $near_red = ($color['red'] > ($result['red'] - $tolerance)) && ($color['red'] < ($result['red'] + $tolerance));
    

    For example, if our current pixels red value is 50 and our tolerance is 5, we would check if there is an item in our $results with a red value between 45 and 55. We do the same for green and blue. If all 3 channels are within the range, it means we already have a similar color in our $results, so we don't add the current pixel color to the $results array.

    A greater $tolerance value will match against less similar colors. A $tolerance value of 0 will check exact match and result in far more unique colors in our $results.

    If a similar value doesn't exist, we push the current pixel RGBA value to our $results array.

    Once the code is finished running, the $results array will contain the "unique" colors. We can count the colors like so:

    echo count($results);
    

    This solution could use a lot of optimisation (it will be very slow on large images with a lot of unique colors), and I've ignored the alpha channel in this implementation (easy enough to add in if needed) but hopefully it's useful to you.

    The worst case complexity of this implementation is

    O(n2)
    which would happen if every pixel color in the image is unique, which would cause our $results array to contain every previous value so each iteration would contain a nested loop of the number of pixels in the image (more or less), which for a large image would be incredibly slow.