CreativelyChris
CreativelyChris

Reputation: 93

Getting RGB Values of Pixels Based on Luminance in Python

Trying to figure out how to accomplish this task: I'd want to select pixels of an image based on luminance, and then grab the rgb values of those pixels.

My initial thought was to use OpenCV to make a histogram on the greyscale of the image:

img = cv2.imread('test.jpg',0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])

but I wasn't sure how to then identify where those pixels in a particular bin of my histogram are in the image?

Alternatively I found this formula to get luminance:

(0.2126*R + 0.7152*G + 0.0722*B)

So I guess I could iterate over ever pixel in the image with that formula and grab the ones that match my chosen luminance level?

Is there a better way to accomplish this in Python?

Upvotes: 1

Views: 2936

Answers (1)

Myndex
Myndex

Reputation: 5364

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:

  1. Parse the sRGB value into discrete RʹGʹBʹ values. We'll assume 8bit.
  2. Divide each individually by 255.0
  3. 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.
  4. 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 and 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.

Upvotes: 2

Related Questions