Michele Pratusevich
Reach me:
The basic ingredients of any Instagram filter:
Let's set up our variables and load our image.
import matplotlib
matplotlib.use('Agg')
%matplotlib inline
import matplotlib.pyplot as plt
import skimage
from skimage import io
from skimage import filters
import numpy as np
original_image = skimage.img_as_float(skimage.io.imread("skyline.jpg"))
matplotlib.rcParams['xtick.major.size'] = 0
matplotlib.rcParams['ytick.major.size'] = 0
matplotlib.rcParams['xtick.labelsize'] = 0
matplotlib.rcParams['ytick.labelsize'] = 0
def plot_side_by_side(first, second, t):
if t == 'image':
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.imshow(first)
ax2.imshow(second)
elif t == 'hist':
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3))
(_, _, _) = ax1.hist(first.flatten(), bins=255)
(_, _, _) = ax2.hist(second.flatten(), bins=255)
ax1.set_title("Original")
ax2.set_title("Transformed")
plt.show()
Sharpening: Take a blurred version of an image and subtract it from the original image. Can scale the blurred / original image as needed.
def sharpen(image, a, b, sigma=10):
blurred = skimage.filters.gaussian(image, sigma=sigma, multichannel=True)
sharper = np.clip(image * a - blurred * b, 0, 1.0)
return sharper
sharpened = sharpen(original_image, 1.3, 0.3)
plot_side_by_side(original_image, sharpened, "image")
(Yes, you can also use this same function to just blur your image if you want to)
blurred = sharpen(original_image, 0, -1.0)
plot_side_by_side(original_image, blurred, "image")
The range [0, 1] is broken into the range of buckets and linearly interpolate between them. It's like the same as a curve adjustment in any photo editing software.
plot_side_by_side(r, r2, "hist")
plt.show()
Let's make a function that takes a single channel of an image and adjust it according to a list of values.
def channel_adjust(channel, values):
# flatten
orig_size = channel.shape
flat_channel = channel.flatten()
adjusted = np.interp(
flat_channel,
np.linspace(0, 1, len(values)),
values)
# put back into image form
return adjusted.reshape(orig_size)
One more set of helper functions.
# skimage loads images in RGB format
def split_image_into_channels(image):
red_channel = image[:, :, 0]
green_channel = image[:, :, 1]
blue_channel = image[:, :, 2]
return red_channel, green_channel, blue_channel
# and we'll have to undo it
def merge_channels(red_channel, green_channel, blue_channel):
return np.stack([red_channel, green_channel, blue_channel], axis=2)
So you believe this works:
r, g, b = split_image_into_channels(original_image)
im = merge_channels(r, g, b)
plt.imshow(im)
plt.show()
The (now-defunct) Gotham filter from Instagram is like this:
Mid-tone contrast boost.
r_boost_lower = channel_adjust(r, [0, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 1.0])
plot_side_by_side(r, r_boost_lower, "hist")
r_boost_img = merge_channels(r_boost_lower, g, b)
plot_side_by_side(original_image, r_boost_img, "image")
Make the blacks a little bluer.
bluer_blacks = merge_channels(r_boost_lower, g, np.clip(b + 0.03, 0, 1.0))
plot_side_by_side(r_boost_img, bluer_blacks, "image")
Sharpen the image.
sharper = sharpen(bluer_blacks, 1.3, 0.3)
plot_side_by_side(bluer_blacks, sharper, "image")
A boost in blue channel for lower mid-tones and decrease in blue channel for upper mid-tones.
r, g, b = split_image_into_channels(sharper)
b_adjusted = channel_adjust(b, [0, 0.047, 0.118, 0.251, 0.318, 0.392, 0.42, 0.439, 0.475, 0.561, 0.58, 0.627, 0.671, 0.733, 0.847, 0.925, 1])
plot_side_by_side(b, b_adjusted, "hist")
And voila!
gotham = merge_channels(r, g, b_adjusted)
plot_side_by_side(original_image, gotham, "image")
# all told, 15 lines
def channel_adjust(channel, values):
orig_size = channel.shape
flat_channel = channel.flatten()
adjusted = np.interp(flat_channel, np.linspace(0, 1, len(values)), values)
return adjusted.reshape(orig_size)
r = original_image[:, :, 0]
b = original_image[:, :, 2]
r_boost_lower = channel_adjust(r, [0, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 1.0])
b_more = np.clip(b + 0.03, 0, 1.0)
merged = np.stack([r_boost_lower, original_image[:, :, 1], b_more], axis=2)
blurred = skimage.filters.gaussian(merged, sigma=10, multichannel=True)
final = np.clip(merged * 1.3 - blurred * 0.3, 0, 1.0)
b = final[:, :, 2]
b_adjusted = channel_adjust(b, [0, 0.047, 0.118, 0.251, 0.318, 0.392, 0.42, 0.439, 0.475, 0.561, 0.58, 0.627, 0.671, 0.733, 0.847, 0.925, 1])
final[:, :, 2] = b_adjusted
plot_side_by_side(original_image, final, "image")