Colorizing the Prokudin-Gorskii Photo Collection

16-726 Assignment #1
Michael Mu (mmu2)

Introduction

The goal of this assignment is to align three slightly offset photos that were taken with a red, green, and blue lens filter. To accomplish this, I created a base implementation which rolled each red and green image to align with the blue image. This base implementation checked the L2 loss between the pixels of the two images to determine if the values were close, acting as a proxy measure of similarity between the images.

After the base implementation, I implemented a more efficient pyramid-based approach. This approach first downsamples the images into an image pyramid, computing the alignment at coarser levels before moving to more fine-grained levels. This approach saves computation cost by reducing the number of pixels to compare at each level.

After this, I implemented iterative "bells and whistles" improvements:

Single Scale Implementation on Low-Res Images

In this implementation, I would roll the red and green images to find the lowest L2 loss with the blue image. To reduce the runtime, I only calculated the L2 loss between two images within a certain offset range, ranging from [-15,15] in both the x and y directions.

I originally tried using padding along the edges and displacing the image instead of rolling, but I found that the loss contributed by padding was either too high (when padding by 0) or too low (when extending edges), resulting in no image displacement or entirely too much displacement.

The two images show a color image after aligning the color channels with (left) and without (right) adjustment. The most notable improvement is the removal of the blue tint on the treeline as well as the misaligned red buildings.
Red displacement: (-1, 8)
Green displacement: (0, 3)

Multiscale Pyramid Efficient Implementation

For larger images, it is inefficient to compare every pixel in the image. Instead, I implemented a pyramid-based approach, where each higher level of the pyramid is 1/4 the size of the previous level (accomplished by blurring, then subsampling every other row). Now, we can perform coarse alignment using the lower resolution levels, and perform fine-grained alignment at the higher resolution levels.

In my personal implementation, each level checks displacement of range [-4,4]. Theoretically, we should only need a range of 2, but I found that giving slightly more wiggle room in the higher resolution images tended to improve image quality without much sacrifice in runtime. Additionally, I changed my L2 loss to only include the central region of the image. This should reduce computation time and also reduce the influence of the messy edges of the images.

Click to see the cathedral results
Improved(left) vs. original (right).
Red displacement: (12, 7)
Green displacement: (7, 2)
We see little difference in the cathedral example.
Click to see the emir results
Improved(left) vs. original (right).
Red displacement: (54, 104)
Green displacement: (25, 52)
Click to see the harvesters results
Improved(left) vs. original (right).
Red displacement: (18, 120)
Green displacement: (12, 64)
Click to see the icon results
Improved(left) vs. original (right).
Red displacement: (18, 92)
Green displacement: (16, 45)
In my opinion, this one turned out really good, so I provided a zoomed in image for you to enjoy.
Click to see the lady results
Improved(left) vs. original (right).
Red displacement: (8, 112)
Green displacement: (9, 55)
Click to see the self_portrait results
Improved(left) vs. original (right).
Red displacement: (40, 172)
Green displacement: (25, 74)
Click to see the three_generations results
Improved(left) vs. original (right).
Red displacement: (8, 113)
Green displacement: (10, 54)
Click to see the train results
Improved(left) vs. original (right).
Red displacement: (28, 85)
Green displacement: (2, 40)
Click to see the turkmen results
Improved(left) vs. original (right).
Red displacement: (24, 120)
Green displacement: (16, 52)
Click to see the village results
Improved(left) vs. original (right).
Red displacement: (22, 139)
Green displacement: (12, 65)

A few examples of my choosing

Very pretty. Much colors.
The whole image is very blue for some reason, but we can see that the red has been rectified.
Weird shadows begone.

Bells & Whistles

Automatic Contrasting

When performing automatic contrasting, I first checked to see if I could rescale image intensites from some small intervals to the full range of 0 to 255. However, the image ranges are already very close, so changes would have minimal impact.

Instead, I tried a few different piecewise scalings to see if any would work well. I ended up creating a mapping where the lowest and highest 40 pixel intensities would be reduced to 25% and the rest of the intensity ranges would scale to fill in the extra details.

Plot showing the contrast mappings from original (blue) to contrasted image (orange)
Contrasted image (left) to uncontrasted image (right)

SIFT Feature-based Alignment

Rather than aligning based on translation or rotation, or using gradients or edges for the features, I decided to implement SIFT to find keypoints. Then, I matched keypoints using RANSAC and found the homography matrix to transform from one image to the other.

I then used openCV's cv.warpPerspective() to transform the images to the same alignment as the blue image. This method can handle more complex transformations, can search globally instead of a local search, and is fairly fast thanks to optimizations made made by the openCV library.

SIFT image (left) to original alignment (right)

The alignment clearly works to align better than the original image, but what about when comparing to our earlier alignment algorithm? It turns out that the result is very good. There are very few artifacts that come from misalignment. This makes sense as we can account for any type of affine transformation, and we are very intelligently using features along with RANSAC's "voting-based" system to select the best transformation.

SIFT image (left) to translation-aligned image (right)

Automatic Cropping

Automatic cropping turns out to be extremely difficult. I tried several different attempts including using SIFT features, color gradients, among other methods. I finally settled on a more conservative approach, so sometimes, artifacts remain. However, the cropping algorithm doesn't overcrop semantically important regions.

The current algorithm starts at the edge and moves inward, line by line, until it encounters a large enough change in color channels. If the change is large enough, it will crop away every line that it has passed so far. Then, it begins again with the new color. The algorithm continues until it reaches a region with a lot of color changes in rapid succession, or it continues for a long time without any changes in color. By doing this, the algorithm searches for uniform color bands and crops them away. The minimum and maximum lengths of the color bands to be cropped were set by me to be on the conservative side.

There is also a minor offset, where after a band ends, the cropping algorithm will automatically skip a few lines before continuing to check for boundaries. This prevents the cropping algorithm from detecting the same band edge multiple times, since most boundaries are not perfectly straight.

Automatically cropped image (left) vs. uncropped original (right). Notice that the green band along the top is cropped, but the left-most border is not cropped.

Better Color Mapping

To do a color mapping, I had to perform an intelligent crop first to reduce the number of noisy color pixels that contribute to the color scheme. After that, I used openCV's white balance algorithm to adjust the color channels. In particular, the white balance algorithm assumes that the color channels should add up to a gray value, so it adjust the color channels to fit this assumption. I included all pixel intensities in the calculation, as I have cropped out most of the unnecessary pixels from the image.

White-balanced image (left) vs. unbalanced image (right).
White-balanced image (left) vs. unbalanced image (right).