Skip to content

Instantly share code, notes, and snippets.

@manojpandey
Created August 12, 2016 10:34
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save manojpandey/f5ece715132c572c80421febebaf66ae to your computer and use it in GitHub Desktop.
Save manojpandey/f5ece715132c572c80421febebaf66ae to your computer and use it in GitHub Desktop.
RGB to CIELab color space conversion
# RGB to Lab conversion
# Step 1: RGB to XYZ
# http://www.easyrgb.com/index.php?X=MATH&H=02#text2
# Step 2: XYZ to Lab
# http://www.easyrgb.com/index.php?X=MATH&H=07#text7
def rgb2lab(inputColor):
num = 0
RGB = [0, 0, 0]
for value in inputColor:
value = float(value) / 255
if value > 0.04045:
value = ((value + 0.055) / 1.055) ** 2.4
else:
value = value / 12.92
RGB[num] = value * 100
num = num + 1
XYZ = [0, 0, 0, ]
X = RGB[0] * 0.4124 + RGB[1] * 0.3576 + RGB[2] * 0.1805
Y = RGB[0] * 0.2126 + RGB[1] * 0.7152 + RGB[2] * 0.0722
Z = RGB[0] * 0.0193 + RGB[1] * 0.1192 + RGB[2] * 0.9505
XYZ[0] = round(X, 4)
XYZ[1] = round(Y, 4)
XYZ[2] = round(Z, 4)
# Observer= 2°, Illuminant= D65
XYZ[0] = float(XYZ[0]) / 95.047 # ref_X = 95.047
XYZ[1] = float(XYZ[1]) / 100.0 # ref_Y = 100.000
XYZ[2] = float(XYZ[2]) / 108.883 # ref_Z = 108.883
num = 0
for value in XYZ:
if value > 0.008856:
value = value ** (0.3333333333333333)
else:
value = (7.787 * value) + (16 / 116)
XYZ[num] = value
num = num + 1
Lab = [0, 0, 0]
L = (116 * XYZ[1]) - 16
a = 500 * (XYZ[0] - XYZ[1])
b = 200 * (XYZ[1] - XYZ[2])
Lab[0] = round(L, 4)
Lab[1] = round(a, 4)
Lab[2] = round(b, 4)
return Lab
@SinBirb
Copy link

SinBirb commented Oct 26, 2017

Hey, I used your gist in mine, I hope that's ok: https://gist.github.com/SinBirb/f71ab664d6f6bd3bbea7992bb264f2cf

@mrgriffin
Copy link

mrgriffin commented Mar 12, 2019

I think that this line:

value = (7.787 * value) + (16 / 116)

Is buggy. If you're using float elsewhere to make the divisions produce float results (i.e. for Python 2 support) then I'd expect you to have to write 16.0 / 116.0 (technically only one would have to be a float literal).

$ python2
>>> 16/116
0
$ python3
>>> 16/116
0.13793103448275862

@weilueluo
Copy link

According to wikipedia, line 29 should be: Z = RGB[0] * 0.0193 + RGB[1] * 0.1192 + RGB[2] * 0.9504, that is, changing 0.9505 to 0.9504

@i-make-robots
Copy link

Hi! I used your RGB>LAB code as confirmation mine was working. I then used it in SLIC superpixel segmentation.

@manojpandey
Copy link
Author

Great. Any feedback @i-make-robots? :)

@i-make-robots
Copy link

Your work is appreciated and I thank you. :)

@roguealexander
Copy link

Super helpful stuff! Just used the alg and values (hard to find in a digestible way) in c#

@MeltyMoon
Copy link

im gonna convert it to javascript for a module im making

@manojpandey
Copy link
Author

@JayRizuri feel free to :D Thanks!

@timotheeg
Copy link

Thanks for that! In case this helps someone, I ported to JS as follow:

function rgb2lab_normalizeRgbChannel(channel) {
	channel /= 255;

	return 100 * (channel > 0.04045
		? Math.pow((channel + 0.055) / 1.055, 2.4)
		: channel / 12.92
	);
}

function rgb2lab_normalizeXyzChannel(channel) {
	return (channel > 0.008856)
		? Math.pow(channel, 1/3)
		: (7.787 * channel) + (16 / 116);
}

function rgb2lab([r, g, b]) {
	r = rgb2lab_normalizeRgbChannel(r);
	g = rgb2lab_normalizeRgbChannel(g);
	b = rgb2lab_normalizeRgbChannel(b);

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

	// Observer= 2°, Illuminant= D65
	X = rgb2lab_normalizeXyzChannel(X / 95.0470);
	Y = rgb2lab_normalizeXyzChannel(Y / 100.0);
	Z = rgb2lab_normalizeXyzChannel(Z / 108.883);

	return [
		(116 * Y) - 16, // L
		500 * (X - Y),  // a
		200 * (Y - Z),  // b
	];
}

@TellAnAx
Copy link

TellAnAx commented Aug 4, 2022

This is the code written in R.
I added a tiny extension, making the maximum of the RGB color value range variable instead of hard-coding it.

https://gist.github.com/TellAnAx/06ace666a92d0849d6b774de1790cc7a

rgb2lab <- function(inputColor, maxColorValue = 255){

    num <- 1
    RGB <- c(0, 0, 0)

    for(value in inputColor){    
      value <- value / maxColorValue

        if(value > 0.04045){
          value <- ((value + 0.055) / 1.055)^2.4
        } else{
          value <- value / 12.92
        }
          

        RGB[num] <- value * 100
        num <- num + 1
    }

    XYZ <- c(0, 0, 0)

    X = RGB[1] * 0.4124 + RGB[2] * 0.3576 + RGB[3] * 0.1805
    Y = RGB[1] * 0.2126 + RGB[2] * 0.7152 + RGB[3] * 0.0722
    Z = RGB[1] * 0.0193 + RGB[2] * 0.1192 + RGB[3] * 0.9505
    XYZ[1] = round(X, 4)
    XYZ[2] = round(Y, 4)
    XYZ[3] = round(Z, 4)

    # Observer= 2°, Illuminant= D65
    XYZ[1] = XYZ[1] / 95.047         # ref_X =  95.047
    XYZ[2] = XYZ[2] / 100.0          # ref_Y = 100.000
    XYZ[3] = XYZ[3] / 108.883        # ref_Z = 108.883

    num = 1
    for(value in XYZ){
            if(value > 0.008856){
            value = value ** (0.3333333333333333)  
            } else{
              value = (7.787 * value) + (16 / 116)
            }
      
        XYZ[num] <- value
        num <- num + 1  
    }



    Lab <- c(0, 0, 0)

    L = (116 * XYZ[2]) - 16
    a = 500 * (XYZ[1] - XYZ[2])
    b = 200 * (XYZ[2] - XYZ[3])

    Lab[1] = round(L, 4)
    Lab[2] = round(a, 4)
    Lab[3] = round(b, 4)

    return(Lab)
}

@i-make-robots
Copy link

@rakesh5283
Copy link

Anyone have the numpy conversion for this code.. this takes too much time

@werdl
Copy link

werdl commented Nov 5, 2023

This is really useful, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment