First, while the coefficients are correct for sRGB or Rec709, to convert from color to Y...
(0.2126*R + 0.7152*G + 0.0722*B)
...they require that the RGB channels are all linearized to remove any gamma encoding.
Second, these are the coefficients for Rec709 or sRGB, but other colorspaces require different coefficients.
A Library
I recommend KenSolaar's ColourScience, a python library that can do things like convert to a luminance, and uses numpy and vectorized math.
https://github.com/colour-science/colour
Conversion and tracking pixel values
Converting an sRGB pixel to luminance is straight forward:
- Parse the sRGB value into discrete RʹGʹBʹ values. We'll assume 8bit.
- Divide each individually by 255.0
- Remove the TRC (aka gamma).
- The simple way for sRGB and several other colorspaces is to apply a power curve to each RʹGʹBʹ channel using an exponent of ^2.2.
- Then apply coefficients and sum for Luminance Y.
- (0.2126 * R + 0.7152 * G + 0.0722 * B)
Putting all that together:
imgPixel = 0xAACCFF
R = (imgPixel & 0xFF0000) >> 16
G = (imgPixel & 0x00FF00) >> 8
B = (imgPixel & 0x0000FF)
Y = 0.2126*(R/255.0)**2.2 + 0.7152*(G/255.0)**2.2 + 0.0722*(B/255.0)**2.2
That's the simplest while still being reasonably accurate, however some purists might suggest using the IEC specified sRGB TRC, which is piecewise and computationally more expensive:
# Piecewise sRGB TRC to Linear (only red is shown in this example)
if R <= 0.04045:
R / 12.92
else:
(( R + 0.055) / 1.055) ** 2.4
Y Not?
The next question was, how to determine the pixels, and that's just creating and populating a list (array) with the coordinates and color value for pixels that match the luminance.
Do you want to quantize back to 8 bit integer values for the luminance? Or stay in a 0.0 to 1.0 and define a range? The later is usually most useful, so let's do that.
For cv2.imread('test.jpg',1)
don't set the flag to 0 — you're going to make your own greyscale and you want to save the color pixel values, correct?
So using the earlier example but with a ternary piecewise TRC method and adding a loop that appends an array for the found pixels:
# declare some variables
Llo = 0.18 # Lo luminance range
Lhi = 0.20 # Hi range choosing pixels between here and Lo
results = [[]]
imgPixel = 0x000000
img = cv2.imread('test.jpg',1) # set flag to 1 (or omit) for color — you're going to make your own greyscale.
rows,cols = img.shape
for ir in range(rows):
for ic in range(cols):
imgPixel = img[ir,ic]
R = ((imgPixel & 0xFF0000) >> 16) / 255.0
G = ((imgPixel & 0x00FF00) >> 8 ) / 255.0
B = ((imgPixel & 0x0000FF) ) / 255.0
R = R / 12.92 if R <= 0.04045 else (( R + 0.055) / 1.055) ** 2.4
G = G / 12.92 if G <= 0.04045 else (( G + 0.055) / 1.055) ** 2.4
B = B / 12.92 if B <= 0.04045 else (( B + 0.055) / 1.055) ** 2.4
Y = 0.2126 * R + 0.7152 * G + 0.0722 * B
# If the Y is in range, then append the pixel coordinates and color value to the array
if Y>Llo or Y<Lhi: results.append([ ic, ir, imgPixel ])
# CAVEAT: This code is entered but not tested in Python.
There's very likely a way to vectorize this, so worth it to look at the colour-science library I linked above as it does so where possible.
for
loops. – Mark Setchell