In [237]:
import numpy as np
import scipy.misc
from PIL import Image
import colorsys
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (20,10)
In [324]:
def gaussian_weights(source, target, sigma):
return np.exp(-(source - target)**2 / (2 * sigma**2)) / (sigma * np.sqrt(2 * np.pi))
def hue(source, target, amount):
"""Move the hue of source closer to the target,
assuming source and target angles between [-pi; pi],
according to their distance
"""
if amount != 0:
sigma = np.pi /2.
x = np.cos(source) + np.cos(target) * gaussian_weights(np.cos(source), np.cos(target), sigma) * sigma * amount
y = np.sin(source) + np.sin(target) * gaussian_weights(np.sin(source), np.sin(target), sigma) * sigma * amount
return np.arctan2(y, x)
else :
return source
def saturation(source, amount):
sigma = 0.5
if amount != 0.:
return source + amount * gaussian_weights(source, 1. - source, sigma)
else:
return source
def normal2rad(theta):
"""Remap the hue channel to [-pi; pi] radians assuming source is in [0;1]"""
# Rescale [0; 1] to [0; 2 pi]
theta = theta * 2 * np.pi
# Remap [pi; 2 pi] to [-pi; 0]
negative = theta > np.pi # Boolean array : put 1 where theta > pi
theta[negative] = - (2 * np.pi - theta[negative])
return theta
def rad2normal(theta):
"""Remap the hue channel to [0; 1] assuming source is in [-pi; pi] radians"""
# Remap [-pi; 0] to [0; 2 pi]
negative = theta < 0 # Boolean array : put 1 where theta < 0
theta[negative] = 2 * np.pi + theta[negative]
# Rescale to [0;1]
theta = theta / (2 * np.pi)
return theta
In [239]:
def highlights(pixels, L_max, sigma):
"""Build a progressive luminance mask between [0;1],
assuming luminance in [0;1], as a gaussian function centered on the max of luminance"""
return gaussian_weights(pixels, L_max, sigma)
def midtones(pixels, sigma):
"""Build a progressive luminance mask between [0;1],
assuming luminance in [0;1], as a gaussian function centered on 0.5"""
return gaussian_weights(pixels, 0.5, sigma)
def shadows(pixels, L_min, sigma):
"""Build a progressive luminance mask between [0;1],
assuming luminance in [0;1], as a gaussian function centered on the min of luminance"""
return gaussian_weights(pixels, L_min, sigma)
def luma_masks(pixels, sigma=1/8.):
"""Utility function to build 3 luma masks at once and normalize the weights"""
L_max = np.amax(pixels)
L_min = np.amin(pixels)
high = highlights(pixels, L_max, 2 * sigma)
low = shadows(pixels, L_min, 2 * sigma)
mid = midtones(pixels, sigma) * (1 + 2 * sigma)
norm = high + low + mid
return low/norm, mid/norm, high/norm
In [240]:
theta = np.arange(0, 1, 0.001)
In [323]:
y = saturation(theta, -0.1)
plt.plot(theta, y)
plt.plot(theta, theta)
plt.show()
In [280]:
y = rad2normal(hue(normal2rad(theta), 0, 1.))
plt.plot(theta, y)
plt.plot(theta, theta)
plt.show()
In [281]:
y = rad2normal(hue(normal2rad(theta), np.pi, 1))
plt.plot(theta, y)
plt.plot(theta, theta)
plt.show()
In [282]:
y = rad2normal(hue(normal2rad(theta), np.pi/2, 1))
plt.plot(theta, y)
plt.plot(theta, theta)
plt.show()
In [283]:
x = np.arange(0, 1, 0.01)
low, mid, high = luma_masks(x)
plt.plot(x, high)
plt.plot(x, low)
plt.plot(x, mid)
plt.show()
In [245]:
# Load image
im = Image.open("../img/153412.jpg")
im = scipy.misc.imresize(im, 0.25, interp='lanczos')
im = Image.fromarray(im, 'RGB')
In [297]:
# Convert RGB image to HSL
r,g,b = im.split()
H = []
S = []
L = []
for red, green, blue in zip(r.getdata(),g.getdata(),b.getdata()):
h, s, l = colorsys.rgb_to_hsv(red/255.,green/255.,blue/255.)
H.append(h)
S.append(s)
L.append(l)
# Convert to Numpy arrays
H = np.array(H)
L = np.array(L)
S = np.array(S)
# Convert hue to radians
H = normal2rad(H)
In [298]:
# Build the luminance masks
low, mid, high = luma_masks(L)
In [299]:
# Adjust the main hue
H_bis = hue(H, np.pi/6., 1)
In [300]:
# Adjust hue selectively for luminance zones
H_bis = hue(H_bis, np.pi/3., 1) * high + hue(H_bis, 4.2, 1) * mid + hue(H_bis, 4.2, 1) * low
In [304]:
# Adjust saturation
S_bis = saturation(S, 0.2) * high + saturation(S, 0.1) * high + saturation(S, 0) * low
#S_bis = np.clip(S_bis, 0., 1.)
S_bis.mean()
Out[304]:
In [292]:
# Convert hue back to normal [0;1]
H_bis = rad2normal(H_bis)
# Convert back HSL image to RGB
rout = []
gout = []
bout = []
for h, s, l in zip(H_bis, S_bis, L):
rd, gn, bl = colorsys.hsv_to_rgb(h, s, l)
rout.append(rd * 255)
gout.append(gn * 255)
bout.append(bl * 255)
In [293]:
# Save image
r.putdata(rout)
g.putdata(gout)
b.putdata(bout)
newimg = Image.merge('RGB',(r,g,b))
#newimg.save('153412-hue-shift.jpg')
print("New : push main color to orange (100 %), hightlights to yellow (100 %), midtones and shadows to blue (100 %)")
plt.imshow(newimg)
plt.show()
print("original")
plt.imshow(im)
plt.show()
In [ ]: