Reputation: 224
I'm trying to come up with an algorithm to give images a score for the amount of green that they contain by iterating through the pixels using PIL. I have come up with a few different methods so far but they all seem flawed.
The first one sums all of the values of g in rgb and divides it but the total of all 3 colours.
def percent_green(img_file):
red = 0
green = 0
blue = 0
img = Image.open(img_file)
pixels = img.load()
width, height = img.size
for x in range(width):
for y in range(height):
rgb = pixels[x, y]
red += rgb[0]
green += rgb[1]
blue += rgb[2]
percent = green / (red + blue + green)
return percent * 100
This method succeeds at ranking images by how green they are but an image composed of just rgb(100, 200, 100) for example would only score 50% despite being very green.
The other method I have thought of is simply determining what percentage of pixels contain more green than red or blue.
def percent_green(img_file):
img = Image.open(img_file)
pixels = img.load()
width, height = img.size
total_green = 0
for x in range(width):
for y in range(height):
rgb = pixels[x, y]
if rgb[1] > rgb[0] and rgb[1] > rgb[2]: #if green predominant colour
total_green += 1
percent = total_green /(width * height)
return percent * 100
The problem with this option is that colours like rgb(0, 1, 0) or rgb(244, 255, 244) would be counted as green. I would ideally like some way to rank the "greenness" of a colour.
I would be very grateful for any suggestions of algorithms which can give a better percentage of how green an image is. Any suggestions on which of my current algorithms is better or how to improve them are also welcome.
Upvotes: 4
Views: 2510
Reputation: 319
I had the exact same problem today. Here is it without using numpy. Instead I use list comprehension,lambdas,zip. You have to convert RGB to HSL color space.
#call it
print(
is_img_hue_green(
Image.open(img_file_path)
)
)
def is_img_hue_green(pil_img):
#if greater than threshold of 18%
#green 120 +- 60; values from pillow 0-255
minvalue = int(60 * 0.708333333)
maxvalue = int(180 * 0.708333333)
if img_return_threshold_hsv(pil_img, minvalue,maxvalue) > 0.40 :
return True
else:
return False
def img_return_threshold_hsv(pil_img, min_hue, max_hue):
hue_band_iterable = list(pil_img.convert( 'HSV' ).getdata(0)) #convert getdata to list
sat_band_iterable = list(pil_img.convert( 'HSV' ).getdata(1))
#fill all with 1s, if all 1s Bitwise AND returns 1; if any 0=0
bitlist = [1 for i in range( pil_img.width * pil_img.height )] #fill with 1s
func_hue = lambda hue : 1 if hue >= min_hue and hue <= max_hue else 0
func_sat = lambda sat : 1 if sat >= 50 else 0
green_mask_h = [func_hue(hue) for hue in hue_band_iterable ] #1 if True
green_mask_s = [func_sat(sat) for sat in sat_band_iterable ]
bitlist = [x & y & z for x, y, z in zip(bitlist, green_mask_h, green_mask_s)]
#zip returns a tuple (x,y,z) of elements side by side, if all 1 return 1
return sum(bitlist) / (pil_img.width * pil_img.height)
Play around with the threshold(0.4=40%) and saturation level(50 vs 0-255)
Upvotes: 1
Reputation: 207385
One potential way is to look at the image in "Hue Saturation and Value" colourspace HSV colourspace. Then you can look at the Hue and see if it corresponds to the range of greens you want to identify.
On the linked HSV colour wheel, you can see Reds have a Hue of 0, Greens are 120 and Blues are 240. However, PIL wants to save those values in an unsigned 8-bit number with a range 0..255 rather than 0..360, so all the values are scaled by 255/360. So, in PIL, Reds come out around 0, Greens come out at around 85 and Blues come out at around 170.
So you could count all pixels that fall between 80..90 as green with the code below. Note that it is generally a really bad idea to iterate over pixels in Python - it is dog slow - so I use Numpy. If you don't want to use Numpy, just get the Hue channel like I do below and iterate over the pixels counting the ones in the range you want in regular Python:
from PIL import Image
import numpy as np
# Load image and convert to HSV
im = Image.open('bp-1.jpg').convert('HSV')
# Extract Hue channel and make Numpy array for fast processing
Hue = np.array(im.getchannel('H'))
# Make mask of zeroes in which we will set greens to 1
mask = np.zeros_like(Hue, dtype=np.uint8)
# Set all green pixels to 1
mask[(Hue>80) & (Hue<90)] = 1
# Now print percentage of green pixels
print((mask.mean()*100)
If I run that on his image, I get 4%
Whereas with this image I get 31%
You could extract and consider the Saturation too, if you wanted to only count highly saturated colours.
Upvotes: 7