Java:如何将RGB颜色转换为CIE Lab?

19

如何在CIE Lab颜色模型中表示颜色对象。

Color c = ...
float[] lab = {0,0,0};
...
c.getColorComponents(ColorSpace.getInstance(???), lab);

尽管ColorSpace类中有TYPE_Lab,但我无法强制使用CIE Lab。

谢谢关注。

6个回答

21

这是我的实现:

import java.awt.color.ColorSpace;

public class CIELab extends ColorSpace {

    public static CIELab getInstance() {
        return Holder.INSTANCE;
    }

    @Override
    public float[] fromCIEXYZ(float[] colorvalue) {
        double l = f(colorvalue[1]);
        double L = 116.0 * l - 16.0;
        double a = 500.0 * (f(colorvalue[0]) - l);
        double b = 200.0 * (l - f(colorvalue[2]));
        return new float[] {(float) L, (float) a, (float) b};
    }

    @Override
    public float[] fromRGB(float[] rgbvalue) {
        float[] xyz = CIEXYZ.fromRGB(rgbvalue);
        return fromCIEXYZ(xyz);
    }

    @Override
    public float getMaxValue(int component) {
        return 128f;
    }

    @Override
    public float getMinValue(int component) {
        return (component == 0)? 0f: -128f;
    }    

    @Override
    public String getName(int idx) {
        return String.valueOf("Lab".charAt(idx));
    }

    @Override
    public float[] toCIEXYZ(float[] colorvalue) {
        double i = (colorvalue[0] + 16.0) * (1.0 / 116.0);
        double X = fInv(i + colorvalue[1] * (1.0 / 500.0));
        double Y = fInv(i);
        double Z = fInv(i - colorvalue[2] * (1.0 / 200.0));
        return new float[] {(float) X, (float) Y, (float) Z};
    }

    @Override
    public float[] toRGB(float[] colorvalue) {
        float[] xyz = toCIEXYZ(colorvalue);
        return CIEXYZ.toRGB(xyz);
    }

    CIELab() {
        super(ColorSpace.TYPE_Lab, 3);
    }

    private static double f(double x) {
        if (x > 216.0 / 24389.0) {
            return Math.cbrt(x);
        } else {
            return (841.0 / 108.0) * x + N;
        }
    }

    private static double fInv(double x) {
        if (x > 6.0 / 29.0) {
            return x*x*x;
        } else {
            return (108.0 / 841.0) * (x - N);
        }
    }

    private Object readResolve() {
        return getInstance();
    }

    private static class Holder {
        static final CIELab INSTANCE = new CIELab();
    }

    private static final long serialVersionUID = 5027741380892134289L;

    private static final ColorSpace CIEXYZ =
        ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);

    private static final double N = 4.0 / 29.0;

}

@Peter Perháč,适用知识共享署名-相同方式共享许可证。 - finnw
1
优秀 - 我使用这个工具没有任何问题,不过我做了一些小修改。在我的应用程序中,我将RGB图像转换为CIELab颜色空间,在L通道上进行了一些处理,然后再转换回RGB,发现出现了丑陋的伪影。我采用的解决方案是将上述toCIEXYZ方法计算得出的X、Y、Z值夹在[0,1]范围内。不确定这是否是“正确”的做法,但它消除了这些伪影。 - James
@James 代表 yasir-gakhar:你好 James。我已经计算出一张图片的 R、G 和 B 值,并进行了一些计算,以获取 LAB 色彩空间中的 L、a 和 b 值。现在,我该如何在 Android Studio 中使用这些 L、a 和 b 值将我的 RGB 图像转换为 LAB 图像(除了 OpenCV 的内置函数,因为我想先将 RGB 转换为 XYZ,最后再将 XYZ 转换为 LAB 色彩空间)? - jps
1
@jps @yasir-gakhar 这个答案中的实现正是如此 - 请注意 fromRGB 方法调用了 fromCIEXYZ - James

11

使用@finw的答案中的代码时,我遇到了一些问题。我认为这主要是因为进行CIELab转换时应该指定照明条件:

http://en.wikipedia.org/wiki/Standard_illuminant

其中一个流行的标准是D50,基本上只是标准白天光。由于@finw的代码没有进行照明校正,所以应该是中性灰色的颜色会带有一点色调。检查这一点的方法之一是尝试:

 float[] g = { 50.0f, 0f, 0f };
 CIELab.getInstance().toRGB(g); 
 for (float f : g) System.out.println(f);
你应该在三个通道上得到大致相同的数字,但最终得到一个明显(尽管略微)偏蓝的RGB配置文件。我确信在@finw的代码中可以纠正这个问题,但在尝试和搜索一番后,我在这里找到了一些优秀的转换代码:http://www.f4.fhtw-berlin.de/~barthel/ImageJ/ColorInspector//HTMLHelp/farbraumJava.htm。为了保持完整性,在这里提供它。
public void rgb2lab(int R, int G, int B, int[] lab) {
    //http://www.brucelindbloom.com

    float r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr;
    float Ls, as, bs;
    float eps = 216.f/24389.f;
    float k = 24389.f/27.f;

    float Xr = 0.964221f;  // reference white D50
    float Yr = 1.0f;
    float Zr = 0.825211f;

    // RGB to XYZ
    r = R/255.f; //R 0..1
    g = G/255.f; //G 0..1
    b = B/255.f; //B 0..1

    // assuming sRGB (D65)
    if (r <= 0.04045)
        r = r/12;
    else
        r = (float) Math.pow((r+0.055)/1.055,2.4);

    if (g <= 0.04045)
        g = g/12;
    else
        g = (float) Math.pow((g+0.055)/1.055,2.4);

    if (b <= 0.04045)
        b = b/12;
    else
        b = (float) Math.pow((b+0.055)/1.055,2.4);


    X =  0.436052025f*r     + 0.385081593f*g + 0.143087414f *b;
    Y =  0.222491598f*r     + 0.71688606f *g + 0.060621486f *b;
    Z =  0.013929122f*r     + 0.097097002f*g + 0.71418547f  *b;

    // XYZ to Lab
    xr = X/Xr;
    yr = Y/Yr;
    zr = Z/Zr;

    if ( xr > eps )
        fx =  (float) Math.pow(xr, 1/3.);
    else
        fx = (float) ((k * xr + 16.) / 116.);

    if ( yr > eps )
        fy =  (float) Math.pow(yr, 1/3.);
    else
    fy = (float) ((k * yr + 16.) / 116.);

    if ( zr > eps )
        fz =  (float) Math.pow(zr, 1/3.);
    else
        fz = (float) ((k * zr + 16.) / 116);

    Ls = ( 116 * fy ) - 16;
    as = 500*(fx-fy);
    bs = 200*(fy-fz);

    lab[0] = (int) (2.55*Ls + .5);
    lab[1] = (int) (as + .5); 
    lab[2] = (int) (bs + .5);       
} 

在我的测试中,它生成的灰度值是适当无色差的,并且速度更快。


1
你确定这是正确的实现吗?我试图将白色(255,255,255)转换,结果是(L = 255,a = 0,b = 0),而不是(L = 100,a = 0,b = 0)。我还检查了http://colormine.org/convert/rgb-to-lab,以便与其他颜色进行比较,例如红色,黄色和绿色。 - fikr4n
1
似乎Ls被扩展到填充[0, 255]范围。仅返回Ls即可。此外,我省略了+ .5。它们似乎是多余的,因为它们会导致值溢出。我可以四舍五入该值,这更有意义。 - akinuri
关于标准光源,D50“模拟日出或日落时的温暖日光,相关色温为5003K。也被称为地平线光。”可能比预期更温暖。D65是“正午”日光。XYZ参考值:https://www.mathworks.com/help/images/ref/whitepoint.html - Oskar Austegard

8
我使用了这段代码,它起效了:

public double[] rgbToLab(int R, int G, int B) {

    double r, g, b, X, Y, Z, xr, yr, zr;

    // D65/2°
    double Xr = 95.047;  
    double Yr = 100.0;
    double Zr = 108.883;


    // --------- RGB to XYZ ---------//

    r = R/255.0;
    g = G/255.0;
    b = B/255.0;

    if (r > 0.04045)
        r = Math.pow((r+0.055)/1.055,2.4);
    else
        r = r/12.92;

    if (g > 0.04045)
        g = Math.pow((g+0.055)/1.055,2.4);
    else
        g = g/12.92;

    if (b > 0.04045)
        b = Math.pow((b+0.055)/1.055,2.4);
    else
        b = b/12.92 ;

    r*=100;
    g*=100;
    b*=100;

    X =  0.4124*r + 0.3576*g + 0.1805*b;
    Y =  0.2126*r + 0.7152*g + 0.0722*b;
    Z =  0.0193*r + 0.1192*g + 0.9505*b;


    // --------- XYZ to Lab --------- //

    xr = X/Xr;
    yr = Y/Yr;
    zr = Z/Zr;

    if ( xr > 0.008856 )
        xr =  (float) Math.pow(xr, 1/3.);
    else
        xr = (float) ((7.787 * xr) + 16 / 116.0);

    if ( yr > 0.008856 )
        yr =  (float) Math.pow(yr, 1/3.);
    else
        yr = (float) ((7.787 * yr) + 16 / 116.0);

    if ( zr > 0.008856 )
        zr =  (float) Math.pow(zr, 1/3.);
    else
        zr = (float) ((7.787 * zr) + 16 / 116.0);


    double[] lab = new double[3];

    lab[0] = (116*yr)-16;
    lab[1] = 500*(xr-yr); 
    lab[2] = 200*(yr-zr); 

    return lab;

} 

对于上面的代码,我使用这里提供的公式来将RGB转换为XYZ,然后从XYZ转换为CIELab。我得到的结果与在线转换器相同。


2

很抱歉打扰了一个旧帖子,但任何新的帖子都可能被标记为重复 - 我感觉排名最高的答案过于复杂或过度设计,其他答案则不完整或缺乏信息。

public static float[] rgbToLab(int r, int g, int b) {
    return ColorSpace.getInstance(ColorSpace.CS_CIEXYZ).fromRGB(new float[]{r / 255f, g / 255f, b / 255f});
}

使用awt.color.ColorSpace的简单一行代码 - 在我的实践中表现非常好。您可以像这样计算距离:

// Euclidean Distance
public static double distance(Color target, Color control) {
    float[] a = rgbToLab(target), b = rgbToLab(control);
    double L = a[0] - b[0], A = a[1] - b[1], B = a[2] - b[2];

    return Math.sqrt((L * L) + (A * A) + (B * B));
}

public static float[] rgbToLab(Color color) {
    return rgbToLab(color.getRed(), color.getGreen(), color.getBlue());
}

这将产生以下结果:
// Control color = #D9C967
#213B1E | DISTANCE: 2.5532837723818224E-4
#19301C | DISTANCE: 2.74658203125E-4
#1E2D10 | DISTANCE: 2.74658203125E-4
#DDC669 | DISTANCE: 0.0
#DDC56B | DISTANCE: 0.0
#DAC761 | DISTANCE: 0.0

根据 计算器,你的解决方案无法正常工作。我的单元测试失败了,因为对于纯红色即 255,0,0 的 RGB 值,Lab(0.43585205078125,0.222381591796875,0.013916015625) 不等于 Lab(53.23,80.1,67.22)。这些值差异已足够大,因此这不是比较 doubles 的问题。 - Sim

2

存在一个TYPE_Lab,但是没有相应的CS_Lab。您需要扩展ColorSpace并覆盖抽象方法以在XYZ、RGB和Lab之间进行转换。所需的转换可以在Lab color space (Wikipedia)找到。


1

在当前的 Java 库中,CIELAB 似乎仅以名称形式受到支持 - 如果您查看 java.awt.color.Colorspace 的源代码,您会发现仅支持少量命名的颜色空间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接