def get_centers_and_corners(ims: list, Hs: list) -> tuple: """ get the corners and centers of each im in ims :param ims: list of grayscale images. :param Hs: list of 3x3 homography matrices. :return: tuple containing a list of x,y centers and a list with x_min,x_max,y_min and y_max """ centers = [] corners = np.empty((len(ims), 4)) for i in range(len(ims)): rows_cor, cols_cor = ims[i].shape[0] - 1, ims[i].shape[1] - 1 # get center of im[i]: curr_center = np.array([cols_cor / 2, rows_cor / 2]).reshape(1, 2) centers.append(apply_homography(curr_center, Hs[i])[0]) # get corners of im[i]: curr_cornrs = np.array([[0, 0], [cols_cor, 0], [0, rows_cor], [cols_cor, rows_cor]]).reshape(4, 2) curr_cornrs = apply_homography(curr_cornrs, Hs[i]) corners[i, 0] = np.min(curr_cornrs[:, 0]) # curr_x_min corners[i, 1] = np.max(curr_cornrs[:, 0]) # curr_x_max corners[i, 2] = np.min(curr_cornrs[:, 1]) # curr_y_min corners[i, 3] = np.max(curr_cornrs[:, 1]) # curr_y_max # calc canvas corners x_min, x_max = int(np.min(corners[:, 0])), int(np.max(corners[:, 1])) y_min, y_max = int(np.min(corners[:, 2])), int(np.max(corners[:, 3])) corners = [x_min, x_max, y_min, y_max] return centers, corners
def ransac_homography(pos1: np.ndarray, pos2: np.ndarray, num_iters: int, inlier_tol: np.float32) -> tuple: """ apply RANSAC homography fitting :param pos1: an array with n rows of [x,y] coordinates of matched points of first image. :param pos2: an array with n rows of [x,y] coordinates of matched points of second image. :param num_iters: number of RANSAC iterations to perform. :param inlier_tol: inlier tolerance threshold. :return: A 3x3 normalized homography matrix and An Array with shape (S,) where S is the number of inliers, containing the indices in pos1/pos2 of the maximal set of inlier matches found. """ inliers = np.array([]) for i in range(num_iters): rand_idx = np.random.choice( pos1.shape[0], size=NUM_OF_POINTS_TO_TRANS) # choose 4 points pos1_smpl, pos2_smpl = pos1[rand_idx, :], pos2[rand_idx, :] h = sol4_add.least_squares_homography(pos1_smpl, pos2_smpl) if h is None: continue pos1_trans = apply_homography(pos1, h) e = np.linalg.norm(pos1_trans - pos2, axis=1)**2 curr_inliers = np.where( e < inlier_tol)[0] # indices of "good" points of pos2 if len(curr_inliers) > len(inliers): inliers = curr_inliers H12 = sol4_add.least_squares_homography(pos1[inliers, :], pos2[inliers, :]) return H12, inliers
def harris_corner_detector(im: np.ndarray) -> np.ndarray: """ extract harris-corner key feature points :param im: the image to extract key points :return: An array with shape (N,2) of [x,y] key points locations in im. """ ix = sol4_utils.sp_signal.convolve2d(im, DER_FILTER, 'same', 'wrap') iy = sol4_utils.sp_signal.convolve2d(im, DER_FILTER.T, 'same', 'wrap') ix_2 = sol4_utils.blur_spatial(ix**2, BLUR_SIZE) iy_2 = sol4_utils.blur_spatial(iy**2, BLUR_SIZE) ix_iy = sol4_utils.blur_spatial(ix * iy, BLUR_SIZE) r = ix_2 * iy_2 - ix_iy**2 - K * (ix_2 + iy_2)**2 # R - the response of the max_of_r = sol4_add.non_maximum_suppression(r) pos = np.transpose(np.nonzero( max_of_r)) # array of the indices of non-zero pixels in max_of_r pos_for_spread = np.transpose(np.array( [pos[:, 1], pos[:, 0]])) # school function works with [yx] return pos_for_spread
def accumulate_homographies(H_successive: list, m: int) -> list: """ get Hi,m from {Hi,i+1 : i = 0..M-1}. :param H_successive: A list of 3x3 homography matrices where H successive[i] is a homography that transforms points from coordinate system i to coordinate system i+1. :param m: Index of the coordinate system we would like to accumulate the given homographies towards. :return: A list of M 3x3 homography matrices, where H2m[i] transforms points from coordinate system i to coordinate system m. """ less_than_m = H_successive[:m] # all matrices for i<m less_than_m = list(accumulate(less_than_m[::-1], np.dot))[::-1] # reverse again to 0-m less_than_m.append(np.eye(3)) # add for i=m bigger_than_m = H_successive[m:] # all matrices for i>m bigger_than_m = list(map(np.linalg.inv, bigger_than_m)) bigger_than_m = list(accumulate(bigger_than_m, np.dot)) H2m = np.array(less_than_m + bigger_than_m) H2m = H2m.T / H2m[:, 2, 2] return list(H2m.T)
def render_panorama(ims: list, Hs: list) -> np.ndarray: """ panorama rendering :param ims: list of grayscale images. :param Hs: list of 3x3 homography matrices. Hs[i] is a homography that transforms points from the coordinate system of ims [i] to the coordinate system of the panorama. :return: A grayscale panorama image composed of vertical strips, backwarped using homographies from Hs, one from every image in ims. """ if len(ims) == 1: return ims[0] # get data of the shape of the panorama centers, corners = get_centers_and_corners( ims, Hs) # corners = [x_min, x_max, y_min, y_max] x_min, x_max, y_min, y_max = corners[0], corners[1], corners[2], corners[3] width, height = x_max - x_min + 1, y_max - y_min + 1 # calc a fake shape so it could fit the pyramid blending - only power of 2 sizes next_power_of_cols = next_power(width) next_power_of_rows = next_power(height) cols_pad = next_power_of_cols - width rows_pad = next_power_of_rows - height # create the canvas of the panorama x_pano, y_pano = np.meshgrid(np.arange(x_min, x_max + cols_pad + 1), np.arange(y_min, y_max + rows_pad + 1)) panorama = np.zeros(x_pano.shape) # the canvas of the panorama pan_rows, pan_cols = panorama.shape # create borders of strips borders = [ int(np.round((centers[i][0] + centers[i + 1][0]) / 2) - x_min) for i in range(len(ims) - 1) ] borders.insert(0, 0) borders.append(x_pano.shape[1]) # apply panorama for i in range(len(ims)): left = borders[i] - OVERLAP if i != 0 else borders[i] right = borders[i + 1] + OVERLAP if i != len(ims) - 1 else borders[i + 1] x_coord, y_coord = x_pano[:, left: right], y_pano[:, left: right] # indices of the current part xi_yi = np.array([x_coord.flatten(), y_coord.flatten()]).T xi_yi = apply_homography(xi_yi, np.linalg.inv(Hs[i])) curr_im = map_coordinates(ims[i], [xi_yi[:, 1], xi_yi[:, 0]], order=1, prefilter=False) curr_im = curr_im.reshape(panorama[:, left:right].shape) # apply blending on panorama: if i == 0: panorama[:, left:right] = curr_im continue temp_canvas = np.zeros(panorama.shape) temp_canvas[:, left:right] = curr_im # create a mask and blend them: mask = np.ones(panorama.shape) mask[:, borders[i]:] = 0 panorama = sol4_utils.pyramid_blending(panorama, temp_canvas, mask, 4, 15, 15) panorama = panorama[:pan_rows, :pan_cols] panorama = panorama[:height, :width].astype( np.float32) # back to real shape return panorama
import sol4_utils from sol4_utils import np import sol4_add from itertools import accumulate import matplotlib.pyplot as plt from scipy.ndimage.interpolation import map_coordinates DER_FILTER = np.array([1, 0, -1], np.float32).reshape(1, 3) BLUR_SIZE = 3 K = 0.04 N = M = 4 # defaults for spread_out_corners n and m RADIUS = 3 DEFAULT_DESC_RAD = 3 DEFAULT_MIN_SCORE = 0.5 NUM_OF_POINTS_TO_TRANS = 4 EPSILON = 10**-5 OVERLAP = 30 def harris_corner_detector(im: np.ndarray) -> np.ndarray: """ extract harris-corner key feature points :param im: the image to extract key points :return: An array with shape (N,2) of [x,y] key points locations in im. """ ix = sol4_utils.sp_signal.convolve2d(im, DER_FILTER, 'same', 'wrap') iy = sol4_utils.sp_signal.convolve2d(im, DER_FILTER.T, 'same', 'wrap') ix_2 = sol4_utils.blur_spatial(ix**2, BLUR_SIZE) iy_2 = sol4_utils.blur_spatial(iy**2, BLUR_SIZE) ix_iy = sol4_utils.blur_spatial(ix * iy, BLUR_SIZE) r = ix_2 * iy_2 - ix_iy**2 - K * (ix_2 +