In this project, we develop an automated alignment algorithm using image pyramids to align the three color channels of images in the Library of Congress's Prokudin-Gorskii photo collection. Sergei Mikhailovich Prokudin-Gorskii was the first color photographer, and used an invention of his own genious design to snap three simultanous photographs, each of which used different color filters. When combined, these three apparently monochrome photographs produce stunning images of the world in color.
The challenge, however, lies in aligning the three photos, each of which would have been taken at a slightly different angle. Prokudin-Gorskii did so by hand, but today we can produce better results using automated image alignment algorithms. In this lab, we will demonstrate one such algorithm: the image pyramid.
The Prokudin-Gorskii photo collection represents the first color photography
%pylab inline
%load_ext autoreload
%autoreload 2
import skimage as sk
import skimage.io as skio
from plotting import *
from align import *
from utils import *
emir = sk.img_as_float(read_pg_img('data/emir.tif'))
print(emir.shape)
plot_3_channels(emir)
imshow(emir)
show()
im = sk.img_as_float(read_pg_img('data/monastery.jpg'))
print(im.shape)
plot_3_channels(im)
r_translated = translate_img(im[:,:,0], translation=(0,-10))
im_align = np.dstack([r_translated, im[:,:,1:]])
ssd_realign = ssd_loss(r_translated, im[:,:,1])
ncc_realign = 1 - ncc_loss(r_translated, im[:,:,1])
ssd_orig = ssd_loss(im[:,:,0], im[:,:,1])
ncc_orig = 1 - ncc_loss(im[:,:,0], im[:,:,1])
plot_comparison(
im, im_align,
title1 = f'Before: SSD={ssd_orig:0.3f}, NCC={ncc_orig:0.3f}',
title2 = f'After: SSD={ssd_realign:0.3f}, NCC={ncc_realign:0.3f}'
)
Using SSD as loss:
r_align = align_by_grid_search(im[:,:,0], im[:,:,1])
im_align = np.dstack([r_align, im[:,:,1:]])
ssd_realign = ssd_loss(r_align, im[:,:,1])
ncc_realign = 1 - 2*ncc_loss(r_align, im[:,:,1])
plot_comparison(
im, im_align,
title1 = f'Before: SSD={ssd_orig:0.3f}, NCC={ncc_orig:0.3f}',
title2 = f'After: SSD={ssd_realign:0.3f}, NCC={ncc_realign:0.3f}'
)
Using NCC in loss:
r_align_ncc = align_by_grid_search(im[:,:,0], im[:,:,1], loss_fn=ncc_loss)
im_align_ncc = np.dstack([r_align_ncc, im[:,:,1:]])
ssd_realign2 = ssd_loss(r_align_ncc, im[:,:,1])
ncc_realign2 = 1 - 2*ncc_loss(r_align_ncc, im[:,:,1])
plot_comparison(
im, im_align_ncc,
title1 = f'Before: SSD={ssd_orig:0.3f}, NCC={ncc_orig:0.3f}',
title2 = f'After: SSD={ssd_realign:0.3f}, NCC={ncc_realign2:0.3f}'
)
%%time
im_align = align_img(im, ref=1)
plot_comparison(im, im_align)
Try a larger alignment grid
%%time
im_align = align_img(im, ref=1, window=(-30,30))
plot_comparison(im, im_align)
Uniform cropping
im_align = align_img(crop_img_edges(im, pixels=(20,20,20,20)), ref=1)
plot_comparison(im, im_align)
emir_cropped = crop_img_edges(sk.img_as_float(emir), pixels=(200,200,200,200))
%%time
# TODO: try ncc_loss
emir_aligned = align_pyramid(emir_cropped, debug=True)
%%time
emir_ncc = align_pyramid(emir_cropped, loss_fn=ncc_loss)
plot_comparison(emir_ncc, emir_aligned, title1="Using SSD loss", title2="Using NCC loss")
To my eyes, the SSD and NCC losses produce nearly identical results.
%%time
castle = read_and_crop_pg_img('data/castle.tif')
castle_aligned = align_pyramid(castle)
plot_comparison(castle, castle_aligned)
%%time
harvesters = read_and_crop_pg_img('data/harvesters.tif')
harvesters_aligned = align_pyramid(harvesters)
plot_comparison(harvesters, harvesters_aligned)
%%time
icon = read_and_crop_pg_img('data/icon.tif')
icon_aligned = align_pyramid(icon)
plot_comparison(icon, icon_aligned)
%%time
lady = read_and_crop_pg_img('data/lady.tif')
lady_aligned = align_pyramid(lady)
plot_comparison(lady, lady_aligned)
%%time
melons = read_and_crop_pg_img('data/melons.tif')
melons_aligned = align_pyramid(melons)
plot_comparison(melons, melons_aligned)
%%time
onion_church = read_and_crop_pg_img('data/onion_church.tif')
onion_church_aligned = align_pyramid(onion_church)
plot_comparison(onion_church, onion_church_aligned)
%%time
self_portrait = read_and_crop_pg_img('data/self_portrait.tif')
self_portrait_aligned = align_pyramid(self_portrait)
plot_comparison(self_portrait, self_portrait_aligned)
%%time
three_generations = read_and_crop_pg_img('data/three_generations.tif')
three_generations_aligned = align_pyramid(three_generations)
plot_comparison(three_generations, three_generations_aligned)
%%time
train = read_and_crop_pg_img('data/train.tif')
train_aligned = align_pyramid(train)
plot_comparison(train, train_aligned)
%%time
workshop = read_and_crop_pg_img('data/workshop.tif')
workshop_aligned = align_pyramid(workshop)
plot_comparison(workshop, workshop_aligned)
%%time
stained_glass = read_and_crop_pg_img('data/stained_glass.tif')
stained_glass_aligned = align_pyramid(stained_glass)
plot_comparison(stained_glass, stained_glass_aligned)
%%time
cathedral_roof = read_and_crop_pg_img('data/cathedral_roof.tif')
cathedral_roof_aligned = align_pyramid(cathedral_roof)
plot_comparison(cathedral_roof, cathedral_roof_aligned)
%%time
cliff = read_and_crop_pg_img('data/cliff.tif')
cliff_aligned = align_pyramid(cliff)
plot_comparison(cliff, cliff_aligned)