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.feature.local.matcher;
031
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.List;
035
036import org.openimaj.image.feature.local.keypoints.Keypoint;
037import org.openimaj.util.pair.Pair;
038
039
040/**
041 * Matcher rejects matches with no local support
042 * 
043 * @author Jonathon Hare
044 * @param <T> 
045 *
046 */
047public class VotingKeypointMatcher<T extends Keypoint> extends FastBasicKeypointMatcher<T> implements LocalFeatureMatcher<T> {
048        int neighbours;
049        List<Pair<T>> consistentMatches = new ArrayList<Pair<T>>();
050        protected int minVote;
051        protected float singularityDistance;
052        
053        /**
054         * @param threshold threshold for determining matching keypoints
055         */
056        public VotingKeypointMatcher(int threshold) {
057                this(threshold, 15, 1, 200.0f); //default to 15 as in VideoGoogle paper
058        }
059        
060        /**
061         * @param threshold threshold for determining matching keypoints
062         * @param neighbours number of neighbours within which to check for local support
063         * @param minVote
064         * @param singularityDistance
065         */
066        public VotingKeypointMatcher(int threshold, int neighbours, int minVote, float singularityDistance) {
067                super(threshold);
068                this.neighbours = neighbours;
069                this.minVote = minVote;
070                this.singularityDistance = singularityDistance;
071        }
072        
073        /**
074         * @return a list of consistent matching keypoints according
075         * to the estimated model parameters.
076         */
077        @Override
078        public List<Pair<T>> getMatches() {
079                return consistentMatches;
080        }
081
082        /**
083         * @return a list of all matches irrespective of whether they fit the model
084         */
085        public List<Pair<T>> getAllMatches() {
086                return matches;
087        }
088
089        @Override
090        public boolean findMatches(List<T> keys1) {
091                super.findMatches(keys1);
092                
093                consistentMatches = new ArrayList<Pair<T>>();
094                
095                //filter dups
096                //matches = ConsistentKeypointMatcher.filterColinear(matches, 1);
097                
098                //filter spurious matches by voting
099                for (Pair<T> match : matches) {
100                        int vote = vote(match);
101                        if (vote > minVote)
102                                consistentMatches.add(match);
103                }
104                
105                if (consistentMatches.size() == 0) 
106                        return false;
107                
108                //reject mappings to very close points
109                if (checkSingularity()) {
110                        consistentMatches.clear();
111                        return false;
112                }
113                
114                return true;
115        }
116        
117        protected float [] getCentroid() {
118                float mx = consistentMatches.get(0).secondObject().x;
119                float my = consistentMatches.get(0).secondObject().y;
120                for (int i=1; i<consistentMatches.size(); i++) {
121                        mx += consistentMatches.get(i).secondObject().x;
122                        my += consistentMatches.get(i).secondObject().y;
123                }
124                return new float[] {mx/consistentMatches.size(), my/consistentMatches.size()};
125        }
126        
127        protected boolean checkSingularity() {
128                float [] centroid = getCentroid();
129                Keypoint k = new Keypoint();
130                k.y = centroid[1];
131                k.x = centroid[0];
132                
133                for (Pair<T> p : consistentMatches) {
134                        if (euclideanSqr(p.secondObject(), k) > singularityDistance) return false;
135                }
136                return true;
137        }
138        
139        protected int vote(Pair<T> match) {
140                List<T> nn = findModelNeighbours(match.secondObject());
141                int vote = 0;
142                
143                for (Pair<T> m : matches) {
144                        for (Keypoint k : nn) {
145                                if (m.secondObject() == k) {
146                                        vote++;
147                                        break;
148                                }
149                        }
150                }
151                return vote;
152        }
153        
154        protected float euclideanSqr(Keypoint k1, Keypoint k2) {
155                return ((k1.x - k2.x) * (k1.x - k2.x)) + 
156                                ((k1.y - k2.y)*(k1.y - k2.y));          
157        }
158        
159        protected List<T> findModelNeighbours(final T kp) {
160                class KpDist<Q extends Keypoint> implements Comparable<KpDist<Q>> {
161                        float distance;
162                        T keypoint;
163                        
164                        KpDist(T keypoint) {
165                                this.keypoint = keypoint;
166                                
167                                distance = euclideanSqr(keypoint, kp);
168                        }
169
170                        @Override
171                        public int compareTo(KpDist<Q> o) {
172                                if (distance > o.distance) return 1;
173                                if (distance < o.distance) return -1;
174                                return 0;
175                        }
176                }
177
178                List<KpDist<T>> list = new ArrayList<KpDist<T>>(); 
179                for (T k : modelKeypoints) {
180                        list.add(new KpDist<T>(k));
181                }
182                Collections.sort(list);
183                
184                List<T> keys = new ArrayList<T>();
185                for (int i=0; i<Math.min(neighbours, list.size()); i++)
186                        keys.add(list.get(i).keypoint);
187                
188                return keys;
189        }
190}