-
Notifications
You must be signed in to change notification settings - Fork 42
/
main.py
226 lines (189 loc) · 8.6 KB
/
main.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
#!/usr/bin/python
import numpy as np
import cv2
import argparse
import dewarp
import feature_matching
import optimal_seamline
import blending
import cropping
import os
# --------------------------------
# output video resolution
W = 2560
H = 1280
# --------------------------------
# field of view, width of de-warped image
FOV = 194.0
W_remap = 1380
# --------------------------------
# params for template matching
templ_shape = (60, 16)
offsetYL = 160
offsetYR = 160
maxL = 80
maxR = 80
# --------------------------------
# params for optimal seamline and multi-band blending
W_lbl = 120
blend_level = 7
# --------------------------------
dir_path = os.path.dirname(os.path.realpath(__file__))
cwd = os.getcwd()
# --------------------------------
def Hcalc(cap, xmap, ymap):
"""Calculate and return homography for stitching process."""
Mlist = []
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
for frame_no in np.arange(0, frame_count, int(frame_count / 10)):
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_no)
ret, frame = cap.read()
if ret:
# defish / unwarp
cam1 = cv2.remap(frame[:, :1280], xmap, ymap, cv2.INTER_LINEAR)
cam2 = cv2.remap(frame[:, 1280:], xmap, ymap, cv2.INTER_LINEAR)
cam1_gray = cv2.cvtColor(cam1, cv2.COLOR_BGR2GRAY)
cam2_gray = cv2.cvtColor(cam2, cv2.COLOR_BGR2GRAY)
# shift the remapped images along x-axis
shifted_cams = np.zeros((H * 2, W, 3), np.uint8)
shifted_cams[H:, (W - W_remap) / 2:(W + W_remap) / 2] = cam2
shifted_cams[:H, :W_remap / 2] = cam1[:, W_remap / 2:]
shifted_cams[:H, W - W_remap / 2:] = cam1[:, :W_remap / 2]
# find matches and extract pairs of correspondent matching points
matchesL = feature_matching.getMatches_goodtemplmatch(
cam1_gray[offsetYL:H - offsetYL, W / 2:],
cam2_gray[offsetYL:H - offsetYL, :W_remap - W / 2],
templ_shape, maxL)
matchesR = feature_matching.getMatches_goodtemplmatch(
cam2_gray[offsetYR:H - offsetYR, W / 2:],
cam1_gray[offsetYR:H - offsetYR, :W_remap - W / 2],
templ_shape, maxR)
matchesR = matchesR[:, -1::-1]
matchesL = matchesL + ((W - W_remap) / 2, offsetYL)
matchesR = matchesR + ((W - W_remap) / 2 + W / 2, offsetYR)
zipped_matches = zip(matchesL, matchesR)
matches = np.int32([e for i in zipped_matches for e in i])
pts1 = matches[:, 0]
pts2 = matches[:, 1]
# find homography from pairs of correspondent matchings
M, status = cv2.findHomography(pts2, pts1, cv2.RANSAC, 4.0)
Mlist.append(M)
M = np.average(np.array(Mlist), axis=0)
print M
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
return M
def main(input, output):
cap = cv2.VideoCapture(input)
# define the codec and create VideoWriter object
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter(output, fourcc, 30.0, (W, H))
# obtain xmap and ymap
xmap, ymap = dewarp.buildmap(Ws=W_remap, Hs=H, Wd=1280, Hd=1280, fov=FOV)
# calculate homography
M = Hcalc(cap, xmap, ymap)
# calculate vertical boundary of warped image, for later cropping
top, bottom = cropping.verticalBoundary(M, W_remap, W, H)
# estimate empty (invalid) area of warped2
EAof2 = np.zeros((H, W, 3), np.uint8)
EAof2[:, (W - W_remap) / 2 + 1:(W + W_remap) / 2 - 1] = 255
EAof2 = cv2.warpPerspective(EAof2, M, (W, H))
# process the first frame
ret, frame = cap.read()
if ret:
# de-warp
cam1 = cv2.remap(frame[:, :1280], xmap, ymap, cv2.INTER_LINEAR)
cam2 = cv2.remap(frame[:, 1280:], xmap, ymap, cv2.INTER_LINEAR)
# shift the remapped images along x-axis
shifted_cams = np.zeros((H * 2, W, 3), np.uint8)
shifted_cams[H:, (W - W_remap) / 2:(W + W_remap) / 2] = cam2
shifted_cams[:H, :W_remap / 2] = cam1[:, W_remap / 2:]
shifted_cams[:H, W - W_remap / 2:] = cam1[:, :W_remap / 2]
# warp cam2 using homography M
warped2 = cv2.warpPerspective(shifted_cams[H:], M, (W, H))
warped1 = shifted_cams[:H]
# crop to get a largest rectangle, and resize to maintain resolution
warped1 = cv2.resize(warped1[top:bottom], (W, H))
warped2 = cv2.resize(warped2[top:bottom], (W, H))
# image labeling (find minimum error boundary cut)
mask, minloc_old = optimal_seamline.imgLabeling(
warped1[:, W_remap / 2 - W_lbl:W_remap / 2],
warped2[:, W_remap / 2 - W_lbl:W_remap / 2],
warped1[:, W - W_remap / 2:W - W_remap / 2 + W_lbl],
warped2[:, W - W_remap / 2:W - W_remap / 2 + W_lbl],
(W, H), W_remap / 2 - W_lbl, W - W_remap / 2)
labeled = warped1 * mask + warped2 * (1 - mask)
# fill empty area of warped1 and warped2, to avoid darkening
warped1[:, W_remap / 2:W - W_remap /
2] = warped2[:, W_remap / 2:W - W_remap / 2]
warped2[EAof2 == 0] = warped1[EAof2 == 0]
# multi band blending
blended = blending.multi_band_blending(
warped1, warped2, mask, blend_level)
cv2.imshow('p', blended.astype(np.uint8))
cv2.waitKey(0)
# write results from phases
out.write(blended.astype(np.uint8))
cv2.imwrite(dir_path + '/output/0.png', cam1)
cv2.imwrite(dir_path + '/output/1.png', cam2)
cv2.imwrite(dir_path + '/output/2.png', shifted_cams)
cv2.imwrite(dir_path + '/output/3.png', warped2)
cv2.imwrite(dir_path + '/output/4.png', warped1)
cv2.imwrite(dir_path + '/output/labeled.png', labeled.astype(np.uint8))
cv2.imwrite(dir_path + '/output/blended.png', blended.astype(np.uint8))
# process each frame
while(cap.isOpened()):
ret, frame = cap.read()
if ret:
# de-warp
cam1 = cv2.remap(frame[:, :1280], xmap, ymap, cv2.INTER_LINEAR)
cam2 = cv2.remap(frame[:, 1280:], xmap, ymap, cv2.INTER_LINEAR)
# shift the remapped images along x-axis
shifted_cams = np.zeros((H * 2, W, 3), np.uint8)
shifted_cams[H:, (W - W_remap) / 2:(W + W_remap) / 2] = cam2
shifted_cams[:H, :W_remap / 2] = cam1[:, W_remap / 2:]
shifted_cams[:H, W - W_remap / 2:] = cam1[:, :W_remap / 2]
# warp cam2 using homography M
warped2 = cv2.warpPerspective(shifted_cams[H:], M, (W, H))
warped1 = shifted_cams[:H]
# crop to get a largest rectangle
# and resize to maintain resolution
warped1 = cv2.resize(warped1[top:bottom], (W, H))
warped2 = cv2.resize(warped2[top:bottom], (W, H))
# image labeling (find minimum error boundary cut)
mask, minloc_old = optimal_seamline.imgLabeling(
warped1[:, W_remap / 2 - W_lbl:W_remap / 2],
warped2[:, W_remap / 2 - W_lbl:W_remap / 2],
warped1[:, W - W_remap / 2:W - W_remap / 2 + W_lbl],
warped2[:, W - W_remap / 2:W - W_remap / 2 + W_lbl],
(W, H), W_remap / 2 - W_lbl, W - W_remap / 2, minloc_old)
labeled = warped1 * mask + warped2 * (1 - mask)
# fill empty area of warped1 and warped2, to avoid darkening
warped1[:, W_remap / 2:W - W_remap /
2] = warped2[:, W_remap / 2:W - W_remap / 2]
warped2[EAof2 == 0] = warped1[EAof2 == 0]
# multi band blending
blended = blending.multi_band_blending(
warped1, warped2, mask, blend_level)
# write the remapped frame
out.write(blended.astype(np.uint8))
cv2.imshow('warped', blended.astype(np.uint8))
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
# release everything if job is finished
cap.release()
out.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser(
description="A summer research project to seamlessly stitch \
dual-fisheye video into 360-degree videos")
ap.add_argument('input', metavar='INPUT.XYZ',
help="path to the input dual fisheye video")
ap.add_argument('-o', '--output', metavar='OUTPUT.XYZ', required=False,
default=dir_path + '/output/output.MP4',
help="path to the output equirectangular video")
args = vars(ap.parse_args())
main(args['input'], args['output'])