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.math.geometry.transforms.estimation; 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.openimaj.math.geometry.point.Point2d; 036import org.openimaj.math.geometry.transforms.HomographyModel; 037import org.openimaj.math.geometry.transforms.HomographyRefinement; 038import org.openimaj.math.geometry.transforms.TransformUtilities; 039import org.openimaj.math.geometry.transforms.estimation.sampling.BucketingSampler2d; 040import org.openimaj.math.geometry.transforms.residuals.AlgebraicResidual2d; 041import org.openimaj.math.geometry.transforms.residuals.SymmetricTransferResidual2d; 042import org.openimaj.math.model.fit.LMedS; 043import org.openimaj.math.model.fit.RANSAC; 044import org.openimaj.math.model.fit.RANSAC.StoppingCondition; 045import org.openimaj.math.model.fit.RobustModelFitting; 046import org.openimaj.util.function.Predicate; 047import org.openimaj.util.pair.IndependentPair; 048import org.openimaj.util.pair.Pair; 049 050import Jama.Matrix; 051 052/** 053 * Helper class to simplify robust estimation of homographies without having to 054 * deal with the nuts and bolts of the underlying robust model fitters, etc. The 055 * overall robust estimation process is as follows: 056 * <p> 057 * An initial estimate of the inliers and an algebraically optimal homography is 058 * computed using {@link RANSAC} or {@link LMedS} with a 059 * {@link BucketingSampler2d} sampling strategy for selecting subsets. In both 060 * cases, the normalised DLT algorithm is used (see 061 * {@link TransformUtilities#homographyMatrixNorm(List)} 062 * <p> 063 * If an reasonable initial estimate was found, non-linear optimisation using 064 * Levenburg-Marquardt is performed to on the inliers using the initial estimate 065 * to optimise against a true geometric residual given by 066 * {@link HomographyRefinement}. 067 * 068 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 069 */ 070public class RobustHomographyEstimator implements RobustModelFitting<Point2d, Point2d, HomographyModel> { 071 private RobustModelFitting<Point2d, Point2d, HomographyModel> robustFitter; 072 private HomographyRefinement refinement; 073 074 List<IndependentPair<Point2d, Point2d>> inliers = new ArrayList<IndependentPair<Point2d, Point2d>>(); 075 List<IndependentPair<Point2d, Point2d>> outliers = new ArrayList<IndependentPair<Point2d, Point2d>>(); 076 077 /** 078 * Construct using the {@link LMedS} algorithm with the given expected 079 * outlier percentage 080 * 081 * @param outlierProportion 082 * expected proportion of outliers (between 0 and 1) 083 * @param refinement 084 * the refinement technique 085 */ 086 public RobustHomographyEstimator(double outlierProportion, HomographyRefinement refinement) { 087 robustFitter = new LMedS<Point2d, Point2d, HomographyModel>( 088 new HomographyModel(false), 089 new SymmetricTransferResidual2d<HomographyModel>(), 090 outlierProportion, true, new BucketingSampler2d()); 091 092 this.refinement = refinement; 093 } 094 095 /** 096 * Construct using the {@link RANSAC} algorithm with the given options. 097 * 098 * @param threshold 099 * the threshold on the {@link AlgebraicResidual2d} at which to 100 * consider a point as an inlier 101 * @param nIterations 102 * the maximum number of iterations 103 * @param stoppingCondition 104 * the {@link StoppingCondition} for RANSAC 105 * @param refinement 106 * the refinement technique 107 */ 108 public RobustHomographyEstimator(double threshold, int nIterations, StoppingCondition stoppingCondition, 109 HomographyRefinement refinement) 110 { 111 robustFitter = new RANSAC<Point2d, Point2d, HomographyModel>(new HomographyModel(false), 112 new SymmetricTransferResidual2d<HomographyModel>(), threshold, nIterations, stoppingCondition, true, 113 new BucketingSampler2d()); 114 115 this.refinement = refinement; 116 } 117 118 /** 119 * Construct using the {@link LMedS} algorithm with the given expected 120 * outlier percentage 121 * 122 * @param outlierProportion 123 * expected proportion of outliers (between 0 and 1) 124 * @param refinement 125 * the refinement technique 126 * @param modelCheck 127 * the predicate to test whether an estimated model is sane 128 */ 129 public RobustHomographyEstimator(double outlierProportion, HomographyRefinement refinement, 130 Predicate<HomographyModel> modelCheck) 131 { 132 robustFitter = new LMedS<Point2d, Point2d, HomographyModel>( 133 new HomographyModel(false, modelCheck), 134 new SymmetricTransferResidual2d<HomographyModel>(), 135 outlierProportion, true, new BucketingSampler2d()); 136 137 this.refinement = refinement; 138 } 139 140 /** 141 * Construct using the {@link RANSAC} algorithm with the given options. 142 * 143 * @param threshold 144 * the threshold on the {@link AlgebraicResidual2d} at which to 145 * consider a point as an inlier 146 * @param nIterations 147 * the maximum number of iterations 148 * @param stoppingCondition 149 * the {@link StoppingCondition} for RANSAC 150 * @param refinement 151 * the refinement technique 152 * @param modelCheck 153 * the predicate to test whether an estimated model is sane 154 */ 155 public RobustHomographyEstimator(double threshold, int nIterations, StoppingCondition stoppingCondition, 156 HomographyRefinement refinement, Predicate<HomographyModel> modelCheck) 157 { 158 robustFitter = new RANSAC<Point2d, Point2d, HomographyModel>(new HomographyModel(false, modelCheck), 159 new SymmetricTransferResidual2d<HomographyModel>(), threshold, nIterations, stoppingCondition, true, 160 new BucketingSampler2d()); 161 162 this.refinement = refinement; 163 } 164 165 @Override 166 public boolean fitData(List<? extends IndependentPair<Point2d, Point2d>> data) { 167 final Pair<Matrix> norms = TransformUtilities.getNormalisations(data); 168 final List<? extends IndependentPair<Point2d, Point2d>> normData = TransformUtilities.normalise(data, norms); 169 170 // Use a robust fitting technique to find the inliers and estimate a 171 // model using DLT 172 if (!robustFitter.fitData(normData)) { 173 // just go with full-on DLT estimate rather than a robust one 174 robustFitter.getModel().estimate(normData); 175 robustFitter.getModel().denormaliseHomography(norms); 176 177 return false; 178 } 179 180 // remap the inliers and outliers from the normalised ones to the 181 // original space 182 inliers.clear(); 183 for (final IndependentPair<Point2d, Point2d> pair : robustFitter.getInliers()) { 184 inliers.add(data.get(normData.indexOf(pair))); 185 } 186 outliers.clear(); 187 for (final IndependentPair<Point2d, Point2d> pair : robustFitter.getOutliers()) { 188 outliers.add(data.get(normData.indexOf(pair))); 189 } 190 191 // denormalise the estimated matrix before the non-linear step 192 robustFitter.getModel().denormaliseHomography(norms); 193 194 // Now apply non-linear optimisation to get a better estimate 195 final Matrix optimised = refinement.refine(robustFitter.getModel().getTransform(), inliers); 196 robustFitter.getModel().setTransform(optimised); 197 198 return true; 199 } 200 201 @Override 202 public int numItemsToEstimate() { 203 return robustFitter.numItemsToEstimate(); 204 } 205 206 @Override 207 public HomographyModel getModel() { 208 return robustFitter.getModel(); 209 } 210 211 @Override 212 public List<? extends IndependentPair<Point2d, Point2d>> getInliers() { 213 return inliers; 214 } 215 216 @Override 217 public List<? extends IndependentPair<Point2d, Point2d>> getOutliers() { 218 return outliers; 219 } 220}