What's wrong with this RGB to XYZ color space conversion algorithm?
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:
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 co开发者_开发问答nstants?
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)...
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.
*inR/255 is an integer division. 1/255 is zero. Write *inR/255.0 instead.
#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)
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
I just use your code to convert from RGB (to XYZ) to La*b*, and I just found that the XYZ values should go between 0 a 1 before you try to convert them to La*b*
var_R = var_R * 100;
var_G = var_G * 100;
var_B = var_B * 100;
So the previous code must be erased in order to get correct La*b* values.
As they said:
var_R = ( R / 255 ) -> var_R = ( R / 255.0 ) or var_R = ( R * 0.003922 )
var_G = ( G / 255 ) -> var_G = ( G / 255.0 ) or var_G = ( G * 0.003922 )
var_B = ( B / 255 ) -> var_B = ( B / 255.0 ) or var_B = ( B * 0.003922 )
This is because of implicit conversion. Despite the fact that var_R, var_G and var_B variables are float type, the / operator sees two integers R, G, B and 255. it has to divide and returns an integer.
In order to get a float type value you can do a CAST or convert at least one of the variables into float type adding of a decimal point as follows:
var_B = ( B / 255.0f)
Another example of RGB2LAB and LAB2RGB conversion (bear in mind that is CIE Lab D65):
void RGB2LAB(uint8_t R, uint8_t G, uint8_t B, float *l, float *a, float *b) {
float RGB[3], XYZ[3];
RGB[0] = R * 0.003922;
RGB[1] = G * 0.003922;
RGB[2] = B * 0.003922;
RGB[0] = (RGB[0] > 0.04045) ? pow(((RGB[0] + 0.055)/1.055), 2.4) : RGB[0] / 12.92;
RGB[1] = (RGB[1] > 0.04045) ? pow(((RGB[1] + 0.055)/1.055), 2.4) : RGB[1] / 12.92;
RGB[2] = (RGB[2] > 0.04045) ? pow(((RGB[2] + 0.055)/1.055), 2.4) : RGB[2] / 12.92;
XYZ[0] = 0.412424 * RGB[0] + 0.357579 * RGB[1] + 0.180464 * RGB[2];
XYZ[1] = 0.212656 * RGB[0] + 0.715158 * RGB[1] + 0.0721856 * RGB[2];
XYZ[2] = 0.0193324 * RGB[0] + 0.119193 * RGB[1] + 0.950444 * RGB[2];
*l = 116 * ( ( XYZ[1] / 1.000000) > 0.008856 ? pow(XYZ[1] / 1.000000, 0.333333) : 7.787 * XYZ[1] / 1.000000 + 0.137931) - 16;
*a = 500 * ( ((XYZ[0] / 0.950467) > 0.008856 ? pow(XYZ[0] / 0.950467, 0.333333) : 7.787 * XYZ[0] / 0.950467 + 0.137931) - ((XYZ[1] / 1.000000) > 0.008856 ? pow(XYZ[1] / 1.000000, 0.333333) : 7.787 * XYZ[1] / 1.000000 + 0.137931) );
*b = 200 * ( ((XYZ[1] / 1.000000) > 0.008856 ? pow(XYZ[1] / 1.000000, 0.333333) : 7.787 * XYZ[1] / 1.000000 + 0.137931) - ((XYZ[2] / 1.088969) > 0.008856 ? pow(XYZ[2] / 1.088969, 0.333333) : 7.787 * XYZ[2] / 1.088969 + 0.137931) );
}
void LAB2RGB(float L, float A, float B, uint8_t *r, uint8_t *g, uint8_t *b) {
float XYZ[3], RGB[3];
XYZ[1] = (L + 16 ) / 116;
XYZ[0] = A / 500 + XYZ[1];
XYZ[2] = XYZ[1] - B / 200;
XYZ[1] = (XYZ[1]*XYZ[1]*XYZ[1] > 0.008856) ? XYZ[1]*XYZ[1]*XYZ[1] : (XYZ[1] - (16 / 116)) / 7.787;
XYZ[0] = (XYZ[0]*XYZ[0]*XYZ[0] > 0.008856) ? XYZ[0]*XYZ[0]*XYZ[0] : (XYZ[0] - (16 / 116)) / 7.787;
XYZ[2] = (XYZ[2]*XYZ[2]*XYZ[2] > 0.008856) ? XYZ[2]*XYZ[2]*XYZ[2] : (XYZ[2] - (16 / 116)) / 7.787;
RGB[0] = 0.950467 * XYZ[0] * 3.2406 + 1.000000 * XYZ[1] * -1.5372 + 1.088969 * XYZ[2] * -0.4986;
RGB[1] = 0.950467 * XYZ[0] * -0.9689 + 1.000000 * XYZ[1] * 1.8758 + 1.088969 * XYZ[2] * 0.0415;
RGB[2] = 0.950467 * XYZ[0] * 0.0557 + 1.000000 * XYZ[1] * -0.2040 + 1.088969 * XYZ[2] * 1.0570;
*r = (255 * ( (RGB[0] > 0.0031308) ? 1.055 * (pow(RGB[0], (1/2.4)) - 0.055) : RGB[0] * 12.92 ));
*g = (255 * ( (RGB[1] > 0.0031308) ? 1.055 * (pow(RGB[1], (1/2.4)) - 0.055) : RGB[1] * 12.92 ));
*b = (255 * ( (RGB[2] > 0.0031308) ? 1.055 * (pow(RGB[2], (1/2.4)) - 0.055) : RGB[2] * 12.92 ));
}
精彩评论