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.consistent; 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.openimaj.feature.local.matcher.FastBasicKeypointMatcher; 036import org.openimaj.image.feature.local.keypoints.Keypoint; 037import org.openimaj.knn.CoordinateKDTree; 038import org.openimaj.math.geometry.point.Point2d; 039import org.openimaj.math.model.Model; 040import org.openimaj.math.model.fit.RobustModelFitting; 041import org.openimaj.util.pair.IndependentPair; 042import org.openimaj.util.pair.Pair; 043 044/** 045 * Improved version of ConsistentKeypointMatcher. Much much faster! We use a 046 * b-tree to implement scale space search & stop the matcher after a number of 047 * iterations and attempt to find a model with what we have. 048 * 049 * @author Jonathon Hare 050 * @param <T> 051 * The type of keypoint 052 * 053 */ 054public class LocalConsistentKeypointMatcher<T extends Keypoint> extends FastBasicKeypointMatcher<T> 055 implements 056 ModelFittingLocalFeatureMatcher<T> 057{ 058 RobustModelFitting<Point2d, Point2d, ?> modelfit; 059 List<Pair<T>> consistentMatches; 060 Model<Point2d, Point2d> model; 061 CoordinateKDTree<T> tree; 062 063 Keypoint minDim, maxDim; 064 065 /** 066 * Default constructor 067 * 068 * @param threshold 069 * threshold for determining matching keypoints 070 */ 071 public LocalConsistentKeypointMatcher(int threshold) { 072 super(threshold); 073 074 model = null; 075 consistentMatches = new ArrayList<Pair<T>>(); 076 077 minDim = new Keypoint(); 078 maxDim = new Keypoint(); 079 } 080 081 /** 082 * @return a list of consistent matching keypoints according to the 083 * estimated model parameters. 084 */ 085 @Override 086 public List<Pair<T>> getMatches() { 087 return consistentMatches; 088 } 089 090 /** 091 * @return a list of all matches irrespective of whether they fit the model 092 */ 093 public List<Pair<T>> getAllMatches() { 094 return matches; 095 } 096 097 @Override 098 public Model<Point2d, Point2d> getModel() { 099 return model; 100 } 101 102 /* 103 * Given a pair of images and their keypoints, pick the first keypoint from 104 * one image and find its closest match in the second set of keypoints. Then 105 * write the result to a file. 106 */ 107 @SuppressWarnings("unchecked") 108 @Override 109 public boolean findMatches(List<T> keys1) 110 { 111 // if we're gonna re-use the object, we need to reset everything! 112 model = null; 113 matches = new ArrayList<Pair<T>>(); 114 consistentMatches = new ArrayList<Pair<T>>(); 115 // 116 117 final List<Pair<Point2d>> li_p2d = new ArrayList<Pair<Point2d>>(); 118 119 final List<T> klist1 = new ArrayList<T>(); 120 tree.rangeSearch(klist1, minDim, maxDim); 121 122 // Keypoint.KeypointStats(klist1); 123 124 T initMatch = null; 125 for (final T k : keys1) { 126 // find a seed point 127 initMatch = checkForMatch(k, klist1); 128 129 if (initMatch != null) { 130 break; 131 } 132 } 133 134 // System.out.println("INIT: " + initMatch); 135 136 if (initMatch == null) { 137 System.out.println("no match found!"); 138 return false; 139 } 140 141 final Keypoint lbound = new Keypoint(); 142 final Keypoint ubound = new Keypoint(); 143 144 /* 145 * These parameters have been hardcoded, but in reality they probably 146 * shouldn't be. The values of +/-100 are just guestimates - a proper 147 * evaluation is needed to find optimal values (however +/-100 does seem 148 * to work well) 149 */ 150 lbound.x = initMatch.x - 100; 151 lbound.y = initMatch.y - 100; 152 lbound.scale = minDim.scale; 153 ubound.x = initMatch.x + 100; 154 ubound.y = initMatch.y + 100; 155 ubound.scale = maxDim.scale; 156 157 final List<T> klist = new ArrayList<T>(); 158 tree.rangeSearch(klist, lbound, ubound); 159 160 for (final T k : keys1) { 161 // find a seed point 162 final T match = checkForMatch(k, klist); 163 164 if (match != null) { 165 li_p2d.add(new Pair<Point2d>(k, match)); 166 matches.add(new Pair<T>(k, match)); 167 168 // System.out.println(k.col+", "+k.row+", "+k.scale+"\t->\t"+match.col+", "+match.row+", "+match.scale); 169 // System.out.format("%3.2f, %3.2f, %3.2f\t->\t%3.2f, %3.2f, %3.2f\n", 170 // k.col, k.row, k.scale, match.col, match.row, match.scale); 171 172 /* 173 * We could stop after a certain number of matches here... 174 * 175 * Actually, we could stop, then restart if we were unable to 176 * find a good model... 177 */ 178 if (matches.size() >= 10) 179 break; 180 } 181 } 182 183 if (matches.size() < modelfit.numItemsToEstimate()) { 184 System.out.println("Not enough matches to check consistency!"); 185 return false; 186 } 187 188 if (modelfit.fitData(li_p2d)) { 189 model = modelfit.getModel(); 190 for (final IndependentPair<Point2d, Point2d> p : modelfit.getInliers()) { 191 final Object op = p; 192 consistentMatches.add((Pair<T>) op); 193 } 194 } 195 return true; 196 } 197 198 @Override 199 public void setFittingModel(RobustModelFitting<Point2d, Point2d, ?> mf) { 200 modelfit = mf; 201 } 202 203 @Override 204 public void setModelFeatures(List<T> map) { 205 // build KDTree 206 try { 207 // System.out.println("building tree"); 208 tree = new CoordinateKDTree<T>(); 209 210 for (final T k : map) { 211 tree.insert(k); 212 213 if (k.x < minDim.x) 214 minDim.x = k.x; 215 if (k.y < minDim.y) 216 minDim.y = k.y; 217 if (k.scale < minDim.scale) 218 minDim.scale = k.scale; 219 220 if (k.x > maxDim.x) 221 maxDim.x = k.x; 222 if (k.y > maxDim.y) 223 maxDim.y = k.y; 224 if (k.scale > maxDim.scale) 225 maxDim.scale = k.scale; 226 } 227 // System.out.println("done"); 228 } catch (final Exception e) { 229 System.out.println(e); 230 } 231 } 232}