13 # size of block that will be matched
14 # (correlate a block this size against a block with added slippage all around)
18 # pitch between the block centres
22 # allowable +/- slippage between pairs
26 CORNER_CANDIDATES = 10
34 _, _, cs = image.shape
35 num = image - numpy.stack(
37 #scipy.ndimage.gaussian_filter(image[:, :, i], CUTOFF1, mode = 'mirror') -
38 scipy.ndimage.gaussian_filter(image[:, :, i], CUTOFF0, mode = 'mirror')
44 scipy.ndimage.gaussian_filter(
45 numpy.sum(numpy.square(num), 2),
50 denom[denom < EPSILON] = EPSILON
51 return num / denom[:, :, numpy.newaxis]
53 def calc_match(normalized0, normalized1, xc, yc):
56 x1 = xc - XM // 2 - XS
57 y1 = yc - YM // 2 - YS
70 # note: swapping block1, block0 flips the output (subtracts x and y
71 # from BLOCK_SIZE) and we need this to find matching part of image1
75 scipy.signal.correlate(
86 # find slippage from correlation
87 yo, xo = numpy.unravel_index(numpy.argmax(corr), corr.shape)
88 max_corr = corr[yo, xo]
90 temp = .5 + .5 * (corr / max_corr)
92 max(yo - 21, 0):max(yo + 22, 0),
93 max(xo - 1, 0):max(xo + 2, 0)
96 max(yo - 1, 0):max(yo + 2, 0),
97 max(xo - 21, 0):max(xo + 22, 0)
99 gamma.write_image(f'diag_{xc:d}_{yc:d}_corr.png', temp)
101 max_corr < EPSILON or
103 xo > XS * 2 - CUTOFF1 or
105 yo > YS * 2 - CUTOFF1
109 # estimate position within block of feature being matched
110 block10 = block1[yo:yo + YM, xo:xo + XM, :]
111 match = numpy.sum(block0 * block10, 2)
112 #print('xxx', numpy.sum(match), max_corr)
115 numpy.sum(match, 0) @ numpy.arange(XM, dtype = numpy.double)
118 numpy.sum(match, 1) @ numpy.arange(YM, dtype = numpy.double)
121 x = int(math.floor(xf))
122 y = int(math.floor(yf))
126 max(y - 21, 0):max(y + 22, 0),
127 max(x - 1, 0):max(x + 2, 0)
130 max(y - 1, 0):max(y + 2, 0),
131 max(x - 21, 0):max(x + 22, 0)
133 gamma.write_image(f'diag_{xc:d}_{yc:d}_match.png', temp)
137 max(y - 21, 0):max(y + 22, 0),
138 max(x - 1, 0):max(x + 2, 0),
142 max(y - 1, 0):max(y + 2, 0),
143 max(x - 21, 0):max(x + 22, 0),
146 gamma.write_image(f'diag_{xc:d}_{yc:d}_block0.png', diag0)
150 max(y - 21, 0):max(y + 22, 0),
151 max(x - 1, 0):max(x + 2, 0),
155 max(y - 1, 0):max(y + 2, 0),
156 max(x - 21, 0):max(x + 22, 0),
159 gamma.write_image(f'diag_{xc:d}_{yc:d}_block10.png', diag1)
165 max(y - 21, 0):max(y + 22, 0),
166 max(x - 1, 0):max(x + 2, 0),
170 max(y - 1, 0):max(y + 2, 0),
171 max(x - 21, 0):max(x + 22, 0),
174 gamma.write_image(f'diag_{xc:d}_{yc:d}_block11.png', diag1)
176 # return offset and feature relative to block centre
177 return xo - XS, yo - YS, xf - XM // 2, yf - YM // 2
180 if len(sys.argv) >= 2 and sys.argv[1] == '--diag':
183 if len(sys.argv) < 3:
184 print(f'usage: {sys.argv[0]} in_prefix out_prefix')
186 in_prefix = sys.argv[1]
187 out_prefix = sys.argv[2]
189 # compile list of input and output files by directory search
190 i = in_prefix.rfind('/') + 1
191 in_prefix0 = in_prefix[:i] # directory part
192 in_prefix1 = in_prefix[i:] # filename part
194 (file, out_prefix + file[len(in_prefix):])
198 for file in os.listdir(in_prefix0 if len(in_prefix0) else '.')
199 if file[:len(in_prefix1)] == in_prefix1
204 # first file is special as no transformation needs to be done
205 in_png, out_png = files[0]
207 print(f'read {in_png:s}')
208 image0 = gamma.read_image(in_png)
211 sys.stderr.write(f'write {out_png:s}\n')
212 gamma.write_image(out_png, image0)
215 xb = (xs - XM - 2 * XS) // XP // 2
216 yb = (ys - YM - 2 * YS) // YP // 2
217 print('xb', xb, 'yb', yb)
220 normalized0 = normalize(image0)
222 gamma.write_image('normalized0.png', normalized0 + .5)
224 # loop through remaining files comparing each to the previous
225 cumulative_A = numpy.identity(3, numpy.double)
226 for file in range(1, len(files)):
227 in_png, out_png = files[file]
229 print(f'read {in_png:s}')
230 image1 = gamma.read_image(in_png)
231 assert image1.shape == shape
234 normalized1 = normalize(image1)
236 gamma.write_image(f'normalized{file:d}.png', normalized1 + .5)
238 print('find corner candidates')
243 print('i', i, 'j', j)
245 # correlate blocks in (i, j)-corner
249 yc = YS + YM // 2 + k * YP
253 xc = XS + XM // 2 + l * XP
256 match = calc_match(normalized0, normalized1, xc, yc)
257 if match is not None:
258 offsets.append(match [:2])
259 xo, yo, xf, yf = match
266 blocks.append((xf0, yf0, xf1, yf1))
268 # find the offset trend (median offset per axis) in (i, j)-corner
269 k = len(offsets) // 2
270 xo_median = sorted([xo for xo, _ in offsets])[k]
271 yo_median = sorted([yo for _, yo in offsets])[k]
272 #print('i', i, 'j', j, 'xo_median', xo_median, 'yo_median', yo_median)
274 # sort all offsets by their closeness to trend in (i,j)-corner
275 u = numpy.array(offsets, numpy.double)
276 v = numpy.array([xo_median, yo_median], numpy.double)
277 dist = numpy.sum(numpy.square(u - v[numpy.newaxis, :]), 1)
281 for _, k in sorted([(dist[k], k) for k in range(dist.shape[0])]):
282 xf0, yf0, xf1, yf1 = blocks[k]
283 p.append(numpy.array([xf0, yf0], numpy.double))
284 q.append(numpy.array([xf1, yf1], numpy.double))
285 p = numpy.stack(p, 1)
286 q = numpy.stack(q, 1)
291 # try all combinations of the corner candidates
292 print('try corner candidates')
295 for i in range(min(p_all[0].shape[1], CORNER_CANDIDATES)):
296 for j in range(min(p_all[1].shape[1], CORNER_CANDIDATES)):
297 for k in range(min(p_all[2].shape[1], CORNER_CANDIDATES)):
298 for l in range(min(p_all[3].shape[1], CORNER_CANDIDATES)):
299 A = perspective.calc_transform(
301 [p_all[0][:, i], p_all[1][:, j], p_all[2][:, k], p_all[3][:, l]],
305 [q_all[0][:, i], q_all[1][:, j], q_all[2][:, k], q_all[3][:, l]],
314 q_all[m] - perspective.apply_transform_multi(A, p_all[m])
319 dist += sorted(dists)[len(dists) // 2] # median position error
321 if best_dist is None or dist < best_dist:
324 cumulative_A = best_A @ cumulative_A
327 diag0 = numpy.copy(image0)
328 diag1 = numpy.copy(image1)
330 for j in range(p_all[i].shape[1]):
331 xf0, yf0 = numpy.floor(p_all[i][:, j]).astype(numpy.int32)
333 max(yf0 - 21, 0):max(yf0 + 22, 0),
334 max(xf0 - 1, 0):max(xf0 + 2, 0),
338 max(yf0 - 1, 0):max(yf0 + 2, 0),
339 max(xf0 - 21, 0):max(xf0 + 22, 0),
343 xf1, yf1 = numpy.floor(q_all[i][:, j]).astype(numpy.int32)
345 max(yf1 - 21, 0):max(yf1 + 22, 0),
346 max(xf1 - 1, 0):max(xf1 + 2, 0),
350 max(yf1 - 1, 0):max(yf1 + 2, 0),
351 max(xf1 - 21, 0):max(xf1 + 22, 0),
354 gamma.write_image(f'diag_file{file:d}_0.png', diag0)
355 gamma.write_image(f'diag_file{file:d}_1.png', diag1)
358 out_image1 = perspective.remap_image(cumulative_A, image1)
360 sys.stderr.write(f'write {out_png:s}\n')
361 gamma.write_image(out_png, out_image1)
364 normalized0 = normalized1