Panoramic Photo App

Implemented a MATLAB program to stitch images into a single panorama

As part of my Computer Vision coursework, this program applies Homography, Backward Warping, RANSAC, Blending, and Stitching.

Step 1: Homography

The goal:

Given a set of matching points between two images, we find their homography: the projective transformation applied to image 1 which yields image 2.

Image 1

Image 2

Computing Homography:

First, select 2x2 matrices of source points and their corresponding destination points by using ginput(4)as input parameters for our function:

function H_3x3 = computeHomography(src_pts_nx2, dest_pts_nx2)

We then create A, a matrix of known values which contains two rows for each set of points and normalizes their x and y coordinates to the new coordinate system. Finally, we turn to eigenvectors, as homography becomes an eigenvalue problem of constrained least squares. The eigenvector of the smallest eigenvalue of A yields the homography matrix H.

A_invertible = A_T*A;

[V,~] = eig(A_invertible); % V columns are eigenvectors

eigenvalues = eig(A_invertible);

smallest_eigenval = min(eigenvalues);

index_smallest = find(smallest_eigenval);

H_9x1 = V(:, index_smallest);

Return H_9x1 converted as a 3x3 matrix.

Applying Homography:

Using the derived homography matrix and four new source points, we test the accuracy of our computation.

function dest_pts_nx2 = applyHomography(H_3x3,src_pts_nx2)

This function applies the homography matrix to the inputted points to generate new destination x and y coordinates. We the restore the coordinates from their normalized form and return them as a 2x2 matrix.

Showing Correspondence:

After confirming the accuracy of computeHomography, we write a new function to show the homography correspondence between the two images.

function result_img = showCorrespondence(orig_img, warped_img, src_pts_nx2, dest_pts_nx2)

This function plots lines between source and destination points to show the homography between both images. This allows us to visually confirm the accuracy!

Output:

Step 2: Backward Warping

The goal:

Given a destination and unwarped image, we need to warp the image to the given dimensions and superimpose the result onto the destination image.

Destination Image

Unwarped Image

Implementation:

For the backwardWarpImg function, we first compute the homography between the destination image and unwarped image and use the result as one of the input parameters.

function [mask, result_img] = backwardWarpImg(src_img, resultToSrc_H, dest_canvas_width_height)

First, we iterate through the source image coordinates to apply homography to each coordinate and determine its destination coordinate. We then use interpolation (a method of creating new data points in a specified range) to retain the red, green, and blue channels of the source image with the new destination coordinates.

red_channel_new = interp2(src_img(:,:,1), x, y);

green_channel_new = interp2(src_img(:,:,2), x, y);

blue_channel_new = interp2(src_img(:, :, 3), x, y);

Collage showing the separate red, green, and blue channels for Van Gogh's portrait.

In order to ensure that the resulting warped image fits the desired dimensions without retaining a white background, we need to compute a mask. To do this, we set NaN values equal to 0 (sets them to black):

red_channel_new(isnan(red_channel_new)==true) = 0;

green_channel_new(isnan(green_channel_new)==true) = 0;

blue_channel_new(isnan(blue_channel_new)==true) = 0;

Finally, we put the interpolated color channels for the current coordinate iteration into a resulting image.

result_img(k,:,1) = red_channel_new;

result_img(k,:,2) = green_channel_new;

result_img(k,:,3) = blue_channel_new;

After concatenating the mask with all three channels, we superimpose the resulting image onto the destination image.

Output Image:

Step 3: Running RANSAC

The goal:

After applying SIFT (Scale Invariant Feature Transform) to detect matching keypoints (local features) in both images, we need to minimize the number of outliers. Often, SIFT matching can result in multiple outliers, especially in sections of the image with minimal texturesuch as the sky or ocean, both of which have fairly flat features.

RANSAC (random sample consensus) iteratively minimizes the number of outliers in the computed transformations between two images. This sets us up for the Blending step later!

The process:

We iteratively select 4 random samples from the source and destination points, compute the homography matrix, and apply the homography to get new destination points. We then use the Euclidean Distance to calculate the error between the new and initial destination points.

We then select the model with the largest number of inlierswhich minimizes the number of outliers.

Before RANSAC

Here, there are numerous outliers. Most are a result of the flat texture of the sky causing mismatches!

After RANSAC

Now, there are minimal outliers, and we can go on to the Stitching step.

Step 4: Blending

The goal:

We want to seamlessly blend the overlap of two images together, setting us up for the Stitching step later!

Evident below, if we simply overlay the two images, the seam between the two is visible. However, if we account for the differences in colors and their weights (lightness/darkness), we can average out the coordinates at the seams and create a blended result.

Image 1

Image 2

Overlayed Result

Blended Result

The process:

For each image, we compute a mask to set up weights for each pixel. Here, we account for Distance Transform (or bwdist), which gives pixels closer to the edge lower weights. This ensures that the seams of the overlayed results are less visibly abrupt.

Finally, we create the blended result using a sort of weighted average: img_blend = (w1*img1 + w2*img2)/(w1 + w2);

Step 5: Stitching

The goal:

Implement Steps 1 - 4 to create a single, integrated panorama that stitches together a collection of images.

Left Image

Center Image

Right Image

The process:

Given the images above, we would first begin with the left image. After storing its height and width dimensions and using the corner points as source images, we iterate through the remaining images. For each, we first find the SIFT matches between both images, run RANSAC to minimize outliers, and apply homography.

Then we update the height using vertical concatenation and the width using horizontal concatenation. This determines the new dimensions for the next iteration. Using these dimensions, we then compute homography using the corner source points and the new destination points.

Finally, we apply backward warp imaging for correct alignment before overlaying and blending the two images. This loop is repeated until all input images have been stitched together.

Output Image: