
com.irurueta.ar.epipolar.estimators.EightPointsFundamentalMatrixEstimator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of irurueta-ar Show documentation
Show all versions of irurueta-ar Show documentation
Augmented Reality and 3D reconstruction library
/*
* Copyright (C) 2015 Alberto Irurueta Carro ([email protected])
*
* 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 com.irurueta.ar.epipolar.estimators;
import com.irurueta.algebra.AlgebraException;
import com.irurueta.algebra.Matrix;
import com.irurueta.algebra.SingularValueDecomposer;
import com.irurueta.algebra.Utils;
import com.irurueta.ar.epipolar.FundamentalMatrix;
import com.irurueta.ar.epipolar.InvalidFundamentalMatrixException;
import com.irurueta.geometry.Point2D;
import com.irurueta.geometry.ProjectiveTransformation2D;
import com.irurueta.geometry.estimators.LockedException;
import com.irurueta.geometry.estimators.NormalizerException;
import com.irurueta.geometry.estimators.NotReadyException;
import com.irurueta.geometry.estimators.Point2DNormalizer;
import java.util.List;
/**
* Non-robust fundamental matrix estimator that uses 8 matched 2D points on
* left and right views.
*/
public class EightPointsFundamentalMatrixEstimator extends
FundamentalMatrixEstimator {
/**
* Constant indicating that by default an LMSE solution is not allowed.
*/
public static final boolean DEFAULT_ALLOW_LMSE_SOLUTION = false;
/**
* Minimum number of matched 2D points to start the estimation.
*/
public static final int MIN_REQUIRED_POINTS = 8;
/**
* Indicates if by default provided point correspondences are normalized to
* increase the accuracy of the estimation.
*/
public static final boolean DEFAULT_NORMALIZE_POINT_CORRESPONDENCES = true;
/**
* Indicates whether an LMSE (Least Mean Square Error) solution is allowed
* or not. When an LMSE solution is allowed, more than 8 matched points can
* be used for fundamental matrix estimation. If LMSE solution is not
* allowed then only the 8 former matched points will be taken into account.
*/
private boolean mAllowLMSESolution;
/**
* Indicates whether provided matched 2D points must be normalized to
* increase the accuracy of the estimation.
*/
private boolean mNormalizePoints;
/**
* Constructor.
*/
public EightPointsFundamentalMatrixEstimator() {
super();
mAllowLMSESolution = DEFAULT_ALLOW_LMSE_SOLUTION;
mNormalizePoints = DEFAULT_NORMALIZE_POINT_CORRESPONDENCES;
}
/**
* Constructor with matched 2D points.
*
* @param leftPoints 2D points on left view.
* @param rightPoints 2D points on right view.
* @throws IllegalArgumentException if provided list of points do not
* have the same length.
*/
public EightPointsFundamentalMatrixEstimator(final List leftPoints,
final List rightPoints) {
super(leftPoints, rightPoints);
mAllowLMSESolution = DEFAULT_ALLOW_LMSE_SOLUTION;
mNormalizePoints = DEFAULT_NORMALIZE_POINT_CORRESPONDENCES;
}
/**
* Returns boolean indicating whether an LMSE (Least Mean Square Error)
* solution is allowed or not. When an LMSE solution is allowed, more than 8
* matched points can be used for fundamental matrix estimation. If LMSE
* solution is not allowed then only the 8 former matched points will be
* taken into account.
*
* @return true if an LMSE solution is allowed, false otherwise.
*/
public boolean isLMSESolutionAllowed() {
return mAllowLMSESolution;
}
/**
* Sets boolean indicating whether an LMSE (LEast Mean Square Error)
* solution is allowed or not. When an LMSE solution is allowed, more than 8
* matched points can be used for fundamental matrix estimation. If LMSE
* solution is not allowed then only the 8 former matched points will be
* taken into account.
*
* @param allowed true if an LMSE solution is allowed, false otherwise.
* @throws LockedException if this instance is locked because an estimation
* is in progress.
*/
public void setLMSESolutionAllowed(final boolean allowed) throws LockedException {
if (isLocked()) {
throw new LockedException();
}
mAllowLMSESolution = allowed;
}
/**
* Indicates whether provided matched 2D points must be normalized to
* increase the accuracy of the estimation.
*
* @return true if points must be normalized, false otherwise.
*/
public boolean arePointsNormalized() {
return mNormalizePoints;
}
/**
* Sets boolean indicating whether provided matched 2D points must be
* normalized to increase the accuracy of the estimation.
*
* @param normalizePoints true if points must be normalized, false
* otherwise.
* @throws LockedException if this instance is locked because an estimation
* is in progress.
*/
public void setPointsNormalized(final boolean normalizePoints)
throws LockedException {
if (isLocked()) {
throw new LockedException();
}
mNormalizePoints = normalizePoints;
}
/**
* Returns boolean indicating whether estimator is ready to start the
* fundamental matrix estimation.
* This is true when the required minimum number of matched points is
* provided to obtain a solution and both left and right views have the
* same number of matched points.
*
* @return true if estimator is ready to start the fundamental matrix
* estimation, false otherwise.
*/
@Override
public boolean isReady() {
return mLeftPoints != null && mRightPoints != null &&
mLeftPoints.size() == mRightPoints.size() &&
mLeftPoints.size() >= MIN_REQUIRED_POINTS;
}
/**
* Estimates a fundamental matrix using provided lists of matched points on
* left and right views.
*
* @return a fundamental matrix.
* @throws LockedException if estimator is locked doing an estimation.
* @throws NotReadyException if estimator is not ready because required
* input points have not already been provided.
* @throws FundamentalMatrixEstimatorException if configuration of provided
* 2D points is degenerate and fundamental matrix estimation fails.
*/
@SuppressWarnings("DuplicatedCode")
@Override
public FundamentalMatrix estimate() throws LockedException,
NotReadyException, FundamentalMatrixEstimatorException {
if (isLocked()) {
throw new LockedException();
}
if (!isReady()) {
throw new NotReadyException();
}
mLocked = true;
if (mListener != null) {
mListener.onEstimateStart(this);
}
final int nPoints = mLeftPoints.size();
try {
ProjectiveTransformation2D leftNormalization = null;
ProjectiveTransformation2D rightNormalization = null;
final List leftPoints;
final List rightPoints;
if (mNormalizePoints) {
// normalize points on left view
final Point2DNormalizer normalizer = new Point2DNormalizer(
mLeftPoints);
normalizer.compute();
leftNormalization = normalizer.getTransformation();
// normalize points on right view
normalizer.setPoints(mRightPoints);
normalizer.compute();
rightNormalization = normalizer.getTransformation();
// normalize to increase accuracy
leftNormalization.normalize();
rightNormalization.normalize();
leftPoints = leftNormalization.transformPointsAndReturnNew(
mLeftPoints);
rightPoints = rightNormalization.transformPointsAndReturnNew(
mRightPoints);
} else {
leftPoints = mLeftPoints;
rightPoints = mRightPoints;
}
final Matrix a;
if (isLMSESolutionAllowed()) {
a = new Matrix(nPoints, 9);
} else {
a = new Matrix(MIN_REQUIRED_POINTS, 9);
}
Point2D leftPoint;
Point2D rightPoint;
double homLeftX;
double homLeftY;
double homLeftW;
double homRightX;
double homRightY;
double homRightW;
double value0;
double value1;
double value2;
double value3;
double value4;
double value5;
double value6;
double value7;
double value8;
double rowNorm;
for (int i = 0; i < nPoints; i++) {
leftPoint = leftPoints.get(i);
rightPoint = rightPoints.get(i);
// normalize points to increase accuracy
leftPoint.normalize();
rightPoint.normalize();
homLeftX = leftPoint.getHomX();
homLeftY = leftPoint.getHomY();
homLeftW = leftPoint.getHomW();
homRightX = rightPoint.getHomX();
homRightY = rightPoint.getHomY();
homRightW = rightPoint.getHomW();
// set a row values
value0 = homLeftX * homRightX;
value1 = homLeftY * homRightX;
value2 = homLeftW * homRightX;
value3 = homLeftX * homRightY;
value4 = homLeftY * homRightY;
value5 = homLeftW * homRightY;
value6 = homLeftX * homRightW;
value7 = homLeftY * homRightW;
value8 = homLeftW * homRightW;
// normalize row to increase accuracy
rowNorm = Math.sqrt(Math.pow(value0, 2.0) +
Math.pow(value1, 2.0) + Math.pow(value2, 2.0) +
Math.pow(value3, 2.0) + Math.pow(value4, 2.0) +
Math.pow(value5, 2.0) + Math.pow(value6, 2.0) +
Math.pow(value7, 2.0) + Math.pow(value8, 2.0));
a.setElementAt(i, 0, value0 / rowNorm);
a.setElementAt(i, 1, value1 / rowNorm);
a.setElementAt(i, 2, value2 / rowNorm);
a.setElementAt(i, 3, value3 / rowNorm);
a.setElementAt(i, 4, value4 / rowNorm);
a.setElementAt(i, 5, value5 / rowNorm);
a.setElementAt(i, 6, value6 / rowNorm);
a.setElementAt(i, 7, value7 / rowNorm);
a.setElementAt(i, 8, value8 / rowNorm);
if (!isLMSESolutionAllowed() &&
i == (MIN_REQUIRED_POINTS - 1)) {
break;
}
}
final SingularValueDecomposer decomposer = new SingularValueDecomposer(a);
decomposer.decompose();
// if nullity of provided a matrix is not of dimension 1 (number of
// dimensions of null-space), then epipolar geometry is degenerate
// because there is more than one possible solution (up to scale).
// This is typically due to co-linearities or co-planarities on
// projected 2D points. In this case we throw an exception
if (decomposer.getNullity() > 1) {
throw new FundamentalMatrixEstimatorException();
}
Matrix v = decomposer.getV();
// The fundamental matrix is contained in vector form on the last
// column of V, we reshape such vector into a 3x3 matrix
Matrix fundMatrix = new Matrix(
FundamentalMatrix.FUNDAMENTAL_MATRIX_ROWS,
FundamentalMatrix.FUNDAMENTAL_MATRIX_COLS);
fundMatrix.setElementAt(0, 0, v.getElementAt(0, 8));
fundMatrix.setElementAt(0, 1, v.getElementAt(1, 8));
fundMatrix.setElementAt(0, 2, v.getElementAt(2, 8));
fundMatrix.setElementAt(1, 0, v.getElementAt(3, 8));
fundMatrix.setElementAt(1, 1, v.getElementAt(4, 8));
fundMatrix.setElementAt(1, 2, v.getElementAt(5, 8));
fundMatrix.setElementAt(2, 0, v.getElementAt(6, 8));
fundMatrix.setElementAt(2, 1, v.getElementAt(7, 8));
fundMatrix.setElementAt(2, 2, v.getElementAt(8, 8));
if (mNormalizePoints && leftNormalization != null) {
// denormalize fundMatrix
final Matrix transposedRightTransformationMatrix =
rightNormalization.asMatrix().transposeAndReturnNew();
final Matrix leftTransformationMatrix = leftNormalization.asMatrix();
// compute fundMatrix = transposedRightTransformationMatrix *
// fundMatrix * leftTransformationMatrix
fundMatrix.multiply(leftTransformationMatrix);
transposedRightTransformationMatrix.multiply(fundMatrix);
fundMatrix = transposedRightTransformationMatrix;
// normalize by Frobenius norm to increase accuracy after point
// de-normalization
final double norm = Utils.normF(fundMatrix);
fundMatrix.multiplyByScalar(1.0 / norm);
}
// enforce rank 2
decomposer.setInputMatrix(fundMatrix);
decomposer.decompose();
// if rank is not already correct, then we enforce it
final int rank = decomposer.getRank();
if (rank > FundamentalMatrix.FUNDAMENTAL_MATRIX_RANK) {
// rank needs to be reduced
final Matrix u = decomposer.getU();
final Matrix w = decomposer.getW();
v = decomposer.getV();
// transpose V
v.transpose();
final Matrix transV = v;
// set last singular value to zero to enforce rank 2
w.setElementAt(2, 2, 0.0);
// compute fundMatrix = U * W * V'
w.multiply(transV);
u.multiply(w);
fundMatrix = u;
} else if (rank < FundamentalMatrix.FUNDAMENTAL_MATRIX_RANK) {
// rank is 1, which is lower than required fundamental matrix
// rank (rank 2)
throw new FundamentalMatrixEstimatorException();
}
final FundamentalMatrix result = new FundamentalMatrix(fundMatrix);
if (mListener != null) {
mListener.onEstimateEnd(this, result);
}
return result;
} catch (final InvalidFundamentalMatrixException | AlgebraException | NormalizerException e) {
throw new FundamentalMatrixEstimatorException(e);
} finally {
mLocked = false;
}
}
/**
* Returns method of non-robust fundamental matrix estimator.
*
* @return method of fundamental matrix estimator.
*/
@Override
public FundamentalMatrixEstimatorMethod getMethod() {
return FundamentalMatrixEstimatorMethod.EIGHT_POINTS_ALGORITHM;
}
/**
* Returns minimum number of matched pair of points required to start
* the estimation. This implementation requires a minimum of 8 points
*
* @return minimum number of matched pair of points required to start
* the estimation. Always returns 8.
*/
@Override
public int getMinRequiredPoints() {
return MIN_REQUIRED_POINTS;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy