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.feature.local.interest; 031 032import java.io.IOException; 033import java.util.ArrayList; 034import java.util.List; 035 036import org.apache.log4j.BasicConfigurator; 037import org.apache.log4j.Level; 038import org.apache.log4j.Logger; 039import org.openimaj.image.DisplayUtilities; 040import org.openimaj.image.FImage; 041import org.openimaj.image.ImageUtilities; 042import org.openimaj.image.MBFImage; 043import org.openimaj.image.colour.RGBColour; 044import org.openimaj.image.pixel.FValuePixel; 045import org.openimaj.image.pixel.Pixel; 046import org.openimaj.image.processing.convolution.FConvolution; 047import org.openimaj.image.processing.convolution.FGaussianConvolve; 048import org.openimaj.image.processing.resize.ResizeProcessor; 049import org.openimaj.image.processing.transform.FProjectionProcessor; 050import org.openimaj.math.geometry.point.Point2dImpl; 051import org.openimaj.math.geometry.shape.Ellipse; 052import org.openimaj.math.geometry.shape.EllipseUtilities; 053import org.openimaj.math.geometry.shape.Rectangle; 054import org.openimaj.math.matrix.EigenValueVectorPair; 055import org.openimaj.math.matrix.MatrixUtils; 056 057import Jama.Matrix; 058 059/** 060 * Using an underlying feature detector, adapt the ellipse detected to result in 061 * a more stable region according to work by 062 * http://www.robots.ox.ac.uk/~vgg/research/affine/ 063 * 064 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 065 * 066 */ 067public class AffineAdaption implements MultiscaleInterestPointDetector<EllipticInterestPointData> { 068 private static final FImage LAPLACIAN_KERNEL = new FImage(new float[][] { { 2, 0, 2 }, { 0, -8, 0 }, { 2, 0, 2 } }); 069 private static final FConvolution LAPLACIAN_KERNEL_CONV = new FConvolution(LAPLACIAN_KERNEL); 070 // private static final FImage DX_KERNEL = new FImage(new float[][] {{-1, 0, 071 // 1}}); 072 // private static final FImage DY_KERNEL = new FImage(new float[][] {{-1}, 073 // {0}, {1}}); 074 075 static Logger logger = Logger.getLogger(AffineAdaption.class); 076 static { 077 078 BasicConfigurator.configure(); 079 logger.setLevel(Level.INFO); 080 } 081 082 private AbstractStructureTensorIPD internal; 083 private AbstractStructureTensorIPD initial; 084 private List<EllipticInterestPointData> points; 085 086 private IPDSelectionMode initialMode; 087 088 private boolean fastDifferentiationScale = false; 089 090 /** 091 * instatiate with a {@link HarrisIPD} detector with scales 2.0f and 2.0f * 092 * 1.4f. Selection 100 points 093 */ 094 public AffineAdaption() { 095 this(new HarrisIPD(2.0f, 2.0f * 1.4f), new IPDSelectionMode.Count(100)); 096 } 097 098 /** 099 * specify the internal detector and the selection mode 100 * 101 * @param internal 102 * @param initialSelectionMode 103 */ 104 public AffineAdaption(AbstractStructureTensorIPD internal, IPDSelectionMode initialSelectionMode) { 105 this(internal, internal.clone(), initialSelectionMode); 106 } 107 108 /** 109 * set both the internal and intitial feature detectors (possibly different 110 * settings of the same detector) and a selection mode 111 * 112 * @param internal 113 * @param initial 114 * @param initialSelectionMode 115 */ 116 public AffineAdaption(AbstractStructureTensorIPD internal, AbstractStructureTensorIPD initial, 117 IPDSelectionMode initialSelectionMode) 118 { 119 this.internal = internal; 120 this.initial = initial; 121 122 this.initialMode = initialSelectionMode; 123 this.points = new ArrayList<EllipticInterestPointData>(); 124 } 125 126 @Override 127 public void findInterestPoints(FImage image) { 128 findInterestPoints(image, image.getBounds()); 129 } 130 131 @Override 132 public void findInterestPoints(FImage image, Rectangle window) { 133 this.points = new ArrayList<EllipticInterestPointData>(); 134 initial.findInterestPoints(image, window); 135 // if(logger.getLevel() == Level.INFO) 136 // initial.printStructureTensorStats(); 137 final List<InterestPointData> a = initialMode.selectPoints(initial); 138 139 logger.info("Found " + a.size() + " features at sd/si: " + initial.detectionScale + "/" 140 + initial.integrationScale); 141 142 for (final InterestPointData d : a) { 143 final EllipticInterestPointData kpt = new EllipticInterestPointData(); 144 // InterestPointData d = new InterestPointData(); 145 // d.x = 102; 146 // d.y = 396; 147 kpt.scale = initial.getIntegrationScale(); 148 kpt.x = d.x; 149 kpt.y = d.y; 150 151 final boolean converge = calcAffineAdaptation(image, kpt, internal.clone()); 152 if (converge) 153 { 154 logger.debug("Keypoint at: " + d.x + ", " + d.y); 155 logger.debug("... converged: " + converge); 156 this.points.add(kpt); 157 } 158 159 } 160 } 161 162 @Override 163 public List<EllipticInterestPointData> getInterestPoints(int npoints) { 164 if (points == null) 165 return null; 166 if (npoints < 0) 167 npoints = this.points.size(); 168 return this.points.subList(0, npoints < this.points.size() ? npoints : this.points.size()); 169 } 170 171 @Override 172 public List<EllipticInterestPointData> getInterestPoints(float threshold) { 173 final List<EllipticInterestPointData> validPoints = new ArrayList<EllipticInterestPointData>(); 174 for (final EllipticInterestPointData point : this.points) { 175 if (point.score > threshold) { 176 validPoints.add(point); 177 } 178 } 179 return validPoints; 180 } 181 182 @Override 183 public List<EllipticInterestPointData> getInterestPoints() { 184 return this.points; 185 } 186 187 @Override 188 public void setDetectionScale(float detectionScaleVariance) { 189 this.initial.setDetectionScale(detectionScaleVariance); 190 } 191 192 public void setIntegrationScale(float integrationScaleVariance) { 193 this.initial.setIntegrationScale(integrationScaleVariance); 194 } 195 196 /* 197 * Calculates second moments matrix in point p 198 */ 199 // Matrix calcSecondMomentMatrix(final FImage dx2, final FImage dxy, final 200 // FImage dy2, Pixel p) { 201 // int x = p.x; 202 // int y = p.y; 203 // 204 // Matrix M = new Matrix(2, 2); 205 // M.set(0, 0, dx2.pixels[y][x]); 206 // M.set(0, 1, dxy.pixels[y][x]); 207 // M.set(1, 0, dxy.pixels[y][x]); 208 // M.set(1, 1, dy2.pixels[y][x]); 209 // 210 // return M; 211 // } 212 Matrix calcSecondMomentMatrix(AbstractStructureTensorIPD ipd, int x, int y) { 213 214 return ipd.getSecondMomentsAt(x, y); 215 } 216 217 /* 218 * Performs affine adaptation 219 */ 220 boolean calcAffineAdaptation(final FImage fimage, EllipticInterestPointData kpt, AbstractStructureTensorIPD ipd) { 221 // DisplayUtilities.createNamedWindow("warp", "Warped Image ROI",true); 222 final Matrix transf = new Matrix(2, 3); // Transformation matrix 223 Point2dImpl c = new Point2dImpl(); // Transformed point 224 final Point2dImpl p = new Point2dImpl(); // Image point 225 226 Matrix U = Matrix.identity(2, 2); // Normalization matrix 227 228 final Matrix Mk = U.copy(); 229 FImage img_roi, warpedImg = new FImage(1, 1); 230 float Qinv = 1, q, si = kpt.scale; // sd = 0.75f * si; 231 final float kptSize = 2 * 3 * kpt.scale; 232 boolean divergence = false, convergence = false; 233 int i = 0; 234 235 // Coordinates in image 236 int py = (int) kpt.y; 237 int px = (int) kpt.x; 238 239 // Roi coordinates 240 int roix, roiy; 241 242 // Coordinates in U-trasformation 243 int cx = px; 244 int cy = py; 245 int cxPr = cx; 246 int cyPr = cy; 247 248 float radius = kptSize / 2 * 1.4f; 249 float half_width, half_height; 250 251 Rectangle roi; 252 253 // Affine adaptation 254 while (i <= 10 && !divergence && !convergence) 255 { 256 // Transformation matrix 257 MatrixUtils.zero(transf); 258 transf.setMatrix(0, 1, 0, 1, U); 259 260 kpt.setTransform(U); 261 262 final Rectangle boundingBox = new Rectangle(); 263 264 final double ac_b2 = U.det(); 265 boundingBox.width = (float) Math.ceil(U.get(1, 1) / ac_b2 * 3 * si * 1.4); 266 boundingBox.height = (float) Math.ceil(U.get(0, 0) / ac_b2 * 3 * si * 1.4); 267 268 // Create window around interest point 269 half_width = Math.min(Math.min(fimage.width - px - 1, px), boundingBox.width); 270 half_height = Math.min(Math.min(fimage.height - py - 1, py), boundingBox.height); 271 272 if (half_width <= 0 || half_height <= 0) 273 return divergence; 274 275 roix = Math.max(px - (int) boundingBox.width, 0); 276 roiy = Math.max(py - (int) boundingBox.height, 0); 277 roi = new Rectangle(roix, roiy, px - roix + half_width + 1, py - roiy + half_height + 1); 278 279 // create ROI 280 img_roi = fimage.extractROI(roi); 281 282 // Point within the ROI 283 p.x = px - roix; 284 p.y = py - roiy; 285 286 // Find coordinates of square's angles to find size of warped 287 // ellipse's bounding box 288 final float u00 = (float) U.get(0, 0); 289 final float u01 = (float) U.get(0, 1); 290 final float u10 = (float) U.get(1, 0); 291 final float u11 = (float) U.get(1, 1); 292 293 final float minx = u01 * img_roi.height < 0 ? u01 * img_roi.height : 0; 294 final float miny = u10 * img_roi.width < 0 ? u10 * img_roi.width : 0; 295 final float maxx = (u00 * img_roi.width > u00 * img_roi.width + u01 * img_roi.height ? u00 296 * img_roi.width : u00 * img_roi.width + u01 * img_roi.height) - minx; 297 final float maxy = (u11 * img_roi.width > u10 * img_roi.width + u11 * img_roi.height ? u11 298 * img_roi.height : u10 * img_roi.width + u11 * img_roi.height) - miny; 299 300 // Shift 301 transf.set(0, 2, -minx); 302 transf.set(1, 2, -miny); 303 304 if (maxx >= 2 * radius + 1 && maxy >= 2 * radius + 1) 305 { 306 // Size of normalized window must be 2*radius 307 // Transformation 308 FImage warpedImgRoi; 309 final FProjectionProcessor proc = new FProjectionProcessor(); 310 proc.setMatrix(transf); 311 img_roi.accumulateWith(proc); 312 warpedImgRoi = proc.performProjection(0, (int) maxx, 0, (int) maxy, null); 313 314 // DisplayUtilities.displayName(warpedImgRoi.clone().normalise(), 315 // "warp"); 316 317 // Point in U-Normalized coordinates 318 c = p.transform(U); 319 cx = (int) (c.x - minx); 320 cy = (int) (c.y - miny); 321 322 if (warpedImgRoi.height > 2 * radius + 1 && warpedImgRoi.width > 2 * radius + 1) 323 { 324 // Cut around normalized patch 325 roix = (int) Math.max(cx - Math.ceil(radius), 0.0); 326 roiy = (int) Math.max(cy - Math.ceil(radius), 0.0); 327 roi = new Rectangle(roix, roiy, 328 cx - roix + (float) Math.min(Math.ceil(radius), warpedImgRoi.width - cx - 1) + 1, 329 cy - roiy + (float) Math.min(Math.ceil(radius), warpedImgRoi.height - cy - 1) + 1); 330 warpedImg = warpedImgRoi.extractROI(roi); 331 332 // Coordinates in cutted ROI 333 cx = cx - roix; 334 cy = cy - roiy; 335 } else { 336 warpedImg.internalAssign(warpedImgRoi); 337 } 338 339 if (logger.getLevel() == Level.DEBUG) { 340 displayCurrentPatch(img_roi.clone().normalise(), p.x, p.y, warpedImg.clone().normalise(), cx, cy, U, 341 si * 3); 342 } 343 344 // Integration Scale selection 345 si = selIntegrationScale(warpedImg, si, new Pixel(cx, cy)); 346 347 // Differentation scale selection 348 if (fastDifferentiationScale) { 349 ipd = selDifferentiationScaleFast(warpedImg, ipd, si, new Pixel(cx, cy)); 350 } 351 else { 352 ipd = selDifferentiationScale(warpedImg, ipd, si, new Pixel(cx, cy)); 353 } 354 355 if (ipd.maxima.size() == 0) { 356 divergence = true; 357 continue; 358 } 359 // Spatial Localization 360 cxPr = cx; // Previous iteration point in normalized window 361 cyPr = cy; 362 // 363 // float cornMax = 0; 364 // for (int j = 0; j < 3; j++) 365 // { 366 // for (int t = 0; t < 3; t++) 367 // { 368 // float dx2 = Lxm2smooth.pixels[cyPr - 1 + j][cxPr - 1 + t]; 369 // float dy2 = Lym2smooth.pixels[cyPr - 1 + j][cxPr - 1 + t]; 370 // float dxy = Lxmysmooth.pixels[cyPr - 1 + j][cxPr - 1 + t]; 371 // float det = dx2 * dy2 - dxy * dxy; 372 // float tr = dx2 + dy2; 373 // float cornerness = (float) (det - (0.04 * Math.pow(tr, 2))); 374 // 375 // if (cornerness > cornMax) { 376 // cornMax = cornerness; 377 // cx = cxPr - 1 + t; 378 // cy = cyPr - 1 + j; 379 // } 380 // } 381 // } 382 383 final FValuePixel max = ipd.findMaximum(new Rectangle(cxPr - 1, cyPr - 1, 3, 3)); 384 cx = max.x; 385 cy = max.y; 386 387 // Transform point in image coordinates 388 p.x = px; 389 p.y = py; 390 391 // Displacement vector 392 c.x = cx - cxPr; 393 c.y = cy - cyPr; 394 395 // New interest point location in image 396 p.translate(c.transform(U.inverse())); 397 px = (int) p.x; 398 py = (int) p.y; 399 400 q = calcSecondMomentSqrt(ipd, new Pixel(cx, cy), Mk); 401 402 final float ratio = 1 - q; 403 404 // if ratio == 1 means q == 0 and one axes equals to 0 405 if (!Float.isNaN(ratio) && ratio != 1) 406 { 407 // Update U matrix 408 U = U.times(Mk); 409 410 Matrix uVal, uV; 411 // EigenvalueDecomposition ueig = U.eig(); 412 final EigenValueVectorPair ueig = MatrixUtils.symmetricEig2x2(U); 413 uVal = ueig.getValues(); 414 uV = ueig.getVectors(); 415 416 Qinv = normMaxEval(U, uVal, uV); 417 418 // Keypoint doesn't converge 419 if (Qinv >= 6) { 420 logger.debug("QInverse too large, feature too edge like, affine divergence!"); 421 divergence = true; 422 } else if (ratio <= 0.05) { // Keypoint converges 423 convergence = true; 424 425 // Set transformation matrix 426 MatrixUtils.zero(transf); 427 transf.setMatrix(0, 1, 0, 1, U); 428 // The order here matters, setTransform uses the x and y 429 // to calculate a new ellipse 430 kpt.x = px; 431 kpt.y = py; 432 kpt.scale = si; 433 kpt.setTransform(U); 434 kpt.score = max.value; 435 436 // ax1 = (float) (1 / Math.abs(uVal.get(1, 1)) * 3 * 437 // si); 438 // ax2 = (float) (1 / Math.abs(uVal.get(0, 0)) * 3 * 439 // si); 440 // phi = Math.atan(uV.get(1, 1) / uV.get(0, 1)); 441 // kpt.axes = new Point2dImpl(ax1, ax2); 442 // kpt.phi = phi; 443 // kpt.centre = new Pixel(px, py); 444 // kpt.si = si; 445 // kpt.size = 2 * 3 * si; 446 447 } else { 448 radius = (float) (3 * si * 1.4); 449 } 450 } else { 451 logger.debug("QRatio was close to 0, affine divergence!"); 452 divergence = true; 453 } 454 } else { 455 logger.debug("Window size has grown too fast, scale divergence!"); 456 divergence = true; 457 } 458 459 ++i; 460 } 461 if (!divergence && !convergence) { 462 logger.debug("Reached max iterations!"); 463 } 464 return convergence; 465 } 466 467 private void displayCurrentPatch(FImage unwarped, float unwarpedx, float unwarpedy, FImage warped, int warpedx, 468 int warpedy, Matrix transform, float scale) 469 { 470 DisplayUtilities.createNamedWindow("warpunwarp", "Warped and Unwarped Image", true); 471 logger.debug("Displaying patch"); 472 final float resizeScale = 5f; 473 final float warppedPatchScale = resizeScale; 474 final ResizeProcessor patchSizer = new ResizeProcessor(resizeScale); 475 final FImage warppedPatchGrey = warped.process(patchSizer); 476 MBFImage warppedPatch = new MBFImage(warppedPatchGrey.clone(), warppedPatchGrey.clone(), warppedPatchGrey.clone()); 477 float x = warpedx * warppedPatchScale; 478 float y = warpedy * warppedPatchScale; 479 final float r = scale * warppedPatchScale; 480 481 warppedPatch.createRenderer().drawShape(new Ellipse(x, y, r, r, 0), RGBColour.RED); 482 warppedPatch.createRenderer().drawPoint(new Point2dImpl(x, y), RGBColour.RED, 3); 483 484 final FImage unwarppedPatchGrey = unwarped.clone(); 485 MBFImage unwarppedPatch = new MBFImage(unwarppedPatchGrey.clone(), unwarppedPatchGrey.clone(), 486 unwarppedPatchGrey.clone()); 487 unwarppedPatch = unwarppedPatch.process(patchSizer); 488 final float unwarppedPatchScale = resizeScale; 489 490 x = unwarpedx * unwarppedPatchScale; 491 y = unwarpedy * unwarppedPatchScale; 492 // Matrix sm = state.selected.secondMoments; 493 // float scale = state.selected.scale * unwarppedPatchScale; 494 // Ellipse e = EllipseUtilities.ellipseFromSecondMoments(x, y, sm, 495 // scale*2); 496 final Ellipse e = EllipseUtilities.fromTransformMatrix2x2(transform, x, y, scale * unwarppedPatchScale); 497 498 unwarppedPatch.createRenderer().drawShape(e, RGBColour.BLUE); 499 unwarppedPatch.createRenderer().drawPoint(new Point2dImpl(x, y), RGBColour.RED, 3); 500 // give the patch a border (10px, black) 501 warppedPatch = warppedPatch.padding(5, 5, RGBColour.BLACK); 502 unwarppedPatch = unwarppedPatch.padding(5, 5, RGBColour.BLACK); 503 504 final MBFImage displayArea = warppedPatch.newInstance(warppedPatch.getWidth() * 2, warppedPatch.getHeight()); 505 displayArea.createRenderer().drawImage(warppedPatch, 0, 0); 506 displayArea.createRenderer().drawImage(unwarppedPatch, warppedPatch.getWidth(), 0); 507 DisplayUtilities.displayName(displayArea, "warpunwarp"); 508 logger.debug("Done"); 509 } 510 511 /* 512 * Selects the integration scale that maximize LoG in point c 513 */ 514 float selIntegrationScale(final FImage image, float si, Pixel c) { 515 FImage L; 516 final int cx = c.x; 517 final int cy = c.y; 518 float maxLap = -Float.MAX_VALUE; 519 float maxsx = si; 520 float sigma, sigma_prev = 0; 521 522 L = image.clone(); 523 /* 524 * Search best integration scale between previous and successive layer 525 */ 526 for (float u = 0.7f; u <= 1.41; u += 0.1) 527 { 528 final float sik = u * si; 529 sigma = (float) Math.sqrt(Math.pow(sik, 2) - Math.pow(sigma_prev, 2)); 530 531 L.processInplace(new FGaussianConvolve(sigma, 3)); 532 533 sigma_prev = sik; 534 // Lap = L.process(LAPLACIAN_KERNEL_CONV); 535 536 final float lapVal = sik * sik * Math.abs(LAPLACIAN_KERNEL_CONV.responseAt(cx, cy, L)); 537 // float lapVal = sik * sik * Math.abs(Lap.pixels[cy][cx]); 538 539 if (lapVal >= maxLap) 540 { 541 maxLap = lapVal; 542 maxsx = sik; 543 } 544 } 545 return maxsx; 546 } 547 548 /* 549 * Calculates second moments matrix square root 550 */ 551 float calcSecondMomentSqrt(AbstractStructureTensorIPD ipd, Pixel p, Matrix Mk) 552 { 553 Matrix M, V, eigVal, Vinv; 554 555 M = calcSecondMomentMatrix(ipd, p.x, p.y); 556 557 /* * 558 * M = V * D * V.inv() V has eigenvectors as columns D is a diagonal 559 * Matrix with eigenvalues as elements V.inv() is the inverse of V 560 */ 561 // EigenvalueDecomposition meig = M.eig(); 562 final EigenValueVectorPair meig = MatrixUtils.symmetricEig2x2(M); 563 eigVal = meig.getValues(); 564 V = meig.getVectors(); 565 566 // V = V.transpose(); 567 Vinv = V.inverse(); 568 569 final double eval1 = Math.sqrt(eigVal.get(0, 0)); 570 eigVal.set(0, 0, eval1); 571 final double eval2 = Math.sqrt(eigVal.get(1, 1)); 572 eigVal.set(1, 1, eval2); 573 574 // square root of M 575 Mk.setMatrix(0, 1, 0, 1, V.times(eigVal).times(Vinv)); 576 577 // return q isotropic measure 578 return (float) (Math.min(eval1, eval2) / Math.max(eval1, eval2)); 579 } 580 581 float normMaxEval(Matrix U, Matrix uVal, Matrix uVec) { 582 /* * 583 * Decomposition: U = V * D * V.inv() V has eigenvectors as columns D is 584 * a diagonal Matrix with eigenvalues as elements V.inv() is the inverse 585 * of V 586 */ 587 // uVec = uVec.transpose(); 588 final Matrix uVinv = uVec.inverse(); 589 590 // Normalize min eigenvalue to 1 to expand patch in the direction of min 591 // eigenvalue of U.inv() 592 final double uval1 = uVal.get(0, 0); 593 final double uval2 = uVal.get(1, 1); 594 595 if (Math.abs(uval1) < Math.abs(uval2)) 596 { 597 uVal.set(0, 0, 1); 598 uVal.set(1, 1, uval2 / uval1); 599 600 } else 601 { 602 uVal.set(1, 1, 1); 603 uVal.set(0, 0, uval1 / uval2); 604 } 605 606 // U normalized 607 U.setMatrix(0, 1, 0, 1, uVec.times(uVal).times(uVinv)); 608 609 return (float) (Math.max(Math.abs(uVal.get(0, 0)), Math.abs(uVal.get(1, 1))) / Math.min(Math.abs(uVal.get(0, 0)), 610 Math.abs(uVal.get(1, 1)))); // define the direction of warping 611 } 612 613 /* 614 * Selects diffrentiation scale 615 */ 616 AbstractStructureTensorIPD 617 selDifferentiationScale(FImage img, AbstractStructureTensorIPD ipdToUse, float si, Pixel c) 618 { 619 AbstractStructureTensorIPD best = null; 620 float s = 0.5f; 621 float sigma_prev = 0, sigma; 622 623 FImage L; 624 625 double qMax = 0; 626 627 L = img.clone(); 628 629 final AbstractStructureTensorIPD ipd = ipdToUse.clone(); 630 631 while (s <= 0.751) 632 { 633 Matrix M; 634 final float sd = s * si; 635 636 // Smooth previous smoothed image L 637 sigma = (float) Math.sqrt(Math.pow(sd, 2) - Math.pow(sigma_prev, 2)); 638 639 L.processInplace(new FGaussianConvolve(sigma, 3)); 640 sigma_prev = sd; 641 642 // X and Y derivatives 643 ipd.setDetectionScale(sd); 644 ipd.setIntegrationScale(si); 645 ipd.setImageBlurred(true); 646 647 ipd.findInterestPoints(L); 648 // FImage Lx = L.process(new FConvolution(DX_KERNEL.multiply(sd))); 649 // FImage Ly = L.process(new FConvolution(DY_KERNEL.multiply(sd))); 650 // 651 // FGaussianConvolve gauss = new FGaussianConvolve(si, 3); 652 // 653 // FImage Lxm2 = Lx.multiply(Lx); 654 // dx2 = Lxm2.process(gauss); 655 // 656 // FImage Lym2 = Ly.multiply(Ly); 657 // dy2 = Lym2.process(gauss); 658 // 659 // FImage Lxmy = Lx.multiply(Ly); 660 // dxy = Lxmy.process(gauss); 661 662 M = calcSecondMomentMatrix(ipd, c.x, c.y); 663 664 // calc eigenvalues 665 // EigenvalueDecomposition meig = M.eig(); 666 final EigenValueVectorPair meig = MatrixUtils.symmetricEig2x2(M); 667 final Matrix eval = meig.getValues(); 668 final double eval1 = Math.abs(eval.get(0, 0)); 669 final double eval2 = Math.abs(eval.get(1, 1)); 670 final double q = Math.min(eval1, eval2) / Math.max(eval1, eval2); 671 672 if (q >= qMax) { 673 qMax = q; 674 best = ipd.clone(); 675 } 676 677 s += 0.05; 678 } 679 return best; 680 } 681 682 AbstractStructureTensorIPD selDifferentiationScaleFast(FImage img, AbstractStructureTensorIPD ipd, float si, Pixel c) 683 { 684 final AbstractStructureTensorIPD best = ipd.clone(); 685 final float s = 0.75f; 686 float sigma; 687 FImage L; 688 L = img.clone(); 689 final float sd = s * si; 690 691 // Smooth previous smoothed image L 692 sigma = sd; 693 694 L.processInplace(new FGaussianConvolve(sigma, 3)); 695 696 // X and Y derivatives 697 best.setDetectionScale(sd); 698 best.setIntegrationScale(si); 699 best.setImageBlurred(true); 700 701 best.findInterestPoints(L); 702 703 // M = calcSecondMomentMatrix(best, c.x, c.y); 704 705 // EigenValueVectorPair meig = MatrixUtils.symmetricEig2x2(M); 706 // Matrix eval = meig.getD(); 707 // double eval1 = Math.abs(eval.get(0, 0)); 708 // double eval2 = Math.abs(eval.get(1, 1)); 709 710 return best; 711 } 712 713 // void calcAffineCovariantRegions(final Matrix image, final 714 // vector<KeyPoint> & keypoints, 715 // vector<Elliptic_KeyPoint> & affRegions, string detector_type) 716 // { 717 // 718 // for (size_t i = 0; i < keypoints.size(); ++i) 719 // { 720 // KeyPoint kp = keypoints[i]; 721 // Elliptic_KeyPoint ex(kp.pt, 0, Size_<float> (kp.size / 2, kp.size / 2), 722 // kp.size, 723 // kp.size / 6); 724 // 725 // if (calcAffineAdaptation(image, ex)) 726 // affRegions.push_back(ex); 727 // 728 // } 729 // //Erase similar keypoint 730 // float maxDiff = 4; 731 // Matrix colorimg; 732 // for (size_t i = 0; i < affRegions.size(); i++) 733 // { 734 // Elliptic_KeyPoint kp1 = affRegions[i]; 735 // for (size_t j = i+1; j < affRegions.size(); j++){ 736 // 737 // Elliptic_KeyPoint kp2 = affRegions[j]; 738 // 739 // if(norm(kp1.centre-kp2.centre)<=maxDiff){ 740 // double phi1, phi2; 741 // Size axes1, axes2; 742 // double si1, si2; 743 // phi1 = kp1.phi; 744 // phi2 = kp2.phi; 745 // axes1 = kp1.axes; 746 // axes2 = kp2.axes; 747 // si1 = kp1.si; 748 // si2 = kp2.si; 749 // if(Math.abs(phi1-phi2)<15 && Math.max(si1,si2)/Math.min(si1,si2)<1.4 && 750 // axes1.width-axes2.width<5 && axes1.height-axes2.height<5){ 751 // affRegions.erase(affRegions.begin()+j); 752 // j--; 753 // } 754 // } 755 // } 756 // } 757 // } 758 759 // void calcAffineCovariantDescriptors(final Ptr<DescriptorExtractor>& 760 // dextractor, final Mat& img, 761 // vector<Elliptic_KeyPoint>& affRegions, Mat& descriptors) 762 // { 763 // 764 // assert(!affRegions.empty()); 765 // int size = dextractor->descriptorSize(); 766 // int type = dextractor->descriptorType(); 767 // descriptors = Mat(Size(size, affRegions.size()), type); 768 // descriptors.setTo(0); 769 // 770 // int i = 0; 771 // 772 // for (vector<Elliptic_KeyPoint>::iterator it = affRegions.begin(); it < 773 // affRegions.end(); ++it) 774 // { 775 // Point p = it->centre; 776 // 777 // Mat_<float> size(2, 1); 778 // size(0, 0) = size(1, 0) = it->size; 779 // 780 // //U matrix 781 // Matrix transf = it->transf; 782 // Mat_<float> U(2, 2); 783 // U.setTo(0); 784 // Matrix col0 = U.col(0); 785 // transf.col(0).copyTo(col0); 786 // Matrix col1 = U.col(1); 787 // transf.col(1).copyTo(col1); 788 // 789 // float radius = it->size / 2; 790 // float si = it->si; 791 // 792 // Size_<float> boundingBox; 793 // 794 // double ac_b2 = determinant(U); 795 // boundingBox.width = ceil(U.get(1, 1)/ac_b2 * 3 * si ); 796 // boundingBox.height = ceil(U.get(0, 0)/ac_b2 * 3 * si ); 797 // 798 // //Create window around interest point 799 // float half_width = Math.min((float) Math.min(img.width - p.x-1, p.x), 800 // boundingBox.width); 801 // float half_height = Math.min((float) Math.min(img.height - p.y-1, p.y), 802 // boundingBox.height); 803 // float roix = max(p.x - (int) boundingBox.width, 0); 804 // float roiy = max(p.y - (int) boundingBox.height, 0); 805 // Rect roi = Rect(roix, roiy, p.x - roix + half_width+1, p.y - roiy + 806 // half_height+1); 807 // 808 // Matrix img_roi = img(roi); 809 // 810 // size(0, 0) = img_roi.width; 811 // size(1, 0) = img_roi.height; 812 // 813 // size = U * size; 814 // 815 // Matrix transfImgRoi, transfImg; 816 // warpAffine(img_roi, transfImgRoi, transf, Size(ceil(size(0, 0)), 817 // ceil(size(1, 0))), 818 // INTER_AREA, BORDER_DEFAULT); 819 // 820 // 821 // Mat_<float> c(2, 1); //Transformed point 822 // Mat_<float> pt(2, 1); //Image point 823 // //Point within the Roi 824 // pt(0, 0) = p.x - roix; 825 // pt(1, 0) = p.y - roiy; 826 // 827 // //Point in U-Normalized coordinates 828 // c = U * pt; 829 // float cx = c(0, 0); 830 // float cy = c(1, 0); 831 // 832 // 833 // //Cut around point to have patch of 2*keypoint->size 834 // 835 // roix = Math.max(cx - radius, 0.f); 836 // roiy = Math.max(cy - radius, 0.f); 837 // 838 // roi = Rect(roix, roiy, Math.min(cx - roix + radius, size(0, 0)), 839 // Math.min(cy - roiy + radius, size(1, 0))); 840 // transfImg = transfImgRoi(roi); 841 // 842 // cx = c(0, 0) - roix; 843 // cy = c(1, 0) - roiy; 844 // 845 // Matrix tmpDesc; 846 // KeyPoint kp(Point(cx, cy), it->size); 847 // 848 // vector<KeyPoint> k(1, kp); 849 // 850 // transfImg.convertTo(transfImg, CV_8U); 851 // dextractor->compute(transfImg, k, tmpDesc); 852 // 853 // for (int j = 0; j < tmpDesc.width; j++) 854 // { 855 // descriptors.get(i, j) = tmpDesc.get(0, j); 856 // } 857 // 858 // i++; 859 // 860 // } 861 // 862 // } 863 864 /** 865 * an example run 866 * 867 * @param args 868 * @throws IOException 869 */ 870 public static void main(String[] args) throws IOException { 871 final float sd = 5; 872 final float si = 1.4f * sd; 873 final HessianIPD ipd = new HessianIPD(sd, si); 874 final FImage img = ImageUtilities.readF(AffineAdaption.class 875 .getResourceAsStream("/org/openimaj/image/data/sinaface.jpg")); 876 877 // img = img.multiply(255f); 878 879 // ipd.findInterestPoints(img); 880 // List<InterestPointData> a = ipd.getInterestPoints(1F/(256*256)); 881 // 882 // System.out.println("Found " + a.size() + " features"); 883 // 884 // AffineAdaption adapt = new AffineAdaption(); 885 // EllipticKeyPoint kpt = new EllipticKeyPoint(); 886 final MBFImage outImg = new MBFImage(img.clone(), img.clone(), img.clone()); 887 // for (InterestPointData d : a) { 888 // 889 // // InterestPointData d = new InterestPointData(); 890 // // d.x = 102; 891 // // d.y = 396; 892 // logger.info("Keypoint at: " + d.x + ", " + d.y); 893 // kpt.si = si; 894 // kpt.centre = new Pixel(d.x, d.y); 895 // kpt.size = 2 * 3 * kpt.si; 896 // 897 // boolean converge = adapt.calcAffineAdaptation(img, kpt); 898 // if(converge) 899 // { 900 // outImg.drawShape(new 901 // Ellipse(kpt.centre.x,kpt.centre.y,kpt.axes.getX(),kpt.axes.getY(),kpt.phi), 902 // RGBColour.BLUE); 903 // outImg.drawPoint(kpt.centre, RGBColour.RED,3); 904 // } 905 // 906 // 907 // 908 // logger.info("... converged: "+ converge); 909 // } 910 final AffineAdaption adapt = new AffineAdaption(ipd, new IPDSelectionMode.Count(100)); 911 adapt.findInterestPoints(img); 912 final InterestPointVisualiser<Float[], MBFImage> ipv = InterestPointVisualiser.visualiseInterestPoints(outImg, 913 adapt.points); 914 DisplayUtilities.display(ipv.drawPatches(RGBColour.BLUE, RGBColour.RED)); 915 916 } 917 918 /** 919 * @param b 920 * whether the differencial scaling should be done iteratively 921 * (slow) or not (fast) 922 */ 923 public void setFastDifferentiationScale(boolean b) { 924 this.fastDifferentiationScale = b; 925 } 926}