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.FundamentalModel; 037import org.openimaj.math.geometry.transforms.FundamentalRefinement; 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.model.fit.LMedS; 042import org.openimaj.math.model.fit.RANSAC; 043import org.openimaj.math.model.fit.RANSAC.StoppingCondition; 044import org.openimaj.math.model.fit.RobustModelFitting; 045import org.openimaj.util.pair.IndependentPair; 046import org.openimaj.util.pair.Pair; 047 048import Jama.Matrix; 049 050/** 051 * Helper class to simplify robust estimation of the Fundamental matrix without 052 * having to deal with the nuts and bolts of the underlying robust model 053 * fitters, etc. The overall robust estimation process is as follows: 054 * <p> 055 * An initial estimate of the inliers and an algebraically optimal Fundamental 056 * matrix is computed using {@link RANSAC} or {@link LMedS} with a 057 * {@link BucketingSampler2d} sampling strategy for selecting subsets. In both 058 * cases, the normalised 8-point algorithm is used (see 059 * {@link TransformUtilities#fundamentalMatrix8PtNorm(List)} 060 * <p> 061 * If an reasonable initial estimate was found, non-linear optimisation using 062 * Levenburg-Marquardt is performed to on the inliers using the initial estimate 063 * to optimise against a true geometric residual given by 064 * {@link FundamentalRefinement}. 065 * 066 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 067 */ 068public class RobustFundamentalEstimator implements RobustModelFitting<Point2d, Point2d, FundamentalModel> { 069 private RobustModelFitting<Point2d, Point2d, FundamentalModel> robustFitter; 070 private FundamentalRefinement refinement; 071 072 List<IndependentPair<Point2d, Point2d>> inliers = new ArrayList<IndependentPair<Point2d, Point2d>>(); 073 List<IndependentPair<Point2d, Point2d>> outliers = new ArrayList<IndependentPair<Point2d, Point2d>>(); 074 075 /** 076 * Construct using the {@link LMedS} algorithm with the given expected 077 * outlier percentage 078 * 079 * @param outlierProportion 080 * expected proportion of outliers (between 0 and 1) 081 * @param refinement 082 * the refinement technique 083 */ 084 public RobustFundamentalEstimator(double outlierProportion, FundamentalRefinement refinement) { 085 robustFitter = new LMedS<Point2d, Point2d, FundamentalModel>( 086 new FundamentalModel(false), 087 new FundamentalModel.Fundamental8PtResidual(), 088 outlierProportion, true, new BucketingSampler2d()); 089 090 this.refinement = refinement; 091 } 092 093 /** 094 * Construct using the {@link RANSAC} algorithm with the given options. 095 * 096 * @param threshold 097 * the threshold on the {@link AlgebraicResidual2d} at which to 098 * consider a point as an inlier 099 * @param nIterations 100 * the maximum number of iterations 101 * @param stoppingCondition 102 * the {@link StoppingCondition} for RANSAC 103 * @param refinement 104 * the refinement technique 105 */ 106 public RobustFundamentalEstimator(double threshold, int nIterations, StoppingCondition stoppingCondition, 107 FundamentalRefinement refinement) 108 { 109 robustFitter = new RANSAC<Point2d, Point2d, FundamentalModel>(new FundamentalModel(false), 110 new FundamentalModel.Fundamental8PtResidual(), threshold, nIterations, stoppingCondition, true, 111 new BucketingSampler2d()); 112 113 this.refinement = refinement; 114 } 115 116 @Override 117 public boolean fitData(List<? extends IndependentPair<Point2d, Point2d>> data) { 118 final Pair<Matrix> norms = TransformUtilities.getNormalisations(data); 119 final List<? extends IndependentPair<Point2d, Point2d>> normData = TransformUtilities.normalise(data, norms); 120 121 // Use a robust fitting technique to find the inliers and estimate a 122 // model using DLT 123 if (!robustFitter.fitData(normData)) { 124 // just go with full-on DLT estimate rather than a robust one 125 robustFitter.getModel().estimate(normData); 126 robustFitter.getModel().denormaliseFundamental(norms); 127 128 return false; 129 } 130 131 // remap the inliers and outliers from the normalised ones to the 132 // original space 133 inliers.clear(); 134 for (final IndependentPair<Point2d, Point2d> pair : robustFitter.getInliers()) { 135 inliers.add(data.get(normData.indexOf(pair))); 136 } 137 outliers.clear(); 138 for (final IndependentPair<Point2d, Point2d> pair : robustFitter.getOutliers()) { 139 outliers.add(data.get(normData.indexOf(pair))); 140 } 141 142 // denormalise the estimated matrix before the non-linear step 143 robustFitter.getModel().denormaliseFundamental(norms); 144 145 // Now apply non-linear optimisation to get a better estimate 146 final Matrix optimised = refinement.refine(robustFitter.getModel().getF(), inliers); 147 robustFitter.getModel().setF(optimised); 148 149 return true; 150 } 151 152 @Override 153 public int numItemsToEstimate() { 154 return robustFitter.numItemsToEstimate(); 155 } 156 157 @Override 158 public FundamentalModel getModel() { 159 return robustFitter.getModel(); 160 } 161 162 @Override 163 public List<? extends IndependentPair<Point2d, Point2d>> getInliers() { 164 return inliers; 165 } 166 167 @Override 168 public List<? extends IndependentPair<Point2d, Point2d>> getOutliers() { 169 return outliers; 170 } 171}