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 package org.openimaj.image.analysis.algorithm;
31
32 import java.util.Comparator;
33
34 import org.openimaj.image.FImage;
35 import org.openimaj.image.analyser.ImageAnalyser;
36 import org.openimaj.image.pixel.FValuePixel;
37 import org.openimaj.image.processing.algorithm.FourierCorrelation;
38 import org.openimaj.math.geometry.shape.Rectangle;
39 import org.openimaj.math.util.FloatArrayStatsUtils;
40
41
42
43
44
45
46
47
48
49 public class FourierTemplateMatcher implements ImageAnalyser<FImage> {
50
51
52
53
54
55 public enum Mode {
56
57
58
59
60
61
62 SUM_SQUARED_DIFFERENCE {
63 @Override
64 public boolean scoresAscending() {
65 return false;
66 }
67
68 @Override
69 public void processCorrelationMap(FImage img, FImage template, FImage corr) {
70 SummedSqAreaTable sum = new SummedSqAreaTable();
71 img.analyseWith(sum);
72
73 float templateMean = FloatArrayStatsUtils.mean(template.pixels);
74 float templateStdDev = FloatArrayStatsUtils.std(template.pixels);
75
76 float templateNorm = templateStdDev * templateStdDev;
77 float templateSum2 = templateNorm + templateMean * templateMean;
78
79 templateNorm = templateSum2;
80
81 double invArea = 1.0 / ((double)template.width * template.height);
82 templateSum2 /= invArea;
83 templateNorm = (float) Math.sqrt(templateNorm);
84 templateNorm /= Math.sqrt(invArea);
85
86 final float[][] pix = corr.pixels;
87
88 for( int y = 0; y < corr.height; y++ ) {
89 for( int x = 0; x < corr.width; x++ ) {
90 double num = pix[y][x];
91 double wndSum2 = 0;
92
93 double t = sum.calculateSqSumArea(x, y, x+template.width, y+template.height);
94 wndSum2 += t;
95
96 num = wndSum2 - 2*num + templateSum2;
97
98 pix[y][x] = (float)num;
99 }
100 }
101 }
102 },
103
104
105
106
107
108
109 NORM_SUM_SQUARED_DIFFERENCE {
110 @Override
111 public boolean scoresAscending() {
112 return false;
113 }
114
115 @Override
116 public void processCorrelationMap(FImage img, FImage template, FImage corr) {
117 SummedSqAreaTable sum = new SummedSqAreaTable();
118 img.analyseWith(sum);
119
120 float templateMean = FloatArrayStatsUtils.mean(template.pixels);
121 float templateStdDev = FloatArrayStatsUtils.std(template.pixels);
122
123 float templateNorm = templateStdDev * templateStdDev;
124 float templateSum2 = templateNorm + templateMean * templateMean;
125
126 templateNorm = templateSum2;
127
128 double invArea = 1.0 / ((double)template.width * template.height);
129 templateSum2 /= invArea;
130 templateNorm = (float) Math.sqrt(templateNorm);
131 templateNorm /= Math.sqrt(invArea);
132
133 final float[][] pix = corr.pixels;
134
135 for( int y = 0; y < corr.height; y++ ) {
136 for( int x = 0; x < corr.width; x++ ) {
137 double num = pix[y][x];
138 double wndMean2 = 0, wndSum2 = 0;
139
140 double t = sum.calculateSqSumArea(x, y, x+template.width, y+template.height);
141 wndSum2 += t;
142
143 num = wndSum2 - 2*num + templateSum2;
144
145 t = Math.sqrt( Math.max(wndSum2 - wndMean2, 0) ) * templateNorm;
146 num /= t;
147
148 pix[y][x] = (float)num;
149 }
150 }
151 }
152 },
153
154
155
156
157
158
159 CORRELATION {
160 @Override
161 public boolean scoresAscending() {
162 return true;
163 }
164
165 @Override
166 public void processCorrelationMap(FImage img, FImage template, FImage corr) {
167
168 }
169 },
170
171
172
173
174
175
176 NORM_CORRELATION {
177 @Override
178 public boolean scoresAscending() {
179 return true;
180 }
181
182 @Override
183 public void processCorrelationMap(FImage img, FImage template, FImage corr) {
184 SummedSqAreaTable sum = new SummedSqAreaTable();
185 img.analyseWith(sum);
186
187 float templateMean = FloatArrayStatsUtils.mean(template.pixels);
188 float templateStdDev = FloatArrayStatsUtils.std(template.pixels);
189
190 float templateNorm = templateStdDev * templateStdDev;
191 templateNorm += templateMean * templateMean;
192
193 double invArea = 1.0 / ((double)template.width * template.height);
194 templateNorm = (float) Math.sqrt(templateNorm);
195 templateNorm /= Math.sqrt(invArea);
196
197 final float[][] pix = corr.pixels;
198
199 for( int y = 0; y < corr.height; y++ ) {
200 for( int x = 0; x < corr.width; x++ ) {
201 double num = pix[y][x];
202 double wndMean2 = 0, wndSum2 = 0;
203
204 double t = sum.calculateSqSumArea(x, y, x+template.width, y+template.height);
205 wndSum2 += t;
206
207 t = Math.sqrt( Math.max(wndSum2 - wndMean2, 0) ) * templateNorm;
208 num /= t;
209
210 pix[y][x] = (float)num;
211 }
212 }
213 }
214 },
215
216
217
218
219
220
221 CORRELATION_COEFFICIENT {
222 @Override
223 public boolean scoresAscending() {
224 return true;
225 }
226
227 @Override
228 public void processCorrelationMap(FImage img, FImage template, FImage corr) {
229 SummedAreaTable sum = new SummedAreaTable();
230 img.analyseWith(sum);
231
232 final float templateMean = FloatArrayStatsUtils.mean(template.pixels);
233 final float[][] pix = corr.pixels;
234
235 for( int y = 0; y < corr.height; y++ ) {
236 for( int x = 0; x < corr.width; x++ ) {
237 double num = pix[y][x];
238 double t = sum.calculateArea(x, y, x+template.width, y+template.height);
239
240 num -= t * templateMean;
241
242 pix[y][x] = (float)num;
243 }
244 }
245 }
246 },
247
248
249
250
251
252
253 NORM_CORRELATION_COEFFICIENT {
254 @Override
255 public boolean scoresAscending() {
256 return true;
257 }
258
259 @Override
260 public void processCorrelationMap(FImage img, FImage template, FImage corr) {
261 SummedSqAreaTable sum = new SummedSqAreaTable();
262 img.analyseWith(sum);
263
264 float templateMean = FloatArrayStatsUtils.mean(template.pixels);
265 float templateStdDev = FloatArrayStatsUtils.std(template.pixels);
266
267 float templateNorm = templateStdDev;
268
269 if( templateNorm == 0 )
270 {
271 corr.fill(1);
272 return;
273 }
274
275 double invArea = 1.0 / ((double)template.width * template.height);
276 templateNorm /= Math.sqrt(invArea);
277
278 final float[][] pix = corr.pixels;
279
280 for( int y = 0; y < corr.height; y++ ) {
281 for( int x = 0; x < corr.width; x++ ) {
282 double num = pix[y][x];
283
284 double t = sum.calculateSumArea(x, y, x+template.width, y+template.height);
285 double wndMean2 = t * t * invArea;
286 num -= t * templateMean;
287
288 double wndSum2 = sum.calculateSqSumArea(x, y, x+template.width, y+template.height);
289
290 t = Math.sqrt( Math.max(wndSum2 - wndMean2, 0) ) * templateNorm;
291 num /= t;
292
293 pix[y][x] = (float)num;
294 }
295 }
296 }
297 }
298 ;
299
300
301
302
303
304 public abstract boolean scoresAscending();
305
306
307
308
309
310
311
312
313 public abstract void processCorrelationMap(FImage img, FImage template, FImage corr);
314 }
315
316 private FourierCorrelation correlation;
317 private Mode mode;
318 private Rectangle searchBounds;
319 private FImage responseMap;
320 private int templateWidth;
321 private int templateHeight;
322
323
324
325
326
327
328
329
330
331 public FourierTemplateMatcher(FImage template, Mode mode) {
332 this.correlation = new FourierCorrelation(template);
333 this.mode = mode;
334 this.templateWidth = template.width;
335 this.templateHeight = template.height;
336 }
337
338
339
340
341
342
343
344
345
346
347 public FourierTemplateMatcher(FImage template, Rectangle bounds, Mode mode) {
348 this(template, mode);
349 this.searchBounds = bounds;
350 }
351
352
353
354
355 public Rectangle getSearchBounds() {
356 return searchBounds;
357 }
358
359
360
361
362
363
364
365
366
367 public void setSearchBounds(Rectangle searchBounds) {
368 this.searchBounds = searchBounds;
369 }
370
371
372
373
374
375
376
377
378
379
380 @Override
381 public void analyseImage(FImage image) {
382 FImage subImage;
383
384 if (this.searchBounds != null) {
385 final int halfWidth = templateWidth / 2;
386 final int halfHeight = templateHeight / 2;
387
388 int x = (int) Math.max(searchBounds.x - halfWidth, 0);
389 int width = (int) searchBounds.width + templateWidth;
390 if (searchBounds.x - halfWidth < 0) {
391 width += (searchBounds.x - halfWidth);
392 }
393 if (x + width > image.width)
394 width = image.width;
395
396 int y = (int) Math.max(searchBounds.y - halfHeight, 0);
397 int height = (int) searchBounds.height + templateHeight;
398 if (searchBounds.y - halfHeight < 0) {
399 height += (searchBounds.y - halfHeight);
400 }
401 if (y + height > image.height)
402 height = image.height;
403
404
405 subImage = image.extractROI(
406 x,
407 y,
408 width,
409 height
410 );
411 } else {
412 subImage = image.clone();
413 }
414
415 responseMap = subImage.process(correlation);
416 responseMap.height = responseMap.height - correlation.template.height + 1;
417 responseMap.width = responseMap.width - correlation.template.width + 1;
418
419 mode.processCorrelationMap(subImage, correlation.template, responseMap);
420 }
421
422
423
424
425
426
427
428 public FValuePixel[] getBestResponses(int numResponses) {
429 Comparator<FValuePixel> comparator = mode.scoresAscending() ? FValuePixel.ReverseValueComparator.INSTANCE : FValuePixel.ValueComparator.INSTANCE;
430
431 return TemplateMatcher.getBestResponses(numResponses, responseMap, getXOffset(), getYOffset(), comparator);
432 }
433
434
435
436
437
438
439 public int getXOffset() {
440 final int halfWidth = templateWidth / 2;
441
442 if (this.searchBounds == null)
443 return halfWidth;
444 else
445 return (int) Math.max(searchBounds.x - halfWidth, halfWidth);
446 }
447
448
449
450
451
452
453 public int getYOffset() {
454 final int halfHeight = templateHeight / 2;
455
456 if (this.searchBounds == null)
457 return halfHeight;
458 else
459 return (int) Math.max(searchBounds.y - halfHeight, halfHeight);
460 }
461
462
463
464
465 public FImage getResponseMap() {
466 return responseMap;
467 }
468 }