-
Notifications
You must be signed in to change notification settings - Fork 1
/
a7.py
232 lines (203 loc) · 9.65 KB
/
a7.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
import numpy as np
from scipy import ndimage, linalg
import random
import a6
import math
class point():
def __init__(self, x, y):
self.x=x
self.y=y
class feature():
def __init__(self, pt, descriptor):
self.pt=pt
self.descriptor=descriptor
class correspondence():
def __init__(self, pt1, pt2):
self.pt1=pt1
self.pt2=pt2
### HELPERS
def BW(im, weights=[0.3,0.6,0.1]):
out = np.zeros((im.shape[0], im.shape[1]))
(height, width, rgb) = np.shape(im)
for y in xrange(height):
for x in xrange(width):
out[y][x] = np.dot(im[y][x], weights)
return out
def imIter(im):
for y in range(0,im.shape[0]):
for x in range(0,im.shape[1]):
yield (y,x)
### END HELPERS
def computeTensor(im, sigmaG=1, factorSigma=4):
'''im_out: 3-channel-2D array. The three channels are Ixx, Ixy, Iyy'''
lumiBlurred = ndimage.filters.gaussian_filter(BW(im), sigmaG)
sobel = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float64)
gradX = ndimage.filters.convolve(lumiBlurred, sobel, mode='reflect')
gradY = ndimage.filters.convolve(lumiBlurred, np.transpose(sobel), mode='reflect')
im_out = np.zeros(im.shape)
for y, x in imIter(im_out):
Ix = gradX[y, x]
Iy = gradY[y, x]
im_out[y, x] = np.array([Ix**2, Ix*Iy, Iy**2])
s = sigmaG * factorSigma
im_out = ndimage.filters.gaussian_filter(im_out, [s, s, 0])
return im_out
def cornerResponse(im, k=0.15, sigmaG=1, factorSigma=4):
'''resp: 2D array charactering the response'''
tensor = computeTensor(im, sigmaG, factorSigma)
resp = np.zeros((im.shape[0], im.shape[1]))
for y, x in imIter(resp):
M = np.array([[tensor[y, x, 0], tensor[y, x, 1]], [tensor[y, x, 1], tensor[y, x, 2]]])
R = linalg.det(M) - k * (np.trace(M)**2)
if R > 0:
resp[y, x] = R
return resp
def HarrisCorners(im, k=0.15, sigmaG=1, factor=4, maxiDiam=7, boundarySize=5):
'''result: a list of points that locate the images' corners'''
possibleCorners = cornerResponse(im, k, sigmaG, factor)
maxima = ndimage.filters.maximum_filter(possibleCorners, maxiDiam)
corners = (possibleCorners == maxima)
corners[possibleCorners == 0] = False
height, width = corners.shape
corners[0:boundarySize] = corners[height-1:height-boundarySize-1:-1] = False
corners[:,0:boundarySize] = corners[:,width-1:width-boundarySize-1:-1] = False
return [point(x[1], x[0]) for x in np.transpose(np.nonzero(corners))]
def computeFeatures(im, cornerL, sigmaBlurDescriptor=0.5, radiusDescriptor=4):
'''f_list: a list of feature objects'''
lumiBlurred = ndimage.filters.gaussian_filter(BW(im), sigmaBlurDescriptor)
f_list = []
for corner in cornerL:
desc = descriptor(lumiBlurred, corner, radiusDescriptor)
desc = 1.0/desc.std() * (desc - desc.mean())
f_list.append(feature(corner, desc))
return f_list
def descriptor(blurredIm, P, radiusDescriptor=4):
'''patch: descriptor around 2-D point P, with size (2*radiusDescriptor+1)^2 in 1-D'''
return blurredIm[P.y-radiusDescriptor:P.y+radiusDescriptor+1, P.x-radiusDescriptor:P.x+radiusDescriptor+1].flatten()
def findCorrespondences(listFeatures1, listFeatures2, threshold=1.7):
'''correpondences: a list of correspondences object that associate two feature lists.'''
features = [findCorrespondence(feature1, listFeatures2, threshold) for feature1 in listFeatures1]
return filter(lambda x: x != None, features)
def findCorrespondence(feature1, listFeatures2, threshold=1.7):
sumSquares = [sum((feature1.descriptor - feature2.descriptor)**2) for feature2 in listFeatures2]
minIndex, minDistSq = (np.argmin(sumSquares), np.min(sumSquares))
del sumSquares[minIndex]
secondMinDistSq = np.min(sumSquares)
if secondMinDistSq / minDistSq < threshold**2:
return None
return correspondence(feature1.pt, listFeatures2[minIndex].pt)
def RANSAC(listOfCorrespondences, Niter=1000, epsilon=4, acceptableProbFailure=1e-9):
'''H_best: the best estimation of homorgraphy (3-by-3 matrix)'''
'''inliers: A list of booleans that describe whether the element in listOfCorrespondences
an inlier or not'''
''' 6.815 can bypass acceptableProbFailure'''
numInliers = 0
inliers = [False] * len(listOfCorrespondences)
H_best = np.zeros((3,3))
for n in xrange(Niter):
correspondences = random.sample(listOfCorrespondences, 4)
listOfPairs = A7PairsToA6Pairs(correspondences)
H = a6.computeHomography(listOfPairs)
currentInliers = []
for c in listOfCorrespondences:
estimate = H.dot(A7PointToA6Point(c.pt1))
estimate[0], estimate[1] = (estimate[0] / estimate[2], estimate[1] / estimate[2])
actual = A7PointToA6Point(c.pt2)
error = math.sqrt(sum((estimate - actual)**2))
currentInliers.append(bool(error < epsilon))
currentNumInliers = sum(bool(x) for x in currentInliers)
if currentNumInliers > numInliers:
numInliers = currentNumInliers
inliers = currentInliers
H_best = H.copy()
if (1 - (float(numInliers) / len(listOfCorrespondences))**4) ** n < acceptableProbFailure:
break
return (H_best, inliers)
def computeNHomographies(L, refIndex, blurDescriptor=0.5, radiusDescriptor=4):
'''H_list: a list of Homorgraphy relative to L[refIndex]'''
'''Note: len(H_list) is equal to len(L)'''
H_i = []
for i in xrange(len(L) - 1):
im1Features = computeFeatures(L[i], HarrisCorners(L[i]), blurDescriptor, radiusDescriptor)
im2Features = computeFeatures(L[i+1], HarrisCorners(L[i+1]), blurDescriptor, radiusDescriptor)
correspondences = findCorrespondences(im1Features, im2Features)
H_i.append(RANSAC(correspondences)[0])
N = len(L)
homographies = [np.zeros((3,3))] * N
homographies[refIndex] = np.identity(3)
for i in xrange(N):
if i < refIndex:
homographies[i] = reduce(lambda x, y: x.dot(y), H_i[i:refIndex], np.identity(3))
elif i > refIndex:
if refIndex == 0:
homographies[i] = reduce(lambda x, y: x.dot(linalg.inv(y)), H_i[i-1::-1], np.identity(3))
else:
homographies[i] = reduce(lambda x, y: x.dot(linalg.inv(y)), H_i[i-1:refIndex-1:-1], np.identity(3))
return homographies
def autostitch(L, refIndex, blurDescriptor=0.5, radiusDescriptor=4):
'''Use your a6 code to stitch the images. You need to hand in your A6 code'''
H_list = computeNHomographies(L, refIndex, blurDescriptor, radiusDescriptor)
return a6.compositeNImages(L, H_list)
def weight_map(h,w):
''' Given the image dimension h and w, return the hxwx3 weight map for linear blending'''
w_map = np.zeros((h, w, 3))
halfHeight = float(h/2)
halfWidth = float(w/2)
for y, x in imIter(w_map):
w_map[y, x] = (1 - abs(y - halfHeight)/halfHeight) * (1 - abs(x - halfWidth)/halfWidth)
return w_map
def linear_blending(L, refIndex, blurDescriptor=0.5, radiusDescriptor=4):
''' Return the stitching result with linear blending'''
H_list = computeNHomographies(L, refIndex, blurDescriptor, radiusDescriptor)
weights = weight_map(L[0].shape[0], L[0].shape[1])
return compositeNImages(L, H_list, weights)
def two_scale_blending(L, refIndex, blurDescriptor=0.5, radiusDescriptor=4):
''' Return the stitching result with two scale blending'''
spatialSigma = 2
H_list = computeNHomographies(L, refIndex, blurDescriptor, radiusDescriptor)
weights = weight_map(L[0].shape[0], L[0].shape[1])
L_lowFreq = [ndimage.filters.gaussian_filter(im, [spatialSigma, spatialSigma, 0]) for im in L]
L_highFreq = [im - imL for im, imL in zip(L, L_lowFreq)]
panoLow = compositeNImages(L_lowFreq, H_list, weights)
panoHigh = compositeNImages(L_highFreq, H_list, weights, True)
return panoLow + panoHigh
### MODIFIED FROM A6
def compositeNImages(listOfImages, listOfH, weights, abrupt = False):
# Computes the composite image. listOfH is of the form returned by computeNHomographies.
# Hint: You will need to deal with bounding boxes and translations again in this function.
bbox = a6.computeTransformedBBox(listOfImages[0].shape, listOfH[0])
for img, H in zip(listOfImages, listOfH):
currentbbox = a6.computeTransformedBBox(img.shape, H)
bbox = a6.bboxUnion(currentbbox, bbox)
trans = a6.translate(bbox)
out = np.zeros((bbox[1][0] - bbox[0][0], bbox[1][1] - bbox[0][1], 3))
weightIm = np.zeros((out.shape[0], out.shape[1], 3))
for img, H in zip(listOfImages, listOfH):
applyHomographyFast(img, out, trans.dot(H), weights, weightIm, abrupt)
weightIm[weightIm == 0] = np.min(weightIm[weightIm.nonzero()]) # no zero in denominators
return out / weightIm
def applyHomographyFast(source, out, H, weights, weightIm, abrupt = False):
# takes the image source, warps it by the homography H, and adds it to the composite out.
# This version should only iterate over the pixels inside the bounding box of source's image in out.
bbox = a6.computeTransformedBBox(source.shape, H)
Hinv = linalg.inv(H)
for y in xrange(bbox[0][0], bbox[1][0]):
for x in xrange(bbox[0][1], bbox[1][1]):
yp, xp, w = Hinv.dot(np.array([y, x, 1.0]))
yp, xp = (yp / w, xp / w)
if yp >= 0 and yp < source.shape[0] and xp >= 0 and xp < source.shape[1]:
if abrupt and weights[yp, xp, 0] > weightIm[y, x, 0]:
weightIm[y, x] = weights[yp, xp]
out[y, x] = weights[yp, xp] * a6.interpolateLin(source, yp, xp)
elif not abrupt:
weightIm[y, x] += weights[yp, xp]
out[y, x] += weights[yp, xp] * a6.interpolateLin(source, yp, xp)
# Helpers, you may use the following scripts for convenience.
def A7PointToA6Point(a7_point):
return np.array([a7_point.y, a7_point.x, 1.0], dtype=np.float64)
def A7PairsToA6Pairs(a7_pairs):
A7pointList1=map(lambda pair: pair.pt1 ,a7_pairs)
A6pointList1=map(A7PointToA6Point, A7pointList1)
A7pointList2=map(lambda pair: pair.pt2 ,a7_pairs)
A6pointList2=map(A7PointToA6Point, A7pointList2)
return zip(A6pointList1, A6pointList2)