How to change the colour of pixels in an image depending on their initial luminosity?

Viewed 420

The aim is to take a coloured image, and change any pixels within a certain luminosity range to black. For example, if luminosity is the average of a pixel's RGB values, any pixel with a value under 50 is changed to black.

I’ve attempted to begin using PIL and converting to grayscale, but having trouble trying to find a solution that can identify luminosity value and use that info to manipulate a pixel map.

2 Answers

There are many ways to do this, but the simplest and probably fastest is with Numpy, which you should get accustomed to using with image processing in Python:

from PIL import Image
import numpy as np

# Load image and ensure RGB, not palette image
im = Image.open('start.png').convert('RGB')

# Make into Numpy array
na = np.array(im)

# Make all pixels of "na" where the mean of the R,G,B channels is less than 50 into black (0)
na[np.mean(na, axis=-1)<50] = 0

# Convert back to PIL Image to save or display
result = Image.fromarray(na)
result.show()

That turns this:

enter image description here

Into this:

enter image description here


Another slightly different way would be to convert the image to a more conventional greyscale, rather than averaging for the luminosity:

# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')

# Calculate greyscale version
grey = im.convert('L')

# Point process over pixels to make mask of darker ones
mask = grey.point(lambda p: 255 if p<50 else 0)

# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=mask)

enter image description here

Notice that the blue channel is given considerably less significance in the ITU-R 601-2 luma transform that PIL uses (see the lower 114 weighting for Blue versus 299 for Red and 587 for Green) in the formula:

L = R * 299/1000 + G * 587/1000 + B * 114/1000

so the blue shades are considered darker and become black.


Another way would be to make a greyscale and a mask as above. but then choose the darker pixel at each location when comparing the original and the mask:

from PIL import Image, ImageChops
im = Image.open('start.png').convert('RGB')
grey = im.convert('L')
mask = grey.point(lambda p: 0 if p<50 else 255)
res = ImageChops.darker(im, mask.convert('RGB'))

That gives the same result as above.


Another way, pure PIL and probably closest to what you actually asked, would be to derive a luminosity value by averaging the channels:

# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')

# Calculate greyscale version by averaging R,G and B
grey = im.convert('L', matrix=(0.333, 0.333, 0.333, 0))

# Point process over pixels to make mask of darker ones
mask = grey.point(lambda p: 255 if p<50 else 0)

# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=mask)

enter image description here


Another approach could be to split the image into its constituent RGB channels, evaluate a mathematical function over the channels and mask with the result:

from PIL import Image, ImageMath

# Load image and ensure RGB
im = Image.open('start.png').convert('RGB')

# Split into RGB channels
(R, G, B) = im.split()

# Evaluate mathematical function over channels
dark = ImageMath.eval('(((R+G+B)/3) <= 50) * 255', R=R, G=G, B=B)

# Paste black (i.e. 0) into image where mask indicates it is dark
im.paste(0, mask=dark)

I created a function that returns a list with True if the pixel has a luminosity of less than a parameter, and False if it doesn't. It includes an RGB or RGBA option (True or False)

def get_avg_lum(pic,avg=50,RGBA=False):
  num=3
  numd=4
  if RGBA==False:
    num=2
    numd=3
  li=[[[0]for y in range(0,pic.size[1])] for x in range(0,pic.size[0])]
  for x in range(0,pic.size[0]):
    for y in range(0,pic.size[1]):
      if sum(pic.getpixel((x,y))[:num])/numd<avg:
        li[x][y]=True
      else:
        li[x][y]=False
  return(li)
a=get_avg_lum(im)

The pixels match in the list, so (0,10) on the image is [0][10] in the list.

Hopefully this helps. My module is for standard PIL objects.

Related