0
votes

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?

1
Yes, the better way is to avoid loops and iterating over pixels/images in Python. Use Numpy, Scipy, or OpenCV but, as far as possible, never for loops.Mark Setchell
Compute the luminance image via cv2.cvt() from your input, which is likely just converting to gray or find BGR2... for you luminance formula. The threshold it for the range of luminance you want. See cv2.inRange(). Then apply the thresholded image to your image via numpy np.where to get all the pixels from your input that are under the white of the thresholded image.fmw42
You might get some more help if you could be a bit more specific... do you have a sample input and output image, for example? Could you be more specific about what "grabbing" the matching pixels involves - do you mean to make a list to sort and find unique colours, or masking them and only displaying those on a black background...? Thank you.Mark Setchell

1 Answers

1
votes

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 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.