Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
487 views
in Technique[技术] by (71.8m points)

algorithm - RGB value base color name

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);
 }

 ?>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...