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}