xyz sistema espacio cromaticidad cromaticas coordenadas colorimetria color cielab c algorithm image-processing colors color-space

sistema - espacio de color xyz



¿Qué pasa con este algoritmo de conversión de espacio de color RGB a XYZ? (6)

Mi objetivo es convertir un píxel RGB en espacio de color CIELab para algunos cálculos especiales solo posibles en CIELab. Para esto, primero debo convertir RGB a XYZ, que es la parte realmente difícil.

Traté de implementar este algoritmo en Objective-C (principalmente usando C simple), pero los resultados son incorrectos.

Mi código se basa en la pseudo-implementación proporcionada por easyrgb.com . Tienen un convertidor de color en línea que funciona muy bien. Dicen que su pseudocódigo es el mismo que se usa en su convertidor.

Este es su Pseudo-Código:

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

Este es mi intento de implementarlo en 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; }

Sin embargo, no obtengo los mismos resultados que su herramienta (con la misma configuración de Observador e Iluminante).

En mi prueba, ingresé estos valores en su herramienta y obtuve este resultado para XYZ, que está muy lejos de lo que produce mi implementación para ese valor RGB. Por favor, mira la captura de pantalla:

Los valores de color Lab resultantes son muy parecidos a lo que Photoshop me dice, por lo que el convertidor funciona muy bien.

El código C anterior me da los siguientes resultados:

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

¿Alguna idea de cuál es la causa de esta falla? ¿Cometí un error en mi implementación o necesito otras constantes?

Si conoce algunas implementaciones probadas de RGB a XYZ, XYZ a CIELab o RGB a CIELab, XYZ a Lab o RGB a Lab, no dude en publicarlas aquí.

Básicamente, todo lo que quiero hacer es calcular la desviación entre dos colores, también conocido como Delta-E. Es por eso que necesito convertir de RGB a XYZ en Lab (o CIELab) ...


* inR / 255 es una división entera. 1/255 es cero. Escriba * inR / 255.0 en su lugar.


Creo que este es su problema, esto está truncando a un número entero:

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

Prueba esto:

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

No he revisado el resto del código para ver otros problemas.


Solo uso tu código para convertir de RGB (a XYZ) a La * b *, y acabo de encontrar que los valores XYZ deben ir entre 0 a 1 antes de intentar convertirlos a La * b *

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

Por lo tanto, el código anterior debe borrarse para obtener los valores correctos de La * b *.


#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 );*/ }

Conversiones de color y diferencias en C https://github.com/gi0rikas/Color-converssions Distancia de laboratorio ~ = 2.3 corresponde a JND (diferencia apenas perceptible)


Como dijeron:

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 )

Esto se debe a la conversión implícita. A pesar de que las variables var_R, var_G y var_B son de tipo flotante, el operador / ve dos enteros R, G, B y 255. Tiene que dividirse y devuelve un entero.

Para obtener un valor de tipo flotante puede hacer un CAST o convertir al menos una de las variables en un tipo de letra flotante y agregar un punto decimal de la siguiente manera:

var_B = (B / 255.0f)

Otro ejemplo de conversión RGB2LAB y LAB2RGB (tenga en cuenta que es CIE La b 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 )); }


Los valores correctos de esta matriz son ligeramente diferentes, los precisos de "matrices RGB / XYZ" en 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