# Panoramic Stitching ###### tags: `Computer Vision` `perception` ## Getting started ```python! %matplotlib inline import cv2 import random import numpy as np import matplotlib.pyplot as plt from google.colab.patches import cv2_imshow ]: %%capture ! wget -O img1.jpg "https://drive.google.com/uc?,→export=download&id=1omMydL6ADxq_vW5gl_1EFhdzT9kaMhUt" ! wget -O img2.jpg "https://drive.google.com/uc?,→export=download&id=12lxB1ArAlwGn97XgBgt-SFyjE7udMGvf" ``` ## Visualize Input Images ```python! img1 = plt.imread('img1.jpg') img2 = plt.imread('img2.jpg') def plot_imgs(img1, img2): fig, ax = plt.subplots(1, 2, figsize=(15, 20)) for a in ax: a.set_axis_off() ax[0].imshow(img1) ax[1].imshow(img2) plot_imgs(img1, img2) ``` ![](https://i.imgur.com/8phMfzv.png) ## a)Feature Extraction ### 1)Compute ORB Features ```python! def get_orb_features(img): ''' Compute ORB features using cv2 library functions. Use default parameters when computing the keypoints. Hint: you will need cv2.ORB_create() and some related functions Input: img: cv2 image Returns: keypoints: a list of cv2 keypoints descriptors: a list of ORB descriptors ''' orb = cv2.ORB_create() keypoints, descriptors = orb.detectAndCompute(img, None) return keypoints, descriptors ``` ### 2)Match Keypoints ```python! def match_keypoints(desc_1, desc_2, ratio=0.75): ''' Compute matches between feature descriptors of two images using Lowe's ratio test. You may use cv2 library functions. Hint: you may need to use cv2.DescriptorMatcher_create or cv2.BFMatcher and some related functions Input: desc_1, desc_2: list of feature descriptors Return: matches: list of feature matches ''' bf = cv2.BFMatcher_create() matche = bf.knnMatch(desc_1,desc_2,k=2) # Apply ratio test matches = [] matches_points = [] for m,n in matche: if m.distance < ratio * n.distance: matches_points.append((m.trainIdx, m.queryIdx)) matches.append(m) return matches, matches_points ``` ```python! kp_1, desc_1 = get_orb_features(img1) kp_2, desc_2 = get_orb_features(img2) kp_img1 = cv2.drawKeypoints(img1, kp_1, None, color=(0,255,0), flags=0) kp_img2 = cv2.drawKeypoints(img2, kp_2, None, color=(0,255,0), flags=0) print('keypoints for img1 and img2') plot_imgs(kp_img1, kp_img2) matches, matches_points = match_keypoints(desc_1, desc_2) match_plot = cv2.drawMatches(img1, kp_1, img2, kp_2, matches[:4], None, flags=2) print("orb feature matches") cv2_imshow(match_plot) ``` ![](https://i.imgur.com/C7DPgFk.png) ![](https://i.imgur.com/KQQHcSC.png) ## b) Find Homography Matrix ```python! def find_homography(pts_1, pts_2): ''' Use either nonlinear least squares or direct linear transform to find a homography that estimates the transformation mapping from pts_1 to pts_2. e.g. If x is in pts_1 and y is in pts_2, then y = H * x Hint if using nonlinear least square: The objective function to optimize here is: ||pts_1 - cart(H*homog(pts_2))||^2 where homog(x) converts x into homogeneous coordinates and cart(x) converts x to cartesian coordinates. You can use scipy.optimize.least_squares for this. Hint if using direct linear transform: The solution is given by the right-singular vector with the smallest singular value in the singular vector decomposition. You can use np.linalg.svd for this. Input: pts_1, pts_2: (N, 2) matrix Return: H: the resultant homography matrix (3 x 3) ''' P = [] N = len(pts_1) # try src_pts = pts_2 dst_pts = pts_1 x1 = [] y1 = [] # calculate homography matrix for i in range(0,N): x1 = (src_pts[i][0], dst_pts[i][0]) y1 = (src_pts[i][1], dst_pts[i][1]) P.append([-x1[0], -y1[0], -1, 0, 0, 0, x1[0] * x1[1], y1[0] * x1[1], x1[1]]) P.append([0, 0, 0, -x1[0], -y1[0], -1, x1[0] * y1[1], y1[0] * y1[1], y1[1]]) # print("P",P) [U, S, Vt] = np.linalg.svd(P) H = Vt[-1].reshape(3, 3) H /= H[2][2] return H ``` ## c)Implement RANSAC ```python! def transform_ransac(pts_1, pts_2, verbose=False): ''' Implements RANSAC to estimate homography matrix. Hint: Follow the RANSAC steps outlined in the lecture slides. Hint: Try num iterations = 1000 (not mandatory) Hint: Threshold ε =2 (ε here refers to the L2 distance between two points) Input: pts_1, pts_2: (N, 2) matrices Return: best_model: homography matrix with most inliers ''' n_iterations = 10 threshold = 172 final_inliers = [] final_f = None best_model = None inliners_best = 0 # best_dist = float('inf') inliers = 0 n_rows = pts_1.shape[0] for itr in range(n_iterations): random_indices = np.random.choice(n_rows, size=4) pts1_4 = pts_1[random_indices, :] pts2_4 = pts_2[random_indices, :] H = find_homography(pts_1, pts_2) # print("pts1_4",pts1_4) # src = [src_pts 1] for j in range(n_rows): # print("pts_2[j,:]",pts_2[j,:]) # print("pts2_4",pts2_4) # src = np.pad(pts2_4, [(0, 0), (0, 1)], constant_values=1) src = np.hstack((pts_1[j,:], np.ones(1))) # print("src",src) # pts = H * src # print("pts1_4",pts1_4) pts_pred = np.dot(H, src.T).T # print("pts_pred",pts_pred[:2]) # normalize and throw z=1 # pts1_pred = pts_pred[:2]*pts_pred[-1] pts_pred = (pts_pred / pts_pred[-1].reshape(-1, 1))[:, 0:2] # print("pts_pred",pts_pred) # print("pts_1[j,:]",pts_1[j,:]) # print("pts_1[j,:]",pts_1[j,:]) dist = np.linalg.norm(pts_pred - pts_1[j,:]) # dist = np.sqrt(np.sum(np.square(pts_pred - pts_2[j,:]), axis=1)) # print("dist",dist) # print("distvec",distvec) # dist = np.mean(distvec[distvec < 172]) if abs(dist) < threshold and dist!=np.nan: print("dist",dist) inliers+=1 final_inliers.append(j) # print("j",j) # inlier = np.count_nonzero(distvec < threshold) # if len(indices) > inliners_best: # inliners_best = len(indices) # final_indices = indices # best_model = H_4 # print("final_inliers",final_inliers) pts1 = pts_1[final_inliers, :] pts2 = pts_2[final_inliers, :] best_model = find_homography(pts1, pts2) print("pts1",pts1) print("pts2",pts2) # if inlier > inliners_best or (inlier is inliners_best and dist < best_dist): # best_inlier = inlier # best_dist = dist # best_model = H_4 return best_model ``` ## d) Panoramic Stitching ```python! def panoramic_stitching(img1, img2): ''' Given a pair of overlapping images, generate a panoramic image. Hint: use the functions that you've written in the previous parts. Input: img1, img2: cv2 images Return: final_img: cv2 image of panorama ''' kp_1, desc_1 = get_orb_features(img1) kp_2, desc_2 = get_orb_features(img2) matches, matches_points = match_keypoints(desc_1, desc_2) matches_points = matches_points # print('matches_points',matches_points) image1_kp = np.float32( [kp_1[i].pt for (_, i) in matches_points]) image2_kp = np.float32( [kp_2[i].pt for (i, _) in matches_points]) # print('image1_kp',image1_kp) # print('image2_kp',image2_kp) # H, status = cv2.findHomography(image2_kp,image1_kp, cv2.RANSAC,5.0) # H, status = cv2.findHomography(image2_kp,image1_kp, cv2.RANSAC,0.5) H_my = transform_ransac(image2_kp, image1_kp) H_onlyh = find_homography(image2_kp, image1_kp) # print("H_onlyh",H_onlyh) print("H_my",H_my) # print('H',H) # apply perspective wrap to stitch images together final_img = cv2.warpPerspective(img2, H_my, (img2.shape[1] + img1.shape[1], img2.shape[0] * 2)) final_img[0:img1.shape[0], 0:img1.shape[1]] = img1 return final_img ``` ## Result ```python! result = panoramic_stitching(img1, img2) cv2_imshow(result) ``` ![](https://i.imgur.com/jEIpNWd.png)