简体   繁体   English

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

[英]Java: how to convert RGB color to CIE Lab

How can I present object Color in CIE Lab color model.如何在 CIE Lab 颜色模型中呈现对象颜色。

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

But I wasn't able to force this work with CIE Lab (despite of the fact that TYPE_Lab is presented in ColorSpace class)但我无法强制使用 CIE Lab 进行这项工作(尽管 TYPE_Lab 出现在 ColorSpace 类中)

Thx for attention.谢谢关注。

Here's my implementation:这是我的实现:

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;

}

I had some problems using the code in @finw's answer.我在使用@finw 答案中的代码时遇到了一些问题。 I believe they were mostly due to the fact that to do a CIELab conversion you should specify an illuminant:我相信它们主要是因为要进行 CIELab 转换,您应该指定一个光源:

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

One of the popular standards is D50 , which is basically just a standard daylight.流行的标准之一是D50 ,它基本上只是一个标准的日光。 Because @finw's code doesn't have the correction for illumination, the colors that are supposed to be neutral gray come out slightly tinted.因为@finw 的代码没有对照明进行校正,所以应该是中性灰色的颜色会略微着色。 One way of checking this is to try:检查这一点的一种方法是尝试:

 float[] g = { 50.0f, 0f, 0f };
 CIELab.getInstance().toRGB(g); 
 for (float f : g) System.out.println(f);

You should get roughly the same number on all three channels, but you end up with an RGB profile that's noticeably (albeit slightly) blue.您应该在所有三个通道上获得大致相同的数字,但您最终会得到一个明显(尽管略微)蓝色的 RGB 配置文件。 I'm sure it is possible to correct this in @finw's code, but after a bit of playing with it and searching around, I found some excellent conversion code here:我确信可以在@finw 的代码中更正这一点,但是经过一番尝试和搜索后,我在这里找到了一些出色的转换代码:

http://www.f4.fhtw-berlin.de/~barthel/ImageJ/ColorInspector//HTMLHelp/farbraumJava.htm http://www.f4.fhtw-berlin.de/~barthel/ImageJ/ColorInspector//HTMLHelp/farbraumJava.htm

For completeness, here it is.为了完整起见,这里是。

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);       
} 

In my tests, it produces gray values that are appropriately chroma-free, and it is much speedier to boot.在我的测试中,它会产生适当无色度的灰度值,并且启动速度要快得多。

I used this code and it worked:我使用了这段代码并且它有效:

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;

} 

For the code above I used the formulas provided here in order to convert from rgb to XYZ and then from XYZ to CIELab.对于上面的代码,我使用此处提供的公式将 rgb 转换为 XYZ,然后从 XYZ 转换为 CIELab。 The results I get are the same with this online converter.我得到的结果与这个在线转换器相同。

There is a TYPE_Lab , but no corresponding CS_Lab .有一个TYPE_Lab ,但没有对应的CS_Lab You will need to extend ColorSpace and override the abstract methods to convert between XYZ, RGB, and Lab.您将需要扩展ColorSpace并覆盖抽象方法以在 XYZ、RGB 和 Lab 之间进行转换。 The required conversions can be found at Lab color space (Wikipedia) .可以在Lab color space (Wikipedia)中找到所需的转换。

Sorry to bump an old thread but any new ones would likely get marked as duplicate - I feel the top-rated answers are complex or over-engineered and others are not complete or just lacking in information.很抱歉碰到一个旧线程,但任何新线程都可能被标记为重复 - 我觉得最受好评的答案很复杂或设计过度,而其他答案不完整或只是缺乏信息。

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});
}

Easy 1 liner using awt.color.ColorSpace - working very well in my practice. Easy 1 liner using awt.color.ColorSpace - 在我的实践中效果很好。 You can calculate the distance like so你可以像这样计算距离

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

This yields results as so;这会产生这样的结果;

// 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

CIELAB seems to be supported only by name in the current Java library - if you look at the source of java.awt.color.Colorspace, you'll see that only a handful of the named color spaces are supported.在当前的 Java 库中,CIELAB 似乎仅通过名称来支持 - 如果您查看 java.awt.color.Colorspace 的源代码,您会发现只支持少数命名的颜色空间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM