/*
* Copyright (c) 2012-2014, Peter Abeles. All Rights Reserved.
*
* This file is part of DDogleg (http://ddogleg.org).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ddogleg.fitting.modelset.lmeds;
import org.ddogleg.fitting.modelset.DistanceFromModel;
import org.ddogleg.fitting.modelset.ModelGenerator;
import org.ddogleg.fitting.modelset.ModelManager;
import org.ddogleg.fitting.modelset.ModelMatcher;
import org.ddogleg.fitting.modelset.ransac.Ransac;
import org.ddogleg.sorting.QuickSelect;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
*
* Another technique similar to RANSAC known as Least Median of Squares (LMedS). For each iteration a small number N points are selected. A model
* is fit to these points and then the error is computed for the whole set. The model which minimizes the
* median is selected as the final model. No pruning or formal selection of inlier set is done.
*
* @author Peter Abeles
*/
// TODO Better algorithm for selecting the inlier set.
// Maybe revert this back to the way it was before and just have it be a separate alg entirely.
public class LeastMedianOfSquares implements ModelMatcher {
// random number generator for selecting points
private Random rand;
// number of times it performs its fit cycle
private int totalCycles;
// how many points it samples to generate a model from
private int sampleSize;
// if the best model has more than this error then it is considered a bad match
private double maxMedianError;
// fits a model to the provided data
private ModelGenerator generator;
// computes the error for a point to the model
private DistanceFromModel errorMetric;
// where the initial small set of points is stored
private List smallSet = new ArrayList();
// parameter being considered
private Model candidate;
// the parameter with the best error
private Model bestParam;
private double bestMedian;
// stores all the errors for quicker sorting
private double []errors = new double[1];
// list of indexes converting it from match set to input list
private int []matchToInput = new int[1];
private List inlierSet;
private double inlierFrac;
/**
* Configures the algorithm.
*
* @param randSeed Random seed used internally.
* @param totalCycles Number of random draws it will make when estimating model parameters.
* @param maxMedianError If the best median error is larger than this it is considered a failure.
* @param inlierFraction Data which is this fraction or lower is considered an inlier and used to recompute model parameters at the end. Set to 0 to turn off. Domain: 0 to 1.
* @param generator Creates a list of model hypotheses from a small set of points.
* @param errorMetric Computes the error between a point and a model
*/
public LeastMedianOfSquares( long randSeed ,
int totalCycles ,
double maxMedianError ,
double inlierFraction ,
ModelManager modelManager,
ModelGenerator generator,
DistanceFromModel errorMetric )
{
this.rand = new Random(randSeed);
this.totalCycles = totalCycles;
this.maxMedianError = maxMedianError;
this.inlierFrac = inlierFraction;
this.generator = generator;
this.errorMetric = errorMetric;
bestParam = modelManager.createModelInstance();
candidate = modelManager.createModelInstance();
this.sampleSize = generator.getMinimumPoints();
if( inlierFrac > 0.0 ) {
inlierSet = new ArrayList();
} else if( inlierFrac > 1.0 ) {
throw new IllegalArgumentException("Inlier fraction must be <= 1");
}
}
/**
* Configures the algorithm.
*
* @param randSeed Random seed used internally.
* @param totalCycles Number of random draws it will make when estimating model parameters.
* @param generator Creates a list of model hypotheses from a small set of points.
* @param errorMetric Computes the error between a point and a model
*/
public LeastMedianOfSquares( long randSeed ,
int totalCycles ,
ModelManager modelManager,
ModelGenerator generator,
DistanceFromModel errorMetric )
{
this(randSeed,totalCycles,Double.MAX_VALUE,0,modelManager,generator,errorMetric);
}
/**
* Number of points it samples to compute a model from. Typically this is the minimum number of points needed.
*
* @param sampleSize Number of points sampled when computing the model.
*/
public void setSampleSize(int sampleSize) {
this.sampleSize = sampleSize;
}
@Override
public boolean process(List dataSet) {
if( dataSet.size() < sampleSize )
return false;
int N = dataSet.size();
// make sure the array is large enough. If not declare a new one that is
if( errors.length < N ) {
errors = new double[ N ];
matchToInput = new int[N];
}
bestMedian = Double.MAX_VALUE;
for( int i = 0; i < totalCycles; i++ ) {
Ransac.randomDraw(dataSet, sampleSize, smallSet, rand);
if( generator.generate(smallSet, candidate) ) {
errorMetric.setModel(candidate);
errorMetric.computeDistance(dataSet,errors);
double median = QuickSelect.select(errors, N / 2, N);
if( median < bestMedian ) {
bestMedian = median;
Model t = bestParam;
bestParam = candidate;
candidate = t;
}
}
}
// if configured to do so compute the inlier set
computeInlierSet(dataSet, N);
return bestMedian <= maxMedianError;
}
private void computeInlierSet(List dataSet, int n) {
int numPts = (int)(n *inlierFrac);
if( inlierFrac > 0 && numPts > sampleSize ) {
inlierSet.clear();
errorMetric.setModel(bestParam);
errorMetric.computeDistance(dataSet,errors);
int []indexes = new int[n];
QuickSelect.selectIndex(errors,numPts, n,indexes);
for( int i = 0; i < numPts; i++ ) {
int origIndex = indexes[i];
inlierSet.add( dataSet.get(origIndex) );
matchToInput[i] = origIndex;
}
} else {
inlierSet = dataSet;
}
}
@Override
public Model getModelParameters() {
return bestParam;
}
/**
* If configured to computer the inlier set it returns the computed inliers. Otherwise
* it returns the data set orginally passed in.
*
* @return Set of points that are inliers to the returned model parameters..
*/
@Override
public List getMatchSet() {
return inlierSet;
}
@Override
public int getInputIndex(int matchIndex) {
return matchToInput[matchIndex];
}
/**
* Value of the best median error.
* @return
*/
@Override
public double getFitQuality() {
return bestMedian;
}
@Override
public int getMinimumSize() {
return sampleSize;
}
}
Ads help maintain this website.