Project 2: Stereo matching and homographies

CS 4501 -- Introduction to Computer Vision

Due: Fri, Mar 17 (11:59 PM)

For this assignment, we suggest to install OpenCV for your Python installation, in order to gain access to the joint bilateral filter in OpenCV. We have linked to some hints on installing OpenCV for Python 2 and Python 3 (see also OpenCV for Python 3 on Windows). To verify that your OpenCV is installed correctly, you can try doing import cv2 from an interactive Python prompt.

Assignment Overview

Your goal for this assignment is to implement a stereo matching algorithm, and an algorithm to align two images via a homography. For this assignment, and the other assignments, please submit code that you have individually authored. (However, for the final project, you can work in groups).

Stereo matching (50 points)

Implement a simple stereo matching algorithm, similar to what is described in the stereo lecture notes. The result should look similar to that below:


From left to right: left image, right image, disparities from simple stereo matching, ground truth.

These data are from the Middlebury stereo evaluation dataset. We suggest to directly download the images above though: left and right, because they have been resized to only 288x198 size, which will be much faster to compute on.

Panorama stitching using homographies (50 points)

Implement a two image panorama stitching as described in the lecture about sparse feature descriptors / SIFT. The panorama stitching should compute and match SIFT keypoints, use RANSAC to fit a homography between the two images, and then use image warping to generate the final result. The result should look like:

Input A
Input B
Resulting Composite
At left: two input images A and B. The rightmost image is the desired result of the program: a composite of images A and B warped to coordinate system of image A.

Steps:
  1. (2 points). Load images A and B using cv2.imread().

    Hint 1: OpenCV prefers the images be kept in the default 8-bit unsigned int format to work with SIFT.

    Hint 2: OpenCV weirdly uses a BGR format (blue is channel 0, green is channel 1, red is channel 2), which is the opposite of most of the other Python packages. If you are getting red and blue colors mixed up when you save or display an image A, you can use A[:,:,::-1] to flip the order of the channels.

  2. (5 points). Compute keypoints and SIFT descriptors for images A and B. Match from image A keypoints to image B keypoints. Each match gives a correspondence between a 2D SIFT keypoint in image A and one in image B. Reject matches that fail the ratio test (from the slide titled "Feature-space outlier rejection").

    To make this easier, you can just follow along this OpenCV code that shows how to do this. (Note that in their code, the line assigning sift should be changed to: sift = cv2.xfeatures2d.SIFT_create() for OpenCV version 3+. You can check your OpenCV version by printing cv2.__version__ in Python).

  3. (5 points). Write a function that applies a homography matrix H to a point (xb, yb) in image B, returning a point (xa, ya) in image A. The homography is discussed on the slide titled "Homographies." As a reminder, if the homography H is:


    Then the mapping from a point (xb, yb) in image B to a point (xa, ya) in image A is:
                          (1)

  4. (10 points). Write a function that fits a homography. This function should take a list of four 2D points in image A and a list of four 2D points in image B and return the 3x3 matrix for the homography mapping from image B points to image A points.

    By multiplying the mapping equations (1) through by their denominators, you can obtain a linear system of 8 equations in the 8 unknowns (a, b, c, d, e, f, g, h). Construct an 8x8 matrix A and length 8 vector b for this system so that the unknowns can be solved by the linear system Ax = b. You can use a linear algebra routine to solve this system (e.g. numpy.linalg.lstsq).

    You can check that your homography fitting routine works correctly by using the image A points [(0, 0), (1, 0), (0, 1), (1, 1)], image B points [(1, 2), (3, 2), (1, 4), (3, 4)], which should result in the homography matrix [[0.5, 0, -0.5], [0, 0.5, -1], [0, 0, 1]].

    Hint: We recommend to pass in the four points in image A and in image B as an array, initialize the matrix A and vector b with numpy.zeros, and populate these arrays by a single loop through all four points, with two rows (corresponding to two equations) populated for each point. This will avoid an unnecessary explosion of code/algebra.

  5. (20 points). Write a function that uses RANSAC to compute the best homography mapping image B coordinates to image A coordinates. Consult the slides titled "RANSAC" and "RANSAC for estimating homography" for a description of how to do this. RANSAC runs a loop for some fixed number of iterations, with the following steps inside the loop:


    (3 points) RANSAC should then return the homography H corresponding to the largest set of inliers. You can skip the optional process of refitting the model H using all of the inliers.

  6. (5 points). Warp image B to image A's coordinate system by applying the homography H, and composite the two images. We supply a helper routine that you can use to do this for you:

    import skimage, skimage.transform, numpy, numpy.linalg
    
    def composite_warped(a, b, H):
        "Warp images a and b to a's coordinate system using the homography H which maps b coordinates to a coordinates."
        out_shape = (a.shape[0], 2*a.shape[1])                               # Output image (height, width)
        p = skimage.transform.ProjectiveTransform(numpy.linalg.inv(H))       # Inverse of homography (used for inverse warping)
        bwarp = skimage.transform.warp(b, p, output_shape=out_shape)         # Inverse warp b to a coords
        bvalid = numpy.zeros(b.shape, 'uint8')                               # Establish a region of interior pixels in b
        bvalid[1:-1,1:-1,:] = 255
        bmask = skimage.transform.warp(bvalid, p, output_shape=out_shape)    # Inverse warp interior pixel region to a coords
        apad = numpy.hstack((skimage.img_as_float(a), numpy.zeros(a.shape))) # Pad a with black pixels on the right
        return skimage.img_as_ubyte(numpy.where(bmask==1.0, bwarp, apad))    # Select either bwarp or apad based on mask
    

    Note that this routine assumes it is given unsigned 8-bit integer input images and returns an image in the same format.

  7. (3 points). Include in your readme the best homography matrix found by RANSAC and the composite panorama image.

  8. (Optional extra credit: 5 points). The final composite image does not look that great because it has a seam in it. Determine the overlap region between the two images. Inside this overlap region, modify the function composite_warped to use linear blending to smoothly interpolate each pixel's color between the colors drawn from a's image and the corresponding colors drawn from the warped b image (bwarp). In particular, you can determine the distances to the region where only a pixels exist, and the region where only b pixels exist using a distance transform, and use these distances to make a smooth interpolation.

Policies

Feel free to collaborate on solving the problem but write your code individually.

Submission

Submit your assignment in a zip file named yourname_project2.zip. Please include your source code as .py files, the results of stereo matching for the above image pair for (a) Gaussian filtering, (b) bilateral filtering, and (c) after rejecting occluded/mismatched regions. Please also include a readme describing how to run your code from the command-line, and the RMS distances to the ground truth for scenarios (a-c) listed in the previous sentence. In addition, please include the best homography matrix found by RANSAC and the composite panorama image.

Finally submit your zip to UVA Collab.