001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image.analysis.algorithm; 031 032import java.io.File; 033import java.io.IOException; 034import java.util.Comparator; 035 036import org.openimaj.image.DisplayUtilities; 037import org.openimaj.image.FImage; 038import org.openimaj.image.ImageUtilities; 039import org.openimaj.image.MBFImage; 040import org.openimaj.image.analyser.ImageAnalyser; 041import org.openimaj.image.colour.RGBColour; 042import org.openimaj.image.pixel.FValuePixel; 043import org.openimaj.image.processing.algorithm.MeanCenter; 044import org.openimaj.math.geometry.shape.Rectangle; 045import org.openimaj.util.queue.BoundedPriorityQueue; 046 047/** 048 * Basic template matching for {@link FImage}s. Template matching is 049 * performed in the spatial domain. 050 * 051 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 052 */ 053public class TemplateMatcher implements ImageAnalyser<FImage> { 054 /** 055 * Different algorithms for comparing templates to images. 056 * 057 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 058 */ 059 public enum Mode { 060 /** 061 * Compute the score at a point as the sum-squared difference between the image 062 * and the template with the top-left at the given point. The {@link TemplateMatcher} 063 * will account for the offset to the centre of the template internally. 064 * 065 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 066 */ 067 SUM_SQUARED_DIFFERENCE { 068 @Override 069 protected float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) { 070 final float[][] imageData = image.pixels; 071 final float[][] templateData = template.pixels; 072 073 return computeMatchScore(imageData, x, y, templateData, 0, 0, template.width, template.height); 074 } 075 076 @Override 077 public final float computeMatchScore(final float[][] img, int x, int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) { 078 final int stopX1 = templateWidth + x; 079 final int stopY1 = templateHeight + y; 080 final int stopX2 = templateWidth + templateX; 081 final int stopY2 = templateHeight + templateY; 082 083 float score = 0; 084 for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) { 085 for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) { 086 float diff = (img[yy1][xx1] - template[yy2][xx2]); 087 088 score += diff*diff; 089 } 090 } 091 return score; 092 } 093 094 095 @Override 096 public boolean scoresAscending() { 097 return false; //smaller scores are better 098 } 099 }, 100 /** 101 * Compute the normalised score at a point as the sum-squared difference between the image 102 * and the template with the top-left at the given point. The {@link TemplateMatcher} 103 * will account for the offset to the centre of the template internally. 104 * 105 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 106 */ 107 NORM_SUM_SQUARED_DIFFERENCE { 108 @Override 109 protected float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) { 110 float score = 0; 111 float si = 0; 112 final float st = (Float)workingSpace; 113 114 final float[][] imageData = image.pixels; 115 final float[][] templateData = template.pixels; 116 117 final int stopX = template.width + x; 118 final int stopY = template.height + y; 119 120 for (int yy=y, j=0; yy<stopY; yy++, j++) { 121 for (int xx=x, i=0; xx<stopX; xx++, i++) { 122 float diff = (imageData[yy][xx] - templateData[j][i]); 123 124 score += diff*diff; 125 si += (imageData[yy][xx] * imageData[yy][xx]); 126 } 127 } 128 129 return (float) (score / Math.sqrt(si*st)); 130 } 131 132 @Override 133 public final float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) { 134 final int stopX1 = templateWidth + x; 135 final int stopY1 = templateHeight + y; 136 final int stopX2 = templateWidth + templateX; 137 final int stopY2 = templateHeight + templateY; 138 139 float s1 = 0; 140 float s2 = 0; 141 float score = 0; 142 143 for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) { 144 for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) { 145 float diff = (img[yy1][xx1] - template[yy2][xx2]); 146 score += diff*diff; 147 s1 += (img[yy1][xx1] * img[yy1][xx1]); 148 s2 += (template[yy2][xx2] * template[yy2][xx2]); 149 } 150 } 151 152 return (float) (score / Math.sqrt(s1*s2)); 153 } 154 155 156 @Override 157 public boolean scoresAscending() { 158 return false; //smaller scores are better 159 } 160 161 @Override 162 public Float prepareWorkingSpace(FImage template) { 163 float sumsq = 0; 164 165 for (int y=0; y<template.height; y++) 166 for (int x=0; x<template.width; x++) 167 sumsq += template.pixels[y][x]*template.pixels[y][x]; 168 169 return new Float(sumsq); 170 } 171 }, 172 /** 173 * Compute the score at a point as the summed product between the image 174 * and the template with the top-left at the point given. The {@link TemplateMatcher} 175 * will account for the offset to the centre of the template internally. 176 * 177 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 178 */ 179 CORRELATION { 180 @Override 181 protected float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) { 182 final float[][] imageData = image.pixels; 183 final float[][] templateData = template.pixels; 184 185 return computeMatchScore(imageData, x, y, templateData, 0, 0, template.width, template.height); 186 } 187 188 @Override 189 public float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) { 190 float score = 0; 191 192 final int stopX1 = templateWidth + x; 193 final int stopY1 = templateHeight + y; 194 final int stopX2 = templateWidth + templateX; 195 final int stopY2 = templateHeight + templateY; 196 197 for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) { 198 for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) { 199 float prod = (img[yy1][xx1] * template[yy2][xx2]); 200 201 score += prod; 202 } 203 } 204 205 return score; 206 } 207 208 @Override 209 public boolean scoresAscending() { 210 return true; //bigger scores are better 211 } 212 }, 213 /** 214 * Compute the normalised score at a point as the summed product between the image 215 * and the template with the top-left at the point given. The {@link TemplateMatcher} 216 * will account for the offset to the centre of the template internally. 217 * 218 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 219 */ 220 NORM_CORRELATION { 221 @Override 222 protected float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) { 223 float score = 0; 224 float si = 0; 225 final float st = (Float)workingSpace; 226 227 final float[][] imageData = image.pixels; 228 final float[][] templateData = template.pixels; 229 230 final int stopX = template.width + x; 231 final int stopY = template.height + y; 232 233 for (int yy=y, j=0; yy<stopY; yy++, j++) { 234 for (int xx=x, i=0; xx<stopX; xx++, i++) { 235 float prod = (imageData[yy][xx] * templateData[j][i]); 236 score += prod; 237 si += (imageData[yy][xx] * imageData[yy][xx]); 238 } 239 } 240 241 return (float) (score / Math.sqrt(si*st)); 242 } 243 244 @Override 245 public float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) { 246 float score = 0; 247 float s1 = 0; 248 float s2 = 0; 249 250 final int stopX1 = templateWidth + x; 251 final int stopY1 = templateHeight + y; 252 final int stopX2 = templateWidth + templateX; 253 final int stopY2 = templateHeight + templateY; 254 255 int xx1,xx2,yy1,yy2; 256 for (yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) { 257 for (xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) { 258 float prod = (img[yy1][xx1] * template[yy2][xx2]); 259 260 s1 += (img[yy1][xx1] * img[yy1][xx1]); 261 s2 += (template[yy2][xx2] * template[yy2][xx2]); 262 263 score += prod; 264 } 265 } 266 267 return (float) (score / Math.sqrt(s1*s2)); 268 } 269 270 @Override 271 public boolean scoresAscending() { 272 return true; //bigger scores are better 273 } 274 275 @Override 276 public Float prepareWorkingSpace(FImage template) { 277 float sumsq = 0; 278 279 for (int y=0; y<template.height; y++) 280 for (int x=0; x<template.width; x++) 281 sumsq += template.pixels[y][x]*template.pixels[y][x]; 282 283 return new Float(sumsq); 284 } 285 }, 286 /** 287 * Compute the score at a point as the summed product between the mean-centered image patch 288 * and the mean-centered template with the top-left at the point given. The {@link TemplateMatcher} 289 * will account for the offset to the centre of the template internally. 290 * 291 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 292 */ 293 CORRELATION_COEFFICIENT { 294 @Override 295 protected final float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) { 296 final float[][] imageData = image.pixels; 297 final float[][] templateData = template.pixels; 298 299 final float templateMean = (Float)workingSpace; 300 final float imgMean = MeanCenter.patchMean(imageData); 301 302 return computeMatchScore(imageData, x, y, imgMean, templateData, 0, 0, template.width, template.height, templateMean); 303 } 304 305 @Override 306 public final float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) { 307 float imgMean = MeanCenter.patchMean(img, x, y, templateWidth, templateHeight); 308 float templateMean = MeanCenter.patchMean(template, templateX, templateY, templateWidth, templateHeight); 309 310 return computeMatchScore(img, x, y, imgMean, template, templateX, templateY, templateWidth, templateHeight, templateMean); 311 } 312 313 private final float computeMatchScore(final float[][] img, final int x, final int y, final float imgMean, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight, final float templateMean) { 314 final int stopX1 = templateWidth + x; 315 final int stopY1 = templateHeight + y; 316 final int stopX2 = templateWidth + templateX; 317 final int stopY2 = templateHeight + templateY; 318 319 float score = 0; 320 for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) { 321 for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) { 322 float prod = ((img[yy1][xx1] - imgMean) * (template[yy2][xx2] - templateMean)); 323 score += prod; 324 } 325 } 326 327 return score; 328 } 329 330 @Override 331 public boolean scoresAscending() { 332 return true; //bigger scores are better 333 } 334 335 @Override 336 public Float prepareWorkingSpace(FImage template) { 337 return MeanCenter.patchMean(template.pixels); 338 } 339 }, 340 /** 341 * Compute the normalised score at a point as the summed product between the mean-centered image patch 342 * and the mean-centered template with the top-left at the point given. The {@link TemplateMatcher} 343 * will account for the offset to the centre of the template internally. 344 * 345 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 346 */ 347 NORM_CORRELATION_COEFFICIENT { 348 @Override 349 protected final float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) { 350 final int width = template.width; 351 final int height = template.height; 352 353 float imgMean = MeanCenter.patchMean(image.pixels, x, y, width, height); 354 355 float score = 0; 356 float si = 0; 357 final float st = (Float)workingSpace; 358 359 final float[][] imageData = image.pixels; 360 final float[][] templateData = template.pixels; 361 362 for (int j=0; j<height; j++) { 363 for (int i=0; i<width; i++) { 364 float ival = imageData[j+y][i+x] - imgMean; 365 366 float prod = (ival * templateData[j][i]); 367 368 score += prod; 369 370 si += (ival * ival); 371 } 372 } 373 374 double norm = Math.sqrt(si * st); 375 376 if (norm == 0) return 0; 377 378 return (float) (score / norm); 379 } 380 381 @Override 382 public final float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) { 383 float imgMean = MeanCenter.patchMean(img, x,y,templateWidth, templateHeight); 384 float templateMean = MeanCenter.patchMean(template, templateX, templateY, templateWidth, templateHeight); 385 386 final int stopX1 = templateWidth + x; 387 final int stopY1 = templateHeight + y; 388 final int stopX2 = templateWidth + templateX; 389 final int stopY2 = templateHeight + templateY; 390 391 float score = 0; 392 float s1 = 0; 393 float s2 = 0; 394 395 for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) { 396 for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) { 397 float ival = (img[yy1][xx1] - imgMean); 398 float tval = (template[yy2][xx2] - templateMean); 399 400 float prod = (ival * tval); 401 402 score += prod; 403 404 s1 += (ival * ival); 405 s2 += (tval * tval); 406 } 407 } 408 409 double norm = Math.sqrt(s1*s2); 410 411 if (norm == 0) return 0; 412 413 return (float) (score / norm); 414 } 415 416 @Override 417 public boolean scoresAscending() { 418 return true; //bigger scores are better 419 } 420 421 @Override 422 public FImage prepareTemplate(FImage template) { 423 return template.process(new MeanCenter()); 424 } 425 426 @Override 427 public Float prepareWorkingSpace(FImage template) { 428 float sumsq = 0; 429 430 for (int y=0; y<template.height; y++) 431 for (int x=0; x<template.width; x++) 432 sumsq += template.pixels[y][x]*template.pixels[y][x]; 433 434 return sumsq; 435 } 436 } 437 ; 438 439 /** 440 * Compute the matching score between the image and template, with the top-left of the 441 * template at (x, y) in the image. 442 * @param image The image. 443 * @param template The template. 444 * @param x The x-ordinate top-left of the template in the image 445 * @param y The y-ordinate top-left of the template in the image 446 * @param workingSpace The working space created by #prepareWorkingSpace() 447 * @return The match score. 448 */ 449 protected abstract float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace); 450 451 /** 452 * Compute the matching score between the image and template, with the top-left of the 453 * template at (x, y) in the image. The coordinates of the template can be specified, 454 * so it is possible for the actual template to be a sub-image of the given data. 455 * 456 * @param img the image data 457 * @param x the x-ordinate of the top-left of the template 458 * @param y the y-ordinate of the top-left of the template 459 * @param template the template data 460 * @param templateX the top-left x-ordinate of the template in the template data 461 * @param templateY the top-left y-ordinate of the template in the template data 462 * @param templateWidth the width of the template in the template data 463 * @param templateHeight the height of the template in the template data 464 * @return the match score. 465 */ 466 public abstract float computeMatchScore(final float[][] img, int x, int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight); 467 468 /** 469 * Are the scores ascending (i.e. bigger is better) or descending (smaller is better)? 470 * @return true is bigger scores are better; false if smaller scores are better. 471 */ 472 public abstract boolean scoresAscending(); 473 474 /** 475 * Prepare the template if necessary. The default implementation 476 * just returns the template, but subclasses can override. 477 * @param template the template image 478 * @return the processed template image 479 */ 480 protected FImage prepareTemplate(FImage template) { 481 return template; 482 } 483 484 /** 485 * Prepare an object to hold the working space required by the 486 * mode during score computation. Most modes don't require 487 * this, so the default implementation returns null, but 488 * it can be overridden. 489 * 490 * @param template the template 491 * @return an object representing the required working space for 492 * the {@link #computeMatchScore(FImage, FImage, int, int, Object)} method. 493 */ 494 protected Object prepareWorkingSpace(FImage template) { 495 return null; 496 } 497 } 498 499 private FImage template; 500 private Mode mode; 501 private Object workingSpace; 502 private Rectangle searchBounds; 503 private FImage responseMap; 504 505 /** 506 * Default constructor with the template to match and the mode 507 * with which to estimate template responses. When matching is 508 * performed by {@link #analyseImage(FImage)}, the whole image 509 * will be searched. 510 * 511 * @param template The template 512 * @param mode The mode. 513 */ 514 public TemplateMatcher(FImage template, Mode mode) { 515 this.mode = mode; 516 this.template = mode.prepareTemplate(template); 517 this.workingSpace = mode.prepareWorkingSpace(this.template); 518 } 519 520 /** 521 * Construct with the template to match, the mode with which to 522 * estimate template responses and the bounds rectangle in which 523 * to search. The search bounds rectangle is defined with respect 524 * to the centre of the template. 525 * 526 * @param template The template 527 * @param mode The mode. 528 * @param bounds The bounding box for search. 529 */ 530 public TemplateMatcher(FImage template, Mode mode, Rectangle bounds) { 531 this.searchBounds = bounds; 532 this.mode = mode; 533 this.template = mode.prepareTemplate(template); 534 this.workingSpace = mode.prepareWorkingSpace(this.template); 535 } 536 537 /** 538 * @return the search bound rectangle 539 */ 540 public Rectangle getSearchBounds() { 541 return searchBounds; 542 } 543 544 /** 545 * Set the search bounds rectangle. The search bounds rectangle 546 * is defined with respect to the centre of the template. 547 * Setting to <code>null</code> results in the entire image 548 * being searched. 549 * 550 * @param searchBounds the search bounds to set 551 */ 552 public void setSearchBounds(Rectangle searchBounds) { 553 this.searchBounds = searchBounds; 554 } 555 556 /** 557 * Perform template matching. If a bounds rectangle 558 * is has not been set or is null, then the whole 559 * image will be searched. Otherwise the area of the image 560 * which lies in the previously set search bounds will be 561 * searched. 562 * 563 * @see org.openimaj.image.analyser.ImageAnalyser#analyseImage(org.openimaj.image.Image) 564 */ 565 @Override 566 public void analyseImage(FImage image) { 567 Rectangle searchSpace = null; 568 569 if (this.searchBounds != null) { 570 final int halfWidth = template.width / 2; 571 final int halfHeight = template.height / 2; 572 573 float x = Math.max(searchBounds.x - halfWidth, 0); 574 x = Math.min(x, image.width-template.width); 575 float width = searchBounds.width; 576 if (searchBounds.x - halfWidth < 0) { 577 width += (searchBounds.x - halfWidth); 578 } 579 if (x + width > image.width - template.width) 580 width += (image.width - template.width) - (x+width); 581 582 float y = Math.max(searchBounds.y - halfHeight, 0); 583 y = Math.min(y, image.height - template.height); 584 float height = searchBounds.height; 585 if (searchBounds.y - halfHeight < 0) { 586 height += (searchBounds.y - halfHeight); 587 } 588 if (y + height > image.height - template.height) 589 height += (image.height - template.height) - (y+height); 590 591 searchSpace = new Rectangle( 592 x, 593 y, 594 width, 595 height 596 ); 597 598 } else { 599 searchSpace = new Rectangle( 600 0, 601 0, 602 image.width - template.width + 1, 603 image.height - template.height + 1 604 ); 605 } 606 607 final int scanX = (int) searchSpace.x; 608 final int scanY = (int) searchSpace.y; 609 final int scanWidth = (int)searchSpace.width; 610 final int scanHeight = (int)searchSpace.height; 611 612 responseMap = new FImage(scanWidth, scanHeight); 613 final float[][] responseMapData = responseMap.pixels; 614 615 for (int y=0; y<scanHeight; y++) { 616 for (int x=0; x<scanWidth; x++) { 617 responseMapData[y][x] = mode.computeMatchScore(image, template, x+scanX, y+scanY, workingSpace); 618 } 619 } 620 } 621 622 /** 623 * Get the top-N "best" responses found by the template matcher. 624 * 625 * @param numResponses The number of responses 626 * @return the best responses found 627 */ 628 public FValuePixel[] getBestResponses(int numResponses) { 629 Comparator<FValuePixel> comparator = mode.scoresAscending() ? FValuePixel.ReverseValueComparator.INSTANCE : FValuePixel.ValueComparator.INSTANCE; 630 631 return getBestResponses(numResponses, responseMap, getXOffset(), getYOffset(), comparator); 632 } 633 634 /** 635 * Get the top-N "best" responses found by the template matcher. 636 * 637 * @param numResponses The number of responses 638 * @param responseMap The response map 639 * @param offsetX the amount to shift pixels in the x direction 640 * @param offsetY the amount to shift pixels in the y direction 641 * @param comparator the comparator for determining the "best" responses 642 * @return the best responses found 643 */ 644 public static FValuePixel[] getBestResponses(int numResponses, FImage responseMap, int offsetX, int offsetY, Comparator<FValuePixel> comparator) { 645 BoundedPriorityQueue<FValuePixel> bestResponses = new BoundedPriorityQueue<FValuePixel>(numResponses, comparator); 646 647 final float[][] responseMapData = responseMap.pixels; 648 649 final int scanWidth = responseMap.width; 650 final int scanHeight = responseMap.height; 651 652 FValuePixel tmpPixel = new FValuePixel(0, 0, 0); 653 for (int y=0; y<scanHeight; y++) { 654 for (int x=0; x<scanWidth; x++) { 655 tmpPixel.x = x + offsetX; //account for offset to centre 656 tmpPixel.y = y + offsetY; //account for offset to centre 657 tmpPixel.value = responseMapData[y][x]; 658 659 FValuePixel removed = bestResponses.offerItem(tmpPixel); 660 661 if (removed == null) 662 tmpPixel = new FValuePixel(0, 0, 0); 663 else 664 tmpPixel = removed; 665 } 666 } 667 668 return bestResponses.toOrderedArray(new FValuePixel[numResponses]); 669 } 670 671 /** 672 * @return The x-offset of the top-left of the response map 673 * returned by {@link #getResponseMap()} to the original image 674 * analysed by {@link #analyseImage(FImage)}. 675 */ 676 public int getXOffset() { 677 final int halfWidth = template.width / 2; 678 679 if (this.searchBounds == null) 680 return halfWidth; 681 else 682 return (int) Math.max(searchBounds.x , halfWidth); 683 } 684 685 /** 686 * @return The y-offset of the top-left of the response map 687 * returned by {@link #getResponseMap()} to the original image 688 * analysed by {@link #analyseImage(FImage)}. 689 */ 690 public int getYOffset() { 691 final int halfHeight = template.height / 2; 692 693 if (this.searchBounds == null) 694 return halfHeight; 695 else 696 return (int) Math.max(searchBounds.y , halfHeight); 697 } 698 699 /** 700 * @return The responseMap generated from the last call to {@link #analyseImage(FImage)} 701 */ 702 public FImage getResponseMap() { 703 return responseMap; 704 } 705 706 /** 707 * @return the template held by the matcher; this might be different 708 * to the image used in construction as it might have been pre-processed. 709 */ 710 public FImage getTemplate() { 711 return template; 712 } 713 714 /** 715 * Testing 716 * @param args 717 * @throws IOException 718 */ 719 public static void main(String[] args) throws IOException { 720 FImage image = ImageUtilities.readF(new File("/Users/jsh2/Desktop/image.png")); 721 FImage template = image.extractROI(100, 100, 100, 100); 722 image.fill(0f); 723 image.drawImage(template, 100, 100); 724 725 TemplateMatcher matcher = new TemplateMatcher(template, Mode.CORRELATION); 726 matcher.setSearchBounds(new Rectangle(100,100,200,200)); 727 image.analyseWith(matcher); 728 DisplayUtilities.display(matcher.responseMap.normalise()); 729 730 MBFImage cimg = image.toRGB(); 731 for (FValuePixel p : matcher.getBestResponses(10)) { 732 System.out.println(p); 733 cimg.drawPoint(p, RGBColour.RED, 1); 734 } 735 736 cimg.drawShape(matcher.getSearchBounds(), RGBColour.BLUE); 737 cimg.drawShape(new Rectangle(100,100,100,100), RGBColour.GREEN); 738 739 DisplayUtilities.display(cimg); 740 } 741}