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}