What's wrong with this RGB to XYZ color space conversion algorithm?

16,052

Solution 1

I believe here is your problem, this is truncating to an integer:

CGFloat var_R = (*inR / 255); //R from 0 to 255
CGFloat var_G = (*inG / 255); //G from 0 to 255
CGFloat var_B = (*inB / 255); //B from 0 to 255

Try this:

CGFloat var_R = (*inR / 255.0f); //R from 0 to 255
CGFloat var_G = (*inG / 255.0f); //G from 0 to 255
CGFloat var_B = (*inB / 255.0f); //B from 0 to 255

I haven't checked the rest of the code for other problems.

Solution 2

*inR/255 is an integer division. 1/255 is zero. Write *inR/255.0 instead.

Solution 3

The right values of this matrix is different slightly,the accurate one from "RGB/XYZ Matrices" in http://www.brucelindbloom.com

sX = sRed * 0.4124564 + sGreen * 0.3575761 + sBlue * 0.1804375
sY = sRed * 0.2126729 + sGreen * 0.7151522 + sBlue * 0.072175
sZ = sRed * 0.0193339 + sGreen * 0.119192 + sBlue * 0.9503041

Solution 4

#include <stdio.h>
#include <math.h>

float ref_X = 95.047;
float ref_Y = 100.0;
float ref_Z = 108.883;


void convertRGBtoXYZ(int inR, int inG, int inB, float * outX, float * outY, float * outZ) {


    float var_R = (inR / 255.0f); //R from 0 to 255
    float var_G = (inG / 255.0f); //G from 0 to 255
    float var_B = (inB / 255.0f); //B from 0 to 255

    if (var_R > 0.04045f)
        var_R = powf(( (var_R + 0.055f) / 1.055f), 2.4f);
    else 
        var_R = var_R / 12.92f;

    if (var_G > 0.04045)
        var_G = powf(( (var_G + 0.055f) / 1.055f), 2.4f);
    else
        var_G = var_G / 12.92f;

    if (var_B > 0.04045f)
        var_B = powf(( (var_B + 0.055f) / 1.055f), 2.4f);
    else
        var_B = var_B / 12.92f;

    var_R = var_R * 100;
    var_G = var_G * 100;
    var_B = var_B * 100;

    //Observer. = 2°, Illuminant = D65
    *outX = var_R * 0.4124f + var_G * 0.3576f + var_B * 0.1805f;
    *outY = var_R * 0.2126f + var_G * 0.7152f + var_B * 0.0722f;
    *outZ = var_R * 0.0193f + var_G * 0.1192f + var_B * 0.9505f;
}

void convertXYZtoLab(float inX, float inY, float inZ, float * outL, float * outa, float * outb) {

    float var_X = (inX / ref_X); //ref_X = 95.047
    float var_Y = (inY / ref_Y); //ref_Y = 100.0
    float var_Z = (inZ / ref_Z); //ref_Z = 108.883

    if ( var_X > 0.008856 ) 
        var_X = powf(var_X , ( 1.0f/3 )); 
    else 
        var_X = ( 7.787 * var_X ) + ( 16.0f/116 );

    if ( var_Y > 0.008856 )
        var_Y = powf(var_Y , ( 1.0f/3 )); 
    else
        var_Y = ( 7.787 * var_Y ) + ( 16.0f/116 );

    if ( var_Z > 0.008856 )
        var_Z = powf(var_Z , ( 1.0f/3 )); 
    else 
        var_Z = ( 7.787 * var_Z ) + ( 16.0f/116 );

    *outL = ( 116 * var_Y ) - 16;
    *outa = 500 * ( var_X - var_Y );
    *outb = 200 * ( var_Y - var_Z );
}

void convertLabtoXYZ( float inL, float ina, float  inb, float * outX, float * outY, float * outZ) {

    float var_Y = ( inL + 16 ) / 116;
    float var_X = (ina/500) + var_Y;
    float var_Z = var_Y - (inb/200);

    if ( powf(var_Y,3.f) > 0.008856 ) 
        var_Y = powf(var_Y,3.f);
    else
        var_Y = ( var_Y - (16/116) ) / 7.787;

    if ( powf(var_X,3.f) > 0.008856 ) 
        var_X = powf(var_X,3.f);
    else 
        var_X = ( var_X - (16/116) ) / 7.787;

    if ( powf(var_Z,3.f) > 0.008856 )
        var_Z = powf(var_Z,3.f);
    else
        var_Z = ( var_Z - (16/116) ) / 7.787;

    *outX = ref_X * var_X;     //ref_X =  95.047     Observer= 2°, Illuminant= D65
    *outY = ref_Y * var_Y;     //ref_Y = 100.000
    *outZ = ref_Z * var_Z;     //ref_Z = 108.883
}

void convertXYZtoRGB(float inX, float inY, float inZ, int * outR, int * outG, int * outB) {


    float var_X = inX/100;
    float var_Y = inY/100;
    float var_Z = inZ/100;

    float var_R = var_X *  3.2406 + (var_Y * -1.5372) + var_Z * (-0.4986);
    float var_G = var_X * (-0.9689) + var_Y *  1.8758 + var_Z *  0.0415;
    float var_B = var_X *  0.0557 + var_Y * (-0.2040) + var_Z *  1.0570;

    if ( var_R > 0.0031308 )
        var_R = 1.055 * powf(var_R, ( 1.0f / 2.4 ) )  - 0.055;
    else 
        var_R = 12.92 * var_R;

    if ( var_G > 0.0031308 ) 
        var_G = 1.055 * powf(var_G, ( 1.0f / 2.4 ) ) - 0.055;
    else 
        var_G = 12.92 * var_G;

    if ( var_B > 0.0031308 )
        var_B = 1.055 * powf(var_B, ( 1.0f / 2.4 ) ) - 0.055;
    else
        var_B = 12.92 * var_B;

    *outR = (int)(var_R * 255);
    *outG = (int)(var_G * 255);
    *outB = (int)(var_B * 255);


}

float Lab_color_difference( float inL1, float ina1, float  inb1, float inL2, float ina2, float  inb2){
    return( sqrt( powf(inL1 - inL2, 2.f) + powf(ina1 - ina2, 2.f) + powf(inb1 - inb2, 2.f) ) );
}

float RGB_color_Lab_difference( int R1, int G1, int B1, int R2, int G2, int B2){
    float x1=0,y1=0,z1=0;
    float x2=0,y2=0,z2=0;
    float l1=0,a1=0,b1=0;
    float l2=0,a2=0,b2=0;

    convertRGBtoXYZ(R1, G1, B1, &x1, &x1, &z1);
    convertRGBtoXYZ(R2, G2, B2, &x2, &x2, &z2);

    convertXYZtoLab(x1, y1, z1, &l1, &a1, &b1);
    convertXYZtoLab(x2, y2, z2, &l2, &a2, &b2); 

    return( Lab_color_difference(l1 ,a1 ,b1 ,l2 ,a2 ,b2) );
}


void main(int argc, char const *argv[])
{
    int R1,G1,B1,R2,G2,B2;
    float x=0.f,y=0.f,z=0.f;
    float l=0.f,a=0.f,b=0.f;

    R1 = 200;
    G1 = 2;
    B1 = 50; 

    R2 = 200;
    G2 = 2;
    B2 = 70; 

    printf("LAB DISTANCE = %lf \n", RGB_color_Lab_difference(R1,G1,B1,R2,G2,B2) );

    /*convertRGBtoXYZ(R, G, B, &x, &y, &z);
    printf("R =%d,G =%d,B =%d,x =%lf,y =%lf,z =%lf\n",R,G,B,x,y,z );
    convertXYZtoLab(x, y, z, &l, &a, &b);
    printf("x =%lf,y =%lf,z =%lf, l =%lf,a =%lf,b =%lf\n",x,y,z, l,a,b );
    convertLabtoXYZ( l, a, b ,&x, &y, &z);
    printf("x =%lf,y =%lf,z =%lf, l =%lf,a =%lf,b =%lf\n",x,y,z, l,a,b );
    convertXYZtoRGB( x, y, z,&R, &G, &B);
    printf("R =%d,G =%d,B =%d,x =%lf,y =%lf,z =%lf\n",R,G,B,x,y,z );*/

}

Color conversions and differences in C https://github.com/gi0rikas/Color-conversions Lab distance ~= 2.3 corresponds to JND(Just noticeable difference)

Share:
16,052
SecretService - not really
Author by

SecretService - not really

Why SecretService? Because all other names were already taken. And because it sounds cool. Better than ScrewedMonkey or FatRabbitLookingForCheese.

Updated on June 15, 2022

Comments

  • SecretService - not really
    SecretService - not really about 2 years

    My goal is to convert an RGB pixel into CIELab color space for some special calculations only possible in CIELab. For this, I must convert RGB to XYZ first, which is the really hard part.

    I tried to implement this algorithm in Objective-C (mostly using plain C though), but the results are wrong.

    My code is based on the pseudo-implementation provided by easyrgb.com. They have an online-color-converter which works great. They say that their pseudo-code is the same one used in their converter.

    This is their Pseudo-Code:

    var_R = ( R / 255 )        //R from 0 to 255
    var_G = ( G / 255 )        //G from 0 to 255
    var_B = ( B / 255 )        //B from 0 to 255
    
    if ( var_R > 0.04045 ) var_R = ( ( var_R + 0.055 ) / 1.055 ) ^ 2.4
    else                   var_R = var_R / 12.92
    if ( var_G > 0.04045 ) var_G = ( ( var_G + 0.055 ) / 1.055 ) ^ 2.4
    else                   var_G = var_G / 12.92
    if ( var_B > 0.04045 ) var_B = ( ( var_B + 0.055 ) / 1.055 ) ^ 2.4
    else                   var_B = var_B / 12.92
    
    var_R = var_R * 100
    var_G = var_G * 100
    var_B = var_B * 100
    
    //Observer. = 2°, Illuminant = D65
    X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
    Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
    Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
    

    This is my attempt to implement it in Objective-C / C:

    void convertRGBtoXYZ(NSInteger * inR, NSInteger * inG, NSInteger * inB, CGFloat * outX, CGFloat * outY, CGFloat * outZ) {
        // http://www.easyrgb.com/index.php?X=MATH&H=02#text2
    
        CGFloat var_R = (*inR / 255); //R from 0 to 255
        CGFloat var_G = (*inG / 255); //G from 0 to 255
        CGFloat var_B = (*inB / 255); //B from 0 to 255
    
        if (var_R > 0.04045f) {
            var_R = powf(( (var_R + 0.055f) / 1.055f), 2.4f);
        } else {
            var_R = var_R / 12.92f;
        }
    
        if (var_G > 0.04045) {
            var_G = powf(( (var_G + 0.055f) / 1.055f), 2.4f);
        } else {
            var_G = var_G / 12.92f;
        }
    
        if (var_B > 0.04045f) {
            var_B = powf(( (var_B + 0.055f) / 1.055f), 2.4f);
        } else {
            var_B = var_B / 12.92f;
        }
    
        var_R = var_R * 100;
        var_G = var_G * 100;
        var_B = var_B * 100;
    
        //Observer. = 2°, Illuminant = D65
        *outX = var_R * 0.4124f + var_G * 0.3576f + var_B * 0.1805f;
        *outY = var_R * 0.2126f + var_G * 0.7152f + var_B * 0.0722f;
        *outZ = var_R * 0.0193f + var_G * 0.1192f + var_B * 0.9505f;
    }
    

    However, I don't get the same results as their tool (with same Observer and Illuminant setting).

    In my test, I entered these values into their tool and got this result for XYZ which is far off from what my implementation produces for that RGB value. Please see screenshot:


    screenshot


    The resulting Lab color values are pretty close to what Photoshop tells me, so the converter works great.

    The C-code above gives me this results though:

    X = 35.76... // should be 42.282
    Y = 71.52... // should be 74.129
    Z = 11.92... // should be 46.262
    

    Any idea what's the cause for this failure? Did I do a mistake in my implementation, or do I need other constants?

    If you know some tested RGB to XYZ, XYZ to CIELab or RGB to CIELab, XYZ to Lab or RGB to Lab implementations, please don't hesitate to post them here.

    Basically, all I want to do is calculate the deviation between two colors, also known as Delta-E. That's why I need to convert from RGB to XYZ to Lab (or CIELab)...

  • SecretService - not really
    SecretService - not really almost 13 years
    How could I oversee that?! Now getting the exact same results. Thanks!!
  • SecretService - not really
    SecretService - not really almost 13 years
    Thanks! This was the solution! I accepted the answer of hexa for it's completeness though. Was a hard decision to decide between them.
  • Artemix
    Artemix almost 11 years
    It is not clear from your answer where the code should be erased and why. You'd better post your code (it's essential part) for converting from RGB to Lab.
  • Admin
    Admin almost 7 years
    For Python: var_R = ( sR / 255.0 ) var_G = ( sG / 255.0 ) var_B = ( sB / 255.0 )