Что не так с этим алгоритмом преобразования цветового пространства RGB в XYZ?

Моя цель — преобразовать пиксель RGB в цветовое пространство CIELab для некоторых специальных вычислений, возможных только в CIELab. Для этого я должен сначала преобразовать RGB в XYZ, что является действительно сложной частью.

Я пытался реализовать этот алгоритм в Objective-C (в основном, используя простой C), но результаты неверны.

Мой код основан на псевдо-реализации, предоставленной easyrgb.com. У них есть онлайн-конвертер цвета, который отлично работает. Они говорят, что их псевдокод такой же, как и в конвертере.

Это их псевдокод:

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

Это моя попытка реализовать это на 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;
}

Однако я не получаю таких же результатов, как их инструмент (с теми же настройками Observer и Illuminant).

В моем тесте я ввел эти значения в их инструмент и получил этот результат для XYZ, который далек от того, что моя реализация дает для этого значения RGB. Пожалуйста, смотрите скриншот:


скриншот


Полученные значения цвета Lab довольно близки к тому, что мне сообщает Photoshop, поэтому конвертер работает отлично.

Однако приведенный выше C-код дает мне следующие результаты:

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

Есть идеи, в чем причина этой неудачи? Я сделал ошибку в своей реализации или мне нужны другие константы?

Если вы знаете несколько протестированных реализаций RGB to XYZ, XYZ to CIELab или RGB to CIELab, XYZ to Lab или RGB to Lab, опубликуйте их здесь.

По сути, все, что я хочу сделать, это вычислить отклонение между двумя цветами, также известное как Delta-E. Вот почему мне нужно преобразовать RGB в XYZ в Lab (или CIELab)...


person SecretService - not really    schedule 08.07.2011    source источник
comment
Удалось ли вам воспроизвести значения Photoshop? Спасибо.   -  person Royi    schedule 02.03.2017


Ответы (6)


Я считаю, что вот ваша проблема, это усечение до целого числа:

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

Попробуй это:

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

Я не проверял остальную часть кода на наличие других проблем.

person Vinicius Kamakura    schedule 08.07.2011
comment
Как я мог проследить за этим?! Теперь получаю точно такие же результаты. Спасибо!! - person SecretService - not really; 09.07.2011
comment
Для Python: var_R = ( sR / 255.0 ) var_G = ( sG / 255.0 ) var_B = ( sB / 255.0 ) - person ; 17.09.2017

*inR/255 — целочисленное деление. 1/255 это ноль. Вместо этого напишите *inR/255.0.

person whoplisp    schedule 08.07.2011
comment
Спасибо! Это было решением! Однако я принял ответ гекса за его полноту. Было трудно выбрать между ними. - person SecretService - not really; 09.07.2011

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

}

Преобразования цветов и различия в C https://github.com/gi0rikas/Color-conversions Lab Distance ~= 2,3 соответствует JND (просто заметная разница)

person George Garyfallou    schedule 15.07.2016

Правильные значения этой матрицы немного отличаются, точное из "Матрицы RGB/XYZ" в 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
person saber    schedule 02.05.2017

Я просто использую ваш код для преобразования из RGB (в XYZ) в La * b *, и я только что обнаружил, что значения XYZ должны находиться в диапазоне от 0 до 1, прежде чем вы попытаетесь преобразовать их в La * b *

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

Таким образом, предыдущий код должен быть стерт, чтобы получить правильные значения La*b*.

person asdf    schedule 15.07.2013
comment
Из вашего ответа непонятно, где код надо стирать и почему. Лучше выложите свой код (это важная часть) для конвертации из RGB в Lab. - person Artemix; 15.07.2013

Как они сказали:

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 )

Это из-за неявного преобразования. Несмотря на то, что переменные var_R, var_G и var_B имеют тип float, оператор / видит два целых числа R, G, B и 255. Он должен делить и возвращает целое число.

Чтобы получить значение типа с плавающей запятой, вы можете выполнить CAST или преобразовать хотя бы одну из переменных в тип с плавающей запятой, добавив десятичную точку следующим образом:

var_B = ( B / 255.0f)

Другой пример преобразования RGB2LAB и LAB2RGB (имейте в виду, что это 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 ));
}
person omotto    schedule 27.01.2017