18

I have the following piece of code:

public List<Tuple<double, double, double>> GetNormalizedPixels(Bitmap image)
{
    System.Drawing.Imaging.BitmapData data = image.LockBits(
        new Rectangle(0, 0, image.Width, image.Height),
        System.Drawing.Imaging.ImageLockMode.ReadOnly,
        image.PixelFormat);

    int pixelSize = Image.GetPixelFormatSize(image.PixelFormat) / 8;

    var result = new List<Tuple<double, double, double>>();

    unsafe
    {
        for (int y = 0; y < data.Height; ++y)
        {
            byte* row = (byte*)data.Scan0 + (y * data.Stride);

            for (int x = 0; x < data.Width; ++x)
            {
                Color c = Color.FromArgb(
                    row[x * pixelSize + 3],
                    row[x * pixelSize + 2],
                    row[x * pixelSize + 1],
                    row[x * pixelSize]);

                // (*)
                result.Add(Tuple.Create(
                    1.0 * c.R / 255,
                    1.0 * c.G / 255,
                    1.0 * c.B / 255);
            }
        }
    }

    image.UnlockBits(data);

    return result;
}

The key fragment (*) is this:

result.Add(Tuple.Create(
    1.0 * c.R / 255,
    1.0 * c.G / 255,
    1.0 * c.B / 255);

which adds a pixel with its components scaled to range [0, 1] to be further used in classification tasks with different classifiers. Some of them require the attributes to be normalized like this, others don't care - hence this function.

However, what should I do when I'd like to classify pixels in a different colour space than RGB, like L*a*b*? While values of all coordinates in RGB colour space fall into range [0,256) in L*a*b* colour space a* and b* are said to be unbounded.

So when changing the fragment (*) to:

Lab lab = c.ToLab();

result.Add(Tuple.Create(
    1.0 * lab.L / 100,
    1.0 * lab.A / ?,
    1.0 * lab.B / ?);

(ToLab is an extension method, implemented using appropriate algorithms from here)

what should I put for the question marks?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • Interesting question. Mind that the answer depends on the rgb choice and the reference white. The brute force approach proposed by BartoszKP depends on these factors, and may need to be re-runned depending on the platform. – SeF Jan 22 '17 at 18:39

3 Answers3

31

In practice the number of all possible RGB colours is finite, so the L*a*b* space is bounded. It is easy to find the ranges of coordinates with the following simple program:

Color c;

double maxL = double.MinValue;
double maxA = double.MinValue;
double maxB = double.MinValue;
double minL = double.MaxValue;
double minA = double.MaxValue;
double minB = double.MaxValue;

for (int r = 0; r < 256; ++r)
    for (int g = 0; g < 256; ++g)
        for (int b = 0; b < 256; ++b)
        {
            c = Color.FromArgb(r, g, b);

            Lab lab = c.ToLab();

            maxL = Math.Max(maxL, lab.L);
            maxA = Math.Max(maxA, lab.A);
            maxB = Math.Max(maxB, lab.B);
            minL = Math.Min(minL, lab.L);
            minA = Math.Min(minA, lab.A);
            minB = Math.Min(minB, lab.B);
        }

Console.WriteLine("maxL = " + maxL + ", maxA = " + maxA + ", maxB = " + maxB);
Console.WriteLine("minL = " + minL + ", minA = " + minA + ", minB = " + minB);

or a similar one using any other language.

So, CIELAB space coordinate ranges are as follows:

L in [0, 100]

A in [-86.185, 98.254]

B in [-107.863, 94.482]

and the answer is:

Lab lab = c.ToLab();

result.Add(Tuple.Create(
    1.0 * lab.L / 100,
    1.0 * (lab.A + 86.185) / 184.439,
    1.0 * (lab.B + 107.863) / 202.345);
Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
  • 7
    Define RGB. sRGB? Adobe RGB? – Cole Tobin Oct 06 '13 at 00:17
  • @ColeJohnson Very interesting point. I'd argue that this works for any concrete version of RGB, but I'm not sure. Here the context is defined as GDI+, and in any other language as this language's graphics library. – BartoszKP Oct 06 '13 at 00:22
  • FWIW The CIELAB space includes 'imaginary' colors by design: http://en.wikipedia.org/wiki/Lab_color_space – theodox Oct 17 '13 at 21:47
  • 1
    I just wanted to add that perhaps using maxA.ToString("R") instead of just maxA will give you a more detailed (though maybe not more accurate) result. In my little experience with L*a*b* colors (using .NET), I've found roundtripping the values RGB->L*a*b*->RGB doesn't always work due to floating point calculation errors. My code throws on values out of range, so I had to modify the matrix values very slightly from easyrgb.com. My results turned out to be: L in [0, 100] A in [-86.188001161251748, 98.249414905267] B in [-107.85362559736171, 94.487327241851375] – skataben May 15 '14 at 17:14
  • 2
    Why does most answers use different bounds? For instance, Adobe uses -128-127 for A and B, here it is 184, and other places I've noticed it varies. Does it have something to do with color profiles or is this is a special form of LAB? –  Jan 25 '17 at 17:40
  • adobe implements lots of things in weird ways in order to gain a little bit of performance so they probably chose -128 to 127 because thats the range of signed char in c/c++ – nemesit Jan 28 '17 at 22:20
  • I suspect the L*a*b* bounds output above are due to numeric effects. When using floats, the range for a* and b* probably is from -100.0 to 100.0, while the range of L* should be from 0 to 100.0. Note that L*a*b* is a theoretical color space that no practical device can create. When representing L*a*b* as 24 bit integers, 8 bit are used for each component, so the L* is 0 to 255, a* and b* are both -128 to 127. – U. Windl Mar 07 '19 at 19:34
  • In opencv, L,a,b ranges from 1 to 255. Pls refer here: https://rodrigoberriel.com/2014/11/opencv-color-spaces-splitting-channels/ – Abhinav Ravi Sep 21 '21 at 12:17
15

Normally, the following values work because it is the standard output of common color conversion algorithms:

  • L* axis (lightness) ranges from 0 to 100

  • a* and b* (color attributes) axis range from -128 to +127

More information can be found here.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
bortizj
  • 161
  • 1
  • 6
1

If the Lab-conversion code is implemented in accord with the Lab-colors definition (see, for example Lab color space), then function f(...), which is used for defining L, a and b changes within [4/29,1], thefore

L = 116 * f(y) - 16 is in [0,100]
a = 500 * (f(x)-f(y)) is in [-500*25/29, 500*25/29]
b = 200 * (f(y)-f(z)) is in [-200*25/29, 200*25/29]

Some people (like bortizj in his reply) normalize these values to range, which a byte-variable can hold. So you have to analyze the code in order to determine, what range it produces. But again, the formulas in Wiki will give you the range above. The same range will give you the code here

John Smith
  • 1,027
  • 15
  • 31
  • Thanks for your answer. IIRC I was using the exact conversion algorithms from the EasyRGB web site you're referring to (RGB -> XYZ -> LAB) and the result is different - are you sure the values you provide are correct? – BartoszKP Apr 21 '16 at 13:01
  • 1
    I think you conclusion on the a* and b* ranges is wrong: The factors 200 and 500 merely reflect the difference in human color resolution regarding blue-yellow vs. red-green. – U. Windl Mar 07 '19 at 19:45