3

I want to find color name , given its rgb value .

Example RGB value is : (237, 65, 83)

Predefined Values

array(11, 'Red', '#FF0000', '255,0,0'),

array(3, 'Brown', '#A52A2A', '165,42,42')

If i use this method distance calculation

i am getting color as brown .

But actual color is red if we test that rgb value here

Edited 1

<?php 

 $colors = array(
 array(1, 'Black', '#000000', '0,0,0'),
 array(2, 'Blue',  '#0000FF', '0,0,255'),
 array(3, 'Brown', '#A52A2A', '165,42,42'),    
 array(4, 'Cream', '#FFFFCC', '255,255,204'),   
 array(5, 'Green', '#008000', '0,128,0'),        
 array(6, 'Grey',  '#808080', '128,128,128'),
 array(7, 'Yellow', '#FFFF00', '255,255,0'),
 array(8, 'Orange', '#FFA500', '255,165,0'),          
 array(9, 'Pink', '#FFC0CB', '255,192,203'), 
 array(11, 'Red', '#FF0000', '255,0,0'),  
 array(10, 'Purple', '#800080', '128,0,128'),
 array(12, 'Tan', '#d2b48c', '210,180,140'),
 array(13, 'Turquoise', '#40E0D0', '64,224,208'),
 array(14, 'White', '#FFFFFF', '255,255,255')
   );



 $miDist = 99999999999999999 ;
 $loc = 0 ; 



 $findColor = RGBtoHSV(72, 70, 68);
 for( $i = 0 ; $i < 14 ; $i++){
  $string =  $colors[$i][3];
  $pieces = explode(',' , $string);
  $r0 = $pieces[0];
  $g0 = $pieces[1];
  $b0 = $pieces[2];

  $storedColor = RGBtoHSV($r0,$g0,$b0);

 echo $storedColor[0] ."-" . $storedColor[1] ;
 // distance between colors (regardless of intensity)


 $d = sqrt( ($storedColor[0]-$findColor[0])
      *($storedColor[0]-$findColor[0])
      + 
      ($storedColor[1]-$findColor[1])
      *($storedColor[1]-$findColor[1])



      );



  echo $colors[$i][1] ."=" .$d;
  //echo $d ;
     if( $miDist >= $d )
       {
       $miDist = $d;
       $loc = $i ;    
       } 
      echo "<br>" ;


 }

 echo $colors[$loc][1];









 function RGBtoHSV($R, $G, $B)    // RGB values:    0-255, 0-255, 0-255
 {                                // HSV values:    0-360, 0-100, 0-100
// Convert the RGB byte-values to percentages
$R = ($R / 255);
$G = ($G / 255);
$B = ($B / 255);

// Calculate a few basic values, the maximum value of R,G,B, the
//   minimum value, and the difference of the two (chroma).
$maxRGB = max($R, $G, $B);
$minRGB = min($R, $G, $B);
$chroma = $maxRGB - $minRGB;

// Value (also called Brightness) is the easiest component to calculate,
//   and is simply the highest value among the R,G,B components.
// We multiply by 100 to turn the decimal into a readable percent value.
$computedV = 100 * $maxRGB;

// Special case if hueless (equal parts RGB make black, white, or grays)
// Note that Hue is technically undefined when chroma is zero, as
//   attempting to calculate it would cause division by zero (see
//   below), so most applications simply substitute a Hue of zero.
// Saturation will always be zero in this case, see below for details.
if ($chroma == 0)
    return array(0, 0, $computedV);

// Saturation is also simple to compute, and is simply the chroma
//   over the Value (or Brightness)
// Again, multiplied by 100 to get a percentage.
$computedS = 100 * ($chroma / $maxRGB);

// Calculate Hue component
// Hue is calculated on the "chromacity plane", which is represented
//   as a 2D hexagon, divided into six 60-degree sectors. We calculate
//   the bisecting angle as a value 0 <= x < 6, that represents which
//   portion of which sector the line falls on.
if ($R == $minRGB)
    $h = 3 - (($G - $B) / $chroma);
elseif ($B == $minRGB)
    $h = 1 - (($R - $G) / $chroma);
else // $G == $minRGB
    $h = 5 - (($B - $R) / $chroma);

// After we have the sector position, we multiply it by the size of
//   each sector's arc (60 degrees) to obtain the angle in degrees.
$computedH = 60 * $h;

return array($computedH, $computedS, $computedV);
 }

 ?>
Community
  • 1
  • 1
usernan
  • 572
  • 1
  • 6
  • 23

1 Answers1

5

So if you want distance between 2 colors (r0,g0,b0) and (r1,g1,b1) to detect closest color regardless of its intensity (that is what base color means in this case) you should

  1. Normalize color vectors to common size
  2. Compute distance
  3. Scale the result back
// variables
int r0,g0,b0,c0;
int r1,g1,b1,c1,d;
// color sizes
c0=sqrt(r0*r0+g0*g0+b0*b0);
c1=sqrt(r1*r1+g1*g1+b1*b1);
// distance between normalized colors
d = sqrt((r0*c1-r1*c0)^2 + (g0*c1-g1*c0)^2 + (b0*c1-b1*c0)^2) / (c0*c1);

This approach will get unstable comparing dark colors so you can add simple condition like

if (c0<treshold)  color is dark 

And compare such color only agains the shades of gray or return unknown color. Our vision works similarly we can not safely recognize dark colors ...

Anyway HSV color space is much better for color comparison because it better resembles human color recognition. so convert RGB -> HSV and compute distance ignoring the value V which is the intensity of color...

In HSV you need to handle H as a periodic full circle value so the change can be only half of circle big. S tells you if it is color or grayscale which has to be handled separately and V is the intensity.

// variables
int h0,s0,v0;
int h1,s1,v1,d,q;
q=h1-h0;
if (q<-128) q+=256; // use shorter angle
if (q>+128) q-=256; // use shorter angle
         q*=q; d =q;
q=s1-s0; q*=q; d+=q;
if (s0<32)          // grayscales
    {
    d=0;            // ignore H,S
    if (s1>=32) continue; // compare only to gray-scales so ignore this color
    }
q=v1-v0; q*=q; d+=q;

Some things to compare ...

You should do a visual check of your source to actually see what is happening otherwise you will go in circles without any results. For example I just now coded this in C++/VCL/mine image class:

union color
    {
    DWORD dd; WORD dw[2]; byte db[4];
    int i; short int ii[2];
    color(){}; color(color& a){ *this=a; }; ~color(){}; color* operator = (const color *a) { dd=a->dd; return this; }; /*color* operator = (const color &a) { ...copy... return this; };*/
    };

    enum{ // this is inside my picture:: class
        _x=0,   // dw
        _y=1,

        _b=0,   // db
        _g=1,
        _r=2,
        _a=3,

        _v=0,   // db
        _s=1,
        _h=2,
        };

void rgb2hsv(color &c)
    {
    double r,g,b,min,max,del,h,s,v,dr,dg,db;
    r=c.db[picture::_r]; r/=255.0;
    g=c.db[picture::_g]; g/=255.0;
    b=c.db[picture::_b]; b/=255.0;
    min=r; if (min>g) min=g; if(min>b) min=b;
    max=r; if (max<g) max=g; if(max<b) max=b;
    del=max-min;
    v=max;
    if (del<=1e-10) { h=0; s=0; }   // grayscale
    else{
        s=del/max;
        dr=(((max-r)/6.0)+(del/2.0))/del;
        dg=(((max-g)/6.0)+(del/2.0))/del;
        db=(((max-b)/6.0)+(del/2.0))/del;
        if      (fabs(r-max)<1e-10) h=db-dg;
        else if (fabs(g-max)<1e-10) h=(1.0/3.0)+dr-db;
        else if (fabs(b-max)<1e-10) h=(2.0/3.0)+dg-dr;
        if (h<0.0) h+=1.0;
        if (h>1.0) h-=1.0;
        }
    c.db[picture::_h]=h*255.0;
    c.db[picture::_s]=s*255.0;
    c.db[picture::_v]=v*255.0;
    }

void hsv2rgb(color &c)
    {
    int i;
    double r,g,b,h,s,v,vh,v1,v2,v3,f;
    h=c.db[picture::_h]; h/=255.0;
    s=c.db[picture::_s]; s/=255.0;
    v=c.db[picture::_v]; v/=255.0;
    if (s<=1e-10) { r=v; g=v; b=v; }    // grayscale
    else{
        vh=h*6.0;
        if (vh>=6.0) vh=0.0;
        f=floor(vh); i=f;
        v1=v*(1.0-s);
        v2=v*(1.0-s*(    vh-f));
        v3=v*(1.0-s*(1.0-vh+f));
             if (i==0) { r=v ; g=v3; b=v1; }
        else if (i==1) { r=v2; g=v ; b=v1; }
        else if (i==2) { r=v1; g=v ; b=v3; }
        else if (i==3) { r=v1; g=v2; b=v ; }
        else if (i==4) { r=v3; g=v1; b=v ; }
        else           { r=v ; g=v1; b=v2; }
        }
    c.db[picture::_r]=r*255.0;
    c.db[picture::_g]=g*255.0;
    c.db[picture::_b]=b*255.0;
    }

struct _base_color
    {
    DWORD rgb,hsv;
    const char *nam;
    _base_color(DWORD _rgb,const char *_nam){ nam=_nam; rgb=_rgb; color c; c.dd=rgb; rgb2hsv(c); hsv=c.dd; }

    _base_color(){};
    _base_color(_base_color& a){};
    ~_base_color(){};
    _base_color* operator = (const _base_color *a){};
    //_base_color* operator = (const _base_color &a);
    };
const _base_color base_color[]=
    {
    //          0x00RRGGBB
    _base_color(0x00000000,"Black"),
    _base_color(0x00808080,"Gray"),
    _base_color(0x00C0C0C0,"Silver"),
    _base_color(0x00FFFFFF,"White"),
    _base_color(0x00800000,"Maroon"),
    _base_color(0x00FF0000,"Red"),
    _base_color(0x00808000,"Olive"),
    _base_color(0x00FFFF00,"Yellow"),
    _base_color(0x00008000,"Green"),
    _base_color(0x0000FF00,"Lime"),
    _base_color(0x00008080,"Teal"),
    _base_color(0x0000FFFF,"Aqua"),
    _base_color(0x00000080,"Navy"),
    _base_color(0x000000FF,"Blue"),
    _base_color(0x00800080,"Purple"),
    _base_color(0x00FF00FF,"Fuchsia"),
    _base_color(0x00000000,"")
    };

void compare_colors()
    {
    picture pic0;
    int h0,s0,v0,h1,s1,v1,x,y,i,d,i0,d0;
    int r0,g0,b0,r1,g1,b1,c0,c1,q,xx;
    color c;
    pic0.resize(256*4,256);
    pic0.pf=_pf_rgba;
    for (y=0;y<256;y++)
     for (x=0;x<256;x++)
        {
        // get color from image
        c=pic0.p[y][x];
        xx=x;
        r0=c.db[picture::_r];
        g0=c.db[picture::_g];
        b0=c.db[picture::_b];
        rgb2hsv(c);
        h0=c.db[picture::_h];
        s0=c.db[picture::_s];
        v0=c.db[picture::_v];
        // naive RGB
        xx+=256;
        for (i0=-1,d0=-1,i=0;base_color[i].nam[0];i++)
            {
            // compute distance
            c.dd=base_color[i].rgb;
            r1=c.db[picture::_r];
            g1=c.db[picture::_g];
            b1=c.db[picture::_b];
            // no need for sqrt
            d=((r1-r0)*(r1-r0))+((g1-g0)*(g1-g0))+((b1-b0)*(b1-b0));
            // remember closest match
            if ((d0<0)||(d0>d)) { d0=d; i0=i; }
            }
        pic0.p[y][xx].dd=base_color[i0].rgb;
        // normalized RGB
        xx+=256;
        c0=sqrt((r0*r0)+(g0*g0)+(b0*b0));
        if (!c0) i0=0; else
         for (i0=-1,d0=-1,i=1;base_color[i].nam[0];i++)
            {
            // compute distance
            c.dd=base_color[i].rgb;
            r1=c.db[picture::_r];
            g1=c.db[picture::_g];
            b1=c.db[picture::_b];
            c1=sqrt((r1*r1)+(g1*g1)+(b1*b1));
            // no need for sqrt
            q=((r0*c1)-(r1*c0))/4; q*=q; d =q;
            q=((g0*c1)-(g1*c0))/4; q*=q; d+=q;
            q=((b0*c1)-(b1*c0))/4; q*=q; d+=q;
            d/=c1*c0; d<<=16; d/=c1*c0;
            // remember closest match
            if ((d0<0)||(d0>d)) { d0=d; i0=i; }
            }
        pic0.p[y][xx].dd=base_color[i0].rgb;
        // HSV
        xx+=256;
        for (i0=-1,d0=-1,i=0;base_color[i].nam[0];i++)
            {
            // compute distance
            c.dd=base_color[i].hsv;
            h1=c.db[picture::_h];
            s1=c.db[picture::_s];
            v1=c.db[picture::_v];
            // no need for sqrt
            q=h1-h0;
            if (q<-128) q+=256; // use shorter angle
            if (q>+128) q-=256; // use shorter angle
                     q*=q; d =q;
            q=s1-s0; q*=q; d+=q;
            if (s0<32)          // grayscales
                {
                d=0;            // ignore H,S
                if (s1>=32) continue; // compare only to grayscales
                }
            q=v1-v0; q*=q; d+=q;
            // remember closest match
            if ((d0<0)||(d0>d)) { d0=d; i0=i; }
            }
        pic0.p[y][xx].dd=base_color[i0].rgb;
        }
    pic0.bmp->Canvas->Brush->Style=bsClear;
    pic0.bmp->Canvas->Font->Color=clBlack;
    x =256; pic0.bmp->Canvas->TextOutA(5+x,5,"Naive RGB");
    x+=256; pic0.bmp->Canvas->TextOutA(5+x,5,"Normalized RGB");
    x+=256; pic0.bmp->Canvas->TextOutA(5+x,5,"HSV");
    pic0.bmp->Canvas->Brush->Style=bsSolid;
    //pic0.save("colors.png");
    }

You can ignore the pic0 stuff it is only pixel access to image. I added few quirks in RGB distance equation to shift the sub-results so they fit inside 32 bit ints to avoid overflows. As an input I use this image:

in

And for each pixel is then corresponding base color from LUT found. This is the result:

out

On the Left is the source image, then naive RGB comparison, then Normalized RGB comparison (can not distinguish between the same color shades) and on the right the HSV comparison.

For the normalized RGB the colors found are alway the first in the LUT from the same color but different intensities. The comparison selects the darker only because they are first in the LUT.

As I mentioned before dark and grayscale colors are problem with this and should be handled separately. If you got similar results and still wrong detection then you need to add more base colors to cover the gaps. If you do not have similar results at all then you got most likely a problem with:

  1. wrong HSV or RGB ranges mine are <0,255> for each channel
  2. overflow somewhere

    when multiplying numbers the bits used are summed !!! So

    8bit * 8bit * 8bit * 8bit = 32bit
    

    and if the numbers are signed you re in trouble ... if on 32bit variables just like me in the example above so you need to shift the range a bit or use FPU on <0.0,1.0> intervals.

Just to be sure I added also mine HSV/RGB conversions in case you got some problem there.

And here the original HSV generated conversion:

out

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Ok ill go with second option – usernan May 27 '16 at 07:29
  • how will u make comparison for HSV – usernan Jun 03 '16 at 08:54
  • @usernan use the same euclidean distance but only in 2D ignoring the `Value V` as that is the color intensity (brightness) I added example also see [HSV histogram](http://stackoverflow.com/a/29286584/2521214) for more info – Spektre Jun 03 '16 at 09:07
  • only problem is i am using rgb_to_hsv from python its giving output as (0.16666666666666666, 0, 142) instead of (82 degree ,20 %,56 %) for rgb value (132, 142, 113) – usernan Jun 03 '16 at 09:45
  • @usernan that looks like standard `<0.0,1.0>` range for all components. It should work es expected – Spektre Jun 03 '16 at 10:20
  • it dint worked , but i used conversion from rgb to hsv in the required form . now i am getting proper output thanks – usernan Jun 03 '16 at 11:40
  • Hi , i have given color with hsv (300 , 1 , 52) and predefined colors pink ( 350 , 25 , 100) and grey (0 ,0 , 50 ) . it should detect as grey but its detecting as pink – usernan Jun 06 '16 at 04:57
  • @just render the image (like mine) and from that we can determine what is wrong... also your code would not hurt to see. You can take the left image of mine (256x256) and for each pixel compute the base color and replace it with it and show the result ... – Spektre Jun 06 '16 at 10:20
  • I uploaded the code i have color which i found with the help of opencv hsv(300 , 1 , 52) if we enter on this site the color shows as grey but if i give to my code then its shows as pink http://www.rapidtables.com/web/color/color-picker.htm – usernan Jun 06 '16 at 10:30
  • @usernan low saturation means that is more gray-scale then color and you should handle it as such. The problem with your color distance is the range you are still using ranges `H=<0,360> S=<0,100> V=<0,256 or 100>` and that is not OK you should have the same ranges for all channels !!! Otherwise you are comparing with different weights resulting in different output then you want. Also `H` is angle and can go through the whole circle so `h1-h0` should be truncated to `<-180,+180>` or `<-128,+128>` – Spektre Jun 06 '16 at 11:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/113917/discussion-between-usernan-and-spektre). – usernan Jun 06 '16 at 11:46