-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathImageStitching.py
238 lines (171 loc) · 9.05 KB
/
ImageStitching.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import os
import cv2
import math
import numpy as np
def ReadImage(ImageFolderPath):
Images = [] # Input Images will be stored in this list.
# Checking if path is of folder.
if os.path.isdir(ImageFolderPath): # If path is of a folder contaning images.
ImageNames = os.listdir(ImageFolderPath)
ImageNames_Split = [[int(os.path.splitext(os.path.basename(ImageName))[0]), ImageName] for ImageName in ImageNames]
ImageNames_Split = sorted(ImageNames_Split, key=lambda x:x[0])
ImageNames_Sorted = [ImageNames_Split[i][1] for i in range(len(ImageNames_Split))]
for i in range(len(ImageNames_Sorted)): # Getting all image's name present inside the folder.
ImageName = ImageNames_Sorted[i]
InputImage = cv2.imread(ImageFolderPath + "/" + ImageName) # Reading images one by one.
# Checking if image is read
if InputImage is None:
print("Not able to read image: {}".format(ImageName))
exit(0)
Images.append(InputImage) # Storing images.
else: # If it is not folder(Invalid Path).
print("\nEnter valid Image Folder Path.\n")
if len(Images) < 2:
print("\nNot enough images found. Please provide 2 or more images.\n")
exit(1)
return Images
def FindMatches(BaseImage, SecImage):
# Using SIFT to find the keypoints and decriptors in the images
Sift = cv2.SIFT_create()
BaseImage_kp, BaseImage_des = Sift.detectAndCompute(cv2.cvtColor(BaseImage, cv2.COLOR_BGR2GRAY), None)
SecImage_kp, SecImage_des = Sift.detectAndCompute(cv2.cvtColor(SecImage, cv2.COLOR_BGR2GRAY), None)
# Using Brute Force matcher to find matches.
BF_Matcher = cv2.BFMatcher()
InitialMatches = BF_Matcher.knnMatch(BaseImage_des, SecImage_des, k=2)
# Applytng ratio test and filtering out the good matches.
GoodMatches = []
for m, n in InitialMatches:
if m.distance < 0.75 * n.distance:
GoodMatches.append([m])
return GoodMatches, BaseImage_kp, SecImage_kp
def FindHomography(Matches, BaseImage_kp, SecImage_kp):
# If less than 4 matches found, exit the code.
if len(Matches) < 4:
print("\nNot enough matches found between the images.\n")
exit(0)
# Storing coordinates of points corresponding to the matches found in both the images
BaseImage_pts = []
SecImage_pts = []
for Match in Matches:
BaseImage_pts.append(BaseImage_kp[Match[0].queryIdx].pt)
SecImage_pts.append(SecImage_kp[Match[0].trainIdx].pt)
# Changing the datatype to "float32" for finding homography
BaseImage_pts = np.float32(BaseImage_pts)
SecImage_pts = np.float32(SecImage_pts)
# Finding the homography matrix(transformation matrix).
(HomographyMatrix, Status) = cv2.findHomography(SecImage_pts, BaseImage_pts, cv2.RANSAC, 4.0)
return HomographyMatrix, Status
def GetNewFrameSizeAndMatrix(HomographyMatrix, Sec_ImageShape, Base_ImageShape):
# Reading the size of the image
(Height, Width) = Sec_ImageShape
InitialMatrix = np.array([[0, Width - 1, Width - 1, 0],
[0, 0, Height - 1, Height - 1],
[1, 1, 1, 1]])
FinalMatrix = np.dot(HomographyMatrix, InitialMatrix)
[x, y, c] = FinalMatrix
x = np.divide(x, c)
y = np.divide(y, c)
# Finding the dimentions of the stitched image frame and the "Correction" factor
min_x, max_x = int(round(min(x))), int(round(max(x)))
min_y, max_y = int(round(min(y))), int(round(max(y)))
New_Width = max_x
New_Height = max_y
Correction = [0, 0]
if min_x < 0:
New_Width -= min_x
Correction[0] = abs(min_x)
if min_y < 0:
New_Height -= min_y
Correction[1] = abs(min_y)
if New_Width < Base_ImageShape[1] + Correction[0]:
New_Width = Base_ImageShape[1] + Correction[0]
if New_Height < Base_ImageShape[0] + Correction[1]:
New_Height = Base_ImageShape[0] + Correction[1]
# Finding the coordinates of the corners of the image if they all were within the frame.
x = np.add(x, Correction[0])
y = np.add(y, Correction[1])
OldInitialPoints = np.float32([[0, 0],
[Width - 1, 0],
[Width - 1, Height - 1],
[0, Height - 1]])
NewFinalPonts = np.float32(np.array([x, y]).transpose())
# Updating the homography matrix. Done so that now the secondary image completely
# lies inside the frame
HomographyMatrix = cv2.getPerspectiveTransform(OldInitialPoints, NewFinalPonts)
return [New_Height, New_Width], Correction, HomographyMatrix
def StitchImages(BaseImage, SecImage):
# Applying Cylindrical projection on SecImage
SecImage_Cyl, mask_x, mask_y = ProjectOntoCylinder(SecImage)
# Getting SecImage Mask
SecImage_Mask = np.zeros(SecImage_Cyl.shape, dtype=np.uint8)
SecImage_Mask[mask_y, mask_x, :] = 255
# Finding matches between the 2 images and their keypoints
Matches, BaseImage_kp, SecImage_kp = FindMatches(BaseImage, SecImage_Cyl)
# Finding homography matrix.
HomographyMatrix, Status = FindHomography(Matches, BaseImage_kp, SecImage_kp)
# Finding size of new frame of stitched images and updating the homography matrix
NewFrameSize, Correction, HomographyMatrix = GetNewFrameSizeAndMatrix(HomographyMatrix, SecImage_Cyl.shape[:2], BaseImage.shape[:2])
# Finally placing the images upon one another.
SecImage_Transformed = cv2.warpPerspective(SecImage_Cyl, HomographyMatrix, (NewFrameSize[1], NewFrameSize[0]))
SecImage_Transformed_Mask = cv2.warpPerspective(SecImage_Mask, HomographyMatrix, (NewFrameSize[1], NewFrameSize[0]))
BaseImage_Transformed = np.zeros((NewFrameSize[0], NewFrameSize[1], 3), dtype=np.uint8)
BaseImage_Transformed[Correction[1]:Correction[1]+BaseImage.shape[0], Correction[0]:Correction[0]+BaseImage.shape[1]] = BaseImage
StitchedImage = cv2.bitwise_or(SecImage_Transformed, cv2.bitwise_and(BaseImage_Transformed, cv2.bitwise_not(SecImage_Transformed_Mask)))
return StitchedImage
def Convert_xy(x, y):
global center, f
xt = ( f * np.tan( (x - center[0]) / f ) ) + center[0]
yt = ( (y - center[1]) / np.cos( (x - center[0]) / f ) ) + center[1]
return xt, yt
def ProjectOntoCylinder(InitialImage):
global w, h, center, f
h, w = InitialImage.shape[:2]
center = [w // 2, h // 2]
f = 1100 # 1100 field; 1000 Sun; 1500 Rainier; 1050 Helens
# Creating a blank transformed image
TransformedImage = np.zeros(InitialImage.shape, dtype=np.uint8)
# Storing all coordinates of the transformed image in 2 arrays (x and y coordinates)
AllCoordinates_of_ti = np.array([np.array([i, j]) for i in range(w) for j in range(h)])
ti_x = AllCoordinates_of_ti[:, 0]
ti_y = AllCoordinates_of_ti[:, 1]
# Finding corresponding coordinates of the transformed image in the initial image
ii_x, ii_y = Convert_xy(ti_x, ti_y)
# Rounding off the coordinate values to get exact pixel values (top-left corner)
ii_tl_x = ii_x.astype(int)
ii_tl_y = ii_y.astype(int)
# Finding transformed image points whose corresponding
# initial image points lies inside the initial image
GoodIndices = (ii_tl_x >= 0) * (ii_tl_x <= (w-2)) * \
(ii_tl_y >= 0) * (ii_tl_y <= (h-2))
# Removing all the outside points from everywhere
ti_x = ti_x[GoodIndices]
ti_y = ti_y[GoodIndices]
ii_x = ii_x[GoodIndices]
ii_y = ii_y[GoodIndices]
ii_tl_x = ii_tl_x[GoodIndices]
ii_tl_y = ii_tl_y[GoodIndices]
# Bilinear interpolation
dx = ii_x - ii_tl_x
dy = ii_y - ii_tl_y
weight_tl = (1.0 - dx) * (1.0 - dy)
weight_tr = (dx) * (1.0 - dy)
weight_bl = (1.0 - dx) * (dy)
weight_br = (dx) * (dy)
TransformedImage[ti_y, ti_x, :] = ( weight_tl[:, None] * InitialImage[ii_tl_y, ii_tl_x, :] ) + \
( weight_tr[:, None] * InitialImage[ii_tl_y, ii_tl_x + 1, :] ) + \
( weight_bl[:, None] * InitialImage[ii_tl_y + 1, ii_tl_x, :] ) + \
( weight_br[:, None] * InitialImage[ii_tl_y + 1, ii_tl_x + 1, :] )
# Getting x coorinate to remove black region from right and left in the transformed image
min_x = min(ti_x)
# Cropping out the black region from both sides (using symmetricity)
TransformedImage = TransformedImage[:, min_x : -min_x, :]
return TransformedImage, ti_x-min_x, ti_y
if __name__ == "__main__":
# Reading images.
Images = ReadImage("Images2")
BaseImage, _, _ = ProjectOntoCylinder(Images[0])
for i in range(1, len(Images)):
StitchedImage = StitchImages(BaseImage, Images[i])
BaseImage = StitchedImage.copy()
print("Stitching...", i)
cv2.imwrite("Stitched_Panorama.png", BaseImage)