All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.irurueta.ar.sfm.LMSEInhomogeneousSinglePoint3DTriangulator Maven / Gradle / Ivy

There is a newer version: 1.3.0
Show newest version
/*
 * 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.sfm;

import com.irurueta.algebra.AlgebraException;
import com.irurueta.algebra.Matrix;
import com.irurueta.algebra.SingularValueDecomposer;
import com.irurueta.geometry.PinholeCamera;
import com.irurueta.geometry.Plane;
import com.irurueta.geometry.Point2D;
import com.irurueta.geometry.Point3D;
import com.irurueta.geometry.estimators.LockedException;

import java.util.List;

/**
 * Triangulates matched 2D points into a single 3D one by using 2D point
 * correspondences on different views along with the corresponding cameras on
 * each of those views by finding an LMSE solution to homogeneous systems of
 * equations.
 */
public class LMSEInhomogeneousSinglePoint3DTriangulator extends
        SinglePoint3DTriangulator {

    /**
     * Indicates if by default an LMSE (Least Mean Square Error) is allowed if
     * more correspondences than the minimum are provided.
     */
    public static final boolean DEFAULT_ALLOW_LMSE_SOLUTION = false;

    /**
     * Indicates if an LMSE (Least Mean Square Error) solution is allowed if
     * more correspondences than the minimum are provided. If false, the
     * exceeding correspondences will be ignored and only the 6 first
     * correspondences will be used.
     */
    private boolean mAllowLMSESolution;

    /**
     * Constructor.
     */
    public LMSEInhomogeneousSinglePoint3DTriangulator() {
        super();
        mAllowLMSESolution = DEFAULT_ALLOW_LMSE_SOLUTION;
    }

    /**
     * Constructor.
     *
     * @param points2D list of matched 2D points on each view. Each point in the
     *                 list is assumed to be projected by the corresponding camera in the list.
     * @param cameras  camera for each view where 2D points are represented.
     * @throws IllegalArgumentException if provided lists don't have the same
     *                                  length or their length is less than 2 views, which is the minimum
     *                                  required to compute triangulation.
     */
    public LMSEInhomogeneousSinglePoint3DTriangulator(final List points2D,
                                                      final List cameras) {
        super(points2D, cameras);
        mAllowLMSESolution = DEFAULT_ALLOW_LMSE_SOLUTION;
    }

    /**
     * Constructor.
     *
     * @param listener listener to notify events generated by instances of this
     *                 class.
     */
    public LMSEInhomogeneousSinglePoint3DTriangulator(
            final SinglePoint3DTriangulatorListener listener) {
        super(listener);
        mAllowLMSESolution = DEFAULT_ALLOW_LMSE_SOLUTION;
    }

    /**
     * Constructor.
     *
     * @param points2D list of matched 2D points on each view. Each point in the
     *                 list is assumed to be projected by the corresponding camera in the list.
     * @param cameras  cameras for each view where 2D points are represented.
     * @param listener listener to notify events generated by instances of this
     *                 class.
     * @throws IllegalArgumentException if provided lists don't have the same
     *                                  length or their length is less than 2 views, which is the minimum
     *                                  required to compute triangulation.
     */
    public LMSEInhomogeneousSinglePoint3DTriangulator(final List points2D,
                                                      final List cameras,
                                                      final SinglePoint3DTriangulatorListener listener) {
        super(points2D, cameras, listener);
        mAllowLMSESolution = DEFAULT_ALLOW_LMSE_SOLUTION;
    }

    /**
     * Indicates if an LMSE (Least Mean Square Error) solution is allowed if
     * more correspondences than the minimum are provided. If false, the
     * exceeding correspondences will be ignored and only the 2 first matches
     * corresponding to the first 2 views will be used.
     *
     * @return true if LMSE solution is allowed, false otherwise.
     */
    public boolean isLMSESolutionAllowed() {
        return mAllowLMSESolution;
    }

    /**
     * Specifies if an LMSE (Least Mean Square Error) solution is allowed if
     * more correspondences than the minimum are provided. If false, the
     * exceeding correspondences will be ignored and only the 2 first matches
     * corresponding to the first 2 views will be used.
     *
     * @param allowed true if LMSE solution is allowed, false otherwise.
     * @throws LockedException if estimator is locked.
     */
    public void setLMSESolutionAllowed(final boolean allowed) throws LockedException {
        if (isLocked()) {
            throw new LockedException();
        }
        mAllowLMSESolution = allowed;
    }

    /**
     * Returns type of triangulator.
     *
     * @return type of triangulator.
     */
    @Override
    public Point3DTriangulatorType getType() {
        return Point3DTriangulatorType.LMSE_INHOMOGENEOUS_TRIANGULATOR;
    }

    /**
     * Internal method to triangulate provided matched 2D points being projected
     * by each corresponding camera into a single 3D point.
     * At least 2 matched 2D points and their corresponding 2 cameras are
     * required to compute triangulation. If more views are provided, an
     * averaged solution is found.
     * This method does not check whether instance is locked or ready
     *
     * @param points2D matched 2D points. Each point in the list is assumed to
     *                 be projected by the corresponding camera in the list.
     * @param cameras  list of cameras associated to the matched 2D point on the
     *                 same position as the camera on the list.
     * @param result   instance where triangulated 3D point is stored.
     * @throws Point3DTriangulationException if triangulation fails for some
     *                                       other reason (i.e. degenerate geometry, numerical instabilities, etc).
     */
    @SuppressWarnings("DuplicatedCode")
    @Override
    protected void triangulate(
            final List points2D,
            final List cameras,
            final Point3D result) throws Point3DTriangulationException {
        try {
            mLocked = true;

            if (mListener != null) {
                mListener.onTriangulateStart(this);
            }

            final int numViews = cameras.size();

            final Matrix a;
            final double[] b;
            if (mAllowLMSESolution) {
                // each view will add 2 equations to the linear system of
                // equations
                a = new Matrix(2 * numViews, 3);
                b = new double[2 * numViews];
            } else {
                a = new Matrix(2 * MIN_REQUIRED_VIEWS, 3);
                b = new double[2 * MIN_REQUIRED_VIEWS];
            }

            Point2D point;
            PinholeCamera camera;
            Plane horizontalAxisPlane = new Plane();
            Plane verticalAxisPlane = new Plane();
            Plane principalPlane = new Plane();
            int row = 0;
            double rowNorm;
            for (int i = 0; i < numViews; i++) {
                point = points2D.get(i);
                camera = cameras.get(i);

                // to increase accuracy
                point.normalize();
                camera.normalize();

                final double homX = point.getHomX();
                final double homY = point.getHomY();
                final double homW = point.getHomW();

                // pick rows of camera corresponding to different planes
                // (we do not normalize planes, as it would introduce errors)

                // 1st camera row (p1T)
                camera.verticalAxisPlane(verticalAxisPlane);
                // 2nd camera row (p2T)
                camera.horizontalAxisPlane(horizontalAxisPlane);
                // 3rd camera row (p3T)
                camera.principalPlane(principalPlane);

                // 1st equation
                a.setElementAt(row, 0, homX * principalPlane.getA() -
                        homW * verticalAxisPlane.getA());
                a.setElementAt(row, 1, homX * principalPlane.getB() -
                        homW * verticalAxisPlane.getB());
                a.setElementAt(row, 2, homX * principalPlane.getC() -
                        homW * verticalAxisPlane.getC());

                b[row] = homW * verticalAxisPlane.getD() -
                        homX * principalPlane.getD();

                // normalize equation to increase accuracy
                rowNorm = Math.sqrt(Math.pow(a.getElementAt(row, 0), 2.0) +
                        Math.pow(a.getElementAt(row, 1), 2.0) +
                        Math.pow(a.getElementAt(row, 2), 2.0));

                a.setElementAt(row, 0, a.getElementAt(row, 0) / rowNorm);
                a.setElementAt(row, 1, a.getElementAt(row, 1) / rowNorm);
                a.setElementAt(row, 2, a.getElementAt(row, 2) / rowNorm);
                b[row] /= rowNorm;

                // 2nd equation
                row++;

                a.setElementAt(row, 0, homY * principalPlane.getA() -
                        homW * horizontalAxisPlane.getA());
                a.setElementAt(row, 1, homY * principalPlane.getB() -
                        homW * horizontalAxisPlane.getB());
                a.setElementAt(row, 2, homY * principalPlane.getC() -
                        homW * horizontalAxisPlane.getC());

                b[row] = homW * horizontalAxisPlane.getD() -
                        homY * principalPlane.getD();

                // normalize equation to increase accuracy
                rowNorm = Math.sqrt(Math.pow(a.getElementAt(row, 0), 2.0) +
                        Math.pow(a.getElementAt(row, 1), 2.0) +
                        Math.pow(a.getElementAt(row, 2), 2.0));

                a.setElementAt(row, 0, a.getElementAt(row, 0) / rowNorm);
                a.setElementAt(row, 1, a.getElementAt(row, 1) / rowNorm);
                a.setElementAt(row, 2, a.getElementAt(row, 2) / rowNorm);
                b[row] /= rowNorm;

                if (!mAllowLMSESolution && i == MIN_REQUIRED_VIEWS) {
                    break;
                }
            }

            // make SVD to find solution of A * M = b
            final SingularValueDecomposer decomposer = new SingularValueDecomposer(a);

            decomposer.decompose();

            // solve linear system of equations to obtain inhomogeneous
            // coordinates of triangulated point
            final double[] solution = decomposer.solve(b);

            result.setInhomogeneousCoordinates(solution[0], solution[1],
                    solution[2]);

            if (mListener != null) {
                mListener.onTriangulateEnd(this);
            }
        } catch (final AlgebraException e) {
            throw new Point3DTriangulationException(e);
        } finally {
            mLocked = false;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy