Using NumPy over JPEG images
I was taking a MOOC on Data Science [Fair Disclosure: I have enrolled and started many data science courses but have yet to complete a single one].
At the end of first week of lecture, they taught us that JPEG images are comprised of three dimensional arrays of the shape M x N x 3 with the final dimension comprised of RGB values. By playing with the RGB values we can change the colors of the image. They also have a few lines of code which showed how playing with the numbers increased or decreased the contrast.
This was a light bulb moment. I decided why not use my newfound skills to make the convert an image to grayscale. Googling the query landed me on the following page which told me that in there software they use three algorithms to convert the image to grayscale.
The lightness method averages the most prominent and least prominent colors: (max(R, G, B) + min(R, G, B)) / 2.
The average method simply averages the values: (R + G + B) / 3.
The luminosity method is a more sophisticated version of the average method. It also averages the values, but it forms a weighted average to account for human perception. We’re more sensitive to green than other colors, so green is weighted most heavily. The formula for luminosity is 0.21 R + 0.72 G + 0.07 B.
So I did a google search for a high definition sunflower image, and tried to apply the aforementioned methods to turn the image into grayscale. I used the following image.
I will reproduce the entire code for the first example. For the subsequent example, I will just post the method code.
%matplotlib inline import numpy as np from scipy import misc import matplotlib.pyplot as plt
from skimage import data photo_data = misc.imread("./sunset.jpg")
x,y,z=photo_data.shape ## where z is the RGB dimension
### Method block begin photo_data[:] = photo_data.mean(axis=-1,keepdims=1) ### Method Block ends
Not to bad if I say so myself.
Here we take the weighted average
W = [0.2,0.5,0.3] # weights W_mean = np.tensordot(photo_data,W, axes=((-1,-1)))[...,None] photo_data[:] = W_mean.astype(photo_data.dtype)
It took me a while to work out how to get it to work in a single line of code without using loops.
photo_data[:] = np.max(photo_data,axis=-1,keepdims=1)/2+np.min(photo_data,axis=-1,keepdims=1)/2
You sometimes you see images wherein everything is grayscale except one color is brought out. I wanted to see if I could keep sunflowers yellow. RGB code for Yellow is [255,255,0]. So I selected a small section of the largest sunflower and checked out the color codes.
photo_data = misc.imread("./sunset.jpg") photo_data[495:505,390:405]
It appeared that the Yellow area has minimum value of 210 for R dimension and around 160 for G dimension. So I added a check that if value of dimension is less than 210 and 160 for R and G respectively, then apply the average method:
for i in range(x): for j in range(y): if (photo_data[i,j,0]<210 and photo_data[i,j,1]<160): photo_data[i,j]= np.mean(photo_data[i,j])
I am sure there must be a masking method that achieves the same result more efficiently. But right now For Loop is it.
Obviously, one can easily convert images to grayscale with a click of a button. Moreover, I believe there are existing libraries in Python that do this. But for me it was enlightening to get a peek under the hood (rather trying to build the process) and have so much control over it.