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.detector.ipd.finder;
031
032import java.util.TreeSet;
033
034import org.openimaj.feature.local.list.LocalFeatureList;
035import org.openimaj.image.feature.local.interest.IPDSelectionMode;
036import org.openimaj.image.feature.local.interest.InterestPointData;
037import org.openimaj.image.feature.local.interest.MultiscaleInterestPointDetector;
038import org.openimaj.image.feature.local.keypoints.InterestPointKeypoint;
039import org.openimaj.math.geometry.shape.Ellipse;
040
041/**
042 * A characteristic octave interest point finder throws
043 * {@link InterestPointData} away if two instances are similar. Similarity is
044 * defined by the position, rotation and axis ratio of the two interest points.
045 *
046 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
047 *
048 * @param <T>
049 */
050public class CharacteristicOctaveInterestPointFinder<T extends InterestPointData> extends OctaveInterestPointFinder<T> {
051
052        private static final double DEFAULT_MAX_DISTANCE = 4;
053        private static final double DEFAULT_MAX_ROTATION = (Math.PI / 180.0) * 15.0;
054        private static final double DEFAULT_MAX_AXIS_RATIO = 0.1;
055        /**
056         * The maximum distance before two keypoints are considered "similar"
057         */
058        public double maxDistance = DEFAULT_MAX_DISTANCE;
059        /**
060         * The maximum rotation difference before two keypoints are considered
061         * "similar"
062         */
063        public double maxRotation = DEFAULT_MAX_ROTATION;
064        /**
065         * The maximum axis ratio difference before two keypoints are considered
066         * similar
067         */
068        public double maxAxisRatio = DEFAULT_MAX_AXIS_RATIO;
069
070        /**
071         * construct this finder with the detector and selection mode
072         * 
073         * @param detector
074         * @param selectionMode
075         */
076        public CharacteristicOctaveInterestPointFinder(MultiscaleInterestPointDetector<T> detector,
077                        IPDSelectionMode selectionMode)
078        {
079                super(detector, selectionMode);
080        }
081
082        @Override
083        public void finish() {
084                final LocalFeatureList<InterestPointKeypoint<T>> locatedFeatures = this.listener.getFeatures();
085                final TreeSet<Integer> toRemove = new TreeSet<Integer>();
086                for (int i = 0; i < locatedFeatures.size(); i++) {
087                        final InterestPointKeypoint<T> kp1 = locatedFeatures.get(i);
088                        for (int j = i + 1; j < locatedFeatures.size(); j++) {
089                                final InterestPointKeypoint<T> kp2 = locatedFeatures.get(j);
090                                if (similarTo(kp1, kp2)) {
091                                        if (kp1.location.score >= kp2.location.score) {
092                                                toRemove.add(j);
093                                        }
094                                        else {
095                                                toRemove.add(i);
096                                        }
097                                }
098                        }
099                }
100                int nRemove = 0;
101                for (final int index : toRemove) {
102                        locatedFeatures.remove(index - nRemove++);
103                }
104        }
105
106        private boolean similarTo(InterestPointKeypoint<T> kp1, InterestPointKeypoint<T> kp2) {
107                boolean similar = true;
108                // Similar position
109                similar = Math.sqrt(Math.pow(kp1.x - kp2.x, 2) + Math.pow(kp1.y - kp2.y, 2)) < maxDistance;
110                if (!similar)
111                        return false;
112                final Ellipse e1 = kp1.location.getEllipse();
113                final Ellipse e2 = kp2.location.getEllipse();
114                // Ellipse with a similar rotation
115                similar = Math.abs(e1.getRotation() - e2.getRotation()) < maxRotation;
116                if (!similar)
117                        return false;
118
119                // Similar semi-major and semi-minor axis ratio
120                similar = Math.abs((e1.getMinor() / e1.getMajor()) - (e2.getMinor() / e2.getMajor())) < maxAxisRatio;
121                if (!similar)
122                        return false;
123
124                return true;
125        }
126
127}