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}