How to convert RGB image pixels to L*a*b*?

You can do it with PIL/Pillow using the built-in Colour Management System and building a transform like this:

#!/usr/local/bin/python3

import numpy as np
from PIL import Image, ImageCms

# Open image and discard alpha channel which makes wheel round rather than square
im = Image.open('colorwheel.png').convert('RGB')

# Convert to Lab colourspace
srgb_p = ImageCms.createProfile("sRGB")
lab_p  = ImageCms.createProfile("LAB")

rgb2lab = ImageCms.buildTransformFromOpenProfiles(srgb_p, lab_p, "RGB", "LAB")
Lab = ImageCms.applyTransform(im, rgb2lab)

And Lab is now your image in Lab colourspace. If you carry on and add the following lines to the end of the above code, you can split the Lab image into its constituent channels and save them each as greyscale images for checking.

# Split into constituent channels so we can save 3 separate greyscales
L, a, b = Lab.split()

L.save('L.png')
a.save('a.png')
b.save('b.png')

So, if you start with this image:

enter image description here

you will get this as the L channel:

enter image description here

this as the a channel:

enter image description here

and this the b channel:

enter image description here

Being non-scientific for a moment, the a channel should be negative/low where the image is green and should be high/positive where the image is magenta so it looks correct. And the b channel should be negative/low where the image is blue and high/positive where it is yellow, so that looks pretty good to me! As regards the L channel, the RGB to greyscale formula is (off the top of my head) something like:

L = 0.2*R + 0.7*G + 0.1*B

So you would expect the L channel to be much brighter where the image is green, and darkest where it is blue.


Alternatively, you can do it with the scikit-image module, maybe even more simply like this:

import numpy as np
from skimage import color, io

# Open image and make Numpy arrays 'rgb' and 'Lab'
rgb = io.imread('image.png')
Lab = color.rgb2lab(rgb)

I am not 100% sure of the scaling, but I suspect the L channel is a float in range 0..100, and that a and b are also floats in range -128..+128, though I may be wrong!

With my colour wheel image above I got the following minima/maxima for each channel:

Lab[:,:,0].min()     # L min
32.29567256501352

Lab[:,:,0].max()     # L max
97.13950703971322

Lab[:,:,1].min()     # a min
-86.18302974439501

Lab[:,:,1].max()     # a max
98.23305386311316

Lab[:,:,2].min()     # b min
-107.85730020669489

Lab[:,:,2].max()     # b max
94.47812227647823

from colormath.color_objects import sRGBColor, LabColor
from colormath.color_conversions import convert_color 

def rgb_to_cielab(a):
    """
    a is a pixel with RGB coloring
    """
    a1,a2,a3 = a/255

    color1_rgb = sRGBColor(a1, a2, a3);

    color1_lab = convert_color(color1_rgb, LabColor);

    return color1_lab

rgb_to_cielab(np.array([255,0,255]))

Output: LabColor(lab_l=60.32364943499053,lab_a=98.23532017664644,lab_b=-60.83501679458592)