import numpy as np
import cv2
from matplotlib import pyplot as plt
import random
import math
import warnings
warnings.filterwarnings("ignore")
def main(I):
img1 = cv2.imread(I[0],0) # queryImage
img2 = cv2.imread(I[1],0) # trainImage
# Initiate SIFT detector
sift = cv2.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# BFMatcher with default params
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1,des2, k=2)
# Apply ratio test
good = []
for m,n in matches:
if m.distance < 0.7*n.distance:
good.append([m])
# cv2.drawMatchesKnn expects list of lists as matches.
img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=2) #NOTE: 'None' parameter has to be added (not in documentation)
plt.figure(figsize=(12,8))
plt.imshow(img3),plt.show()
def MatrixMul(m1,m2):
multiplication = [[sum(a * b for a, b in zip(X_row, Y_col)) for Y_col in zip(*m2)] for X_row in m1]
return multiplication
#RANSAC Loop
#Variables to be updated to find the bestH
lowestsum = 10000000000
bestH = []
for iteration in range(5000):
#Picking 4 random indices in the list of good matches
randompts = random.sample(range(0,len(good)), 4)
a = []
#Picking 4 pairs to create matrix a
#this loop will used the 4 calculated random indices above and create the 8x9 matrix a
for index in range(0,4):
i = randompts[index]
qIdx = good[i][0].queryIdx
tIdx = good[i][0].trainIdx
x1 = kp1[qIdx].pt[0]
y1 = kp1[qIdx].pt[1]
x2 = kp2[tIdx].pt[0]
y2 = kp2[tIdx].pt[1]
#Each of the matching pair = 2 rows of the 8 total row of the matrix
a.append([0, 0, 0, -x1, -y1, -1, y2*x1, y2*y1, y2])
a.append([x1, y2, 1, 0, 0, 0, -x2*x1, -x2*y1, -x2])
#RANSAC
U, s, V = np.linalg.svd(a, full_matrices=True)
hCol = np.zeros((9,1), np.float64)
hCol = V[8,:]
# Creating h
hMatrix = [[hCol[0], hCol[1], hCol[2]], [hCol[3], hCol[4], hCol[5]], [hCol[6], hCol[7], hCol[8]]]
totalsum = 0
# Calculating sum of distance
for pairs in range(len(good)):
qIdx = good[pairs][0].queryIdx
tIdx = good[pairs][0].trainIdx
x1 = kp1[qIdx].pt[0]
y1 = kp1[qIdx].pt[1]
x2 = kp2[tIdx].pt[0]
y2 = kp2[tIdx].pt[1]
pt1Matrix = [[x1], [y1], [1]]
result = MatrixMul(hMatrix,pt1Matrix)
#Dividing all 3 rows by homogenous number
for item in range(len(result)):
result[item][0] = result[item][0] / result[2][0]
totalsum += abs(math.sqrt(((result[0][0] - x2) ** 2) + ((result[1][0] - y2) ** 2)))
if totalsum < lowestsum:
lowestsum = totalsum
bestH = hMatrix
print(bestH)
#Panorama Size
mc1 = [[0], [0], [1]]
mc1 = MatrixMul(bestH,mc1)
for item in range(len(mc1)):
mc1[item][0] = mc1[item][0] / mc1[2][0]
mc2 = [[img1.shape[1]], [0], [1]]
mc2 = MatrixMul(bestH,mc2)
for item in range(len(mc2)):
mc2[item][0] = mc2[item][0] / mc2[2][0]
mc3 = [[0], [img1.shape[0]], [1]]
mc3 = MatrixMul(bestH,mc3)
for item in range(len(mc3)):
mc3[item][0] = mc3[item][0] / mc3[2][0]
mc4 = [[img1.shape[1]], [img1.shape[0]], [1]]
mc4 = MatrixMul(bestH,mc4)
for item in range(len(mc4)):
mc4[item][0] = mc4[item][0] / mc4[2][0]
minX = round(min(0, mc1[0][0], mc3[0][0]))
maxX = round(max(img2.shape[1], mc2[0][0], mc4[0][0]))
minY = round(min(0, mc1[1][0], mc2[1][0]))
maxY = round(max(img2.shape[0], mc3[1][0], mc4[1][0]))
#Creating blank Panorama and re-reading colored version of images
panorama = np.zeros(((maxY-minY),(maxX-minX), 3), np.float32)
repeatedpixel = np.zeros(((maxY-minY),(maxX-minX)), np.float32)
img1pix = np.zeros(((maxY-minY),(maxX-minX)), np.float32)
totalred = 0
totalgreen = 0
totalblue = 0
totalpix = 0
img1 = cv2.imread(I[0])
img2 = cv2.imread(I[1])
shiftx = min(0, minX)
shifty = min(0, minY)
#placing img2 into panorama
for row in range(img2.shape[0]-1):
for col in range(img2.shape[1]-1):
panorama[row+abs(shifty)][col+abs(shiftx)][0] = img2[row][col][0]
panorama[row+abs(shifty)][col+abs(shiftx)][1] = img2[row][col][1]
panorama[row+abs(shifty)][col+abs(shiftx)][2] = img2[row][col][2]
repeatedpixel[row+abs(shifty)][col+abs(shiftx)] = 100
#stitching phase
for row in range(panorama.shape[0]):
for col in range(panorama.shape[1]):
# multiplying each pixel by inverse H and dividing by homogenous value
Hinv = np.linalg.inv(bestH)
temp = [[col + shiftx],[row + shifty], [1]]
mappedpt1 = MatrixMul(Hinv, temp)
for item in range(len(mappedpt1)):
mappedpt1[item][0] = mappedpt1[item][0] / mappedpt1[2][0]
roundx = round(mappedpt1[0][0])
roundy = round(mappedpt1[1][0])
# If calculated value of x and y in boundaries of img1, copy to panorama
if 0 < roundy < img1.shape[0] and 0 < roundx < img1.shape[1]:
if repeatedpixel[row][col] > 50:
totalblue += panorama[row][col][2] - img1[roundy][roundx][2]
totalgreen += panorama[row][col][1] - img1[roundy][roundx][1]
totalred += panorama[row][col][0] - img1[roundy][roundx][0]
repeatedpixel[row][col] = 200
panorama[row][col][0] = img1[roundy][roundx][0]
panorama[row][col][1] = img1[roundy][roundx][1]
panorama[row][col][2] = img1[roundy][roundx][2]
img1pix[row][col] = 100
totalpix += 1
totalred = totalred / totalpix
totalgreen = totalgreen / totalpix
totalblue = totalblue / totalpix
#image blending
for row in range(repeatedpixel.shape[0]):
for col in range(repeatedpixel.shape[1]):
if img1pix[row][col] > 50:
panorama[row][col][0] += totalblue
panorama[row][col][1] += totalgreen
panorama[row][col][2] += totalred
panorama = cv2.cvtColor(panorama, cv2.COLOR_RGB2BGR)
plt.figure(figsize=(12,8))
plt.imshow(panorama / 255.0),plt.show()
if __name__ == "__main__":
waterfall = ('./inputs/waterfall1.jpg', './inputs/waterfall2.jpg', 'waterfall')
mountain = ('./inputs/mountainLeft.jpg', './inputs/mountainRight.jpg', 'mountain')
florence = ('./inputs/florenceLeft.jpg', './inputs/florenceRight.jpg', 'florence')
queenstown = ('./inputs/queenstownLeft.jpg', './inputs/queenstownRight.jpg', 'queenstown')
main(waterfall)
main(mountain)
main(florence)
main(queenstown)