/*
* Copyright (C) 2011-2015, Peter Abeles. All Rights Reserved.
*
* This file is part of Geometric Regression Library (GeoRegression).
*
* 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 georegression.fitting.se;
import georegression.fitting.MotionTransformPoint;
import georegression.geometry.GeometryMath_F64;
import georegression.geometry.UtilPoint2D_F64;
import georegression.struct.point.Point2D_F64;
import georegression.struct.se.Se2_F64;
import org.ejml.data.DenseMatrix64F;
import org.ejml.factory.DecompositionFactory;
import org.ejml.interfaces.decomposition.SingularValueDecomposition;
import org.ejml.ops.CommonOps;
import java.util.List;
/**
* <p>
* Finds the rigid body motion which minimizes the different between the two sets of associated points in 2D. The
* rotation is computed from the SVD of a cross correlation matrix.
* </p>
* <p>
* The mean square error function that is minimized is:<br>
* f(p) = (1/N) sum( i=1:N , ||x_i - R(theta)*p_i + T||<sup>2</sup> )<br>
* where theta is the angle of rotation and T is the translation, x is the set of 'to' points
* and p is the set of 'from' points.
* </p>
* <p>
* Based upon the sketch of Arun et al. 1987 provided in: D.W. Eggert, A. Loruso, R.B. Fisher, "Estimating 3-D Rigid Body Transformation:
* A Comparison of Four Major Algorithms" Machine Vision and Applications 1997
* </p>
*
* @author Peter Abeles
*/
public class MotionSe2PointSVD_F64 implements MotionTransformPoint<Se2_F64, Point2D_F64> {
Se2_F64 motion = new Se2_F64();
Point2D_F64 meanFrom = new Point2D_F64();
Point2D_F64 meanTo = new Point2D_F64();
SingularValueDecomposition<DenseMatrix64F> svd = DecompositionFactory.svd(2,2,true,true,false);
DenseMatrix64F Sigma = new DenseMatrix64F(2,2);
DenseMatrix64F U = new DenseMatrix64F(2,2);
DenseMatrix64F V = new DenseMatrix64F(2,2);
DenseMatrix64F R = new DenseMatrix64F(2,2);
@Override
public Se2_F64 getTransformSrcToDst() {
return motion;
}
@Override
public boolean process( List<Point2D_F64> srcPts, List<Point2D_F64> dstPts) {
if( srcPts.size() != dstPts.size() )
throw new IllegalArgumentException( "There must be a 1 to 1 correspondence between the two sets of points" );
// find the mean of both sets of points
UtilPoint2D_F64.mean(srcPts, meanFrom );
UtilPoint2D_F64.mean(dstPts, meanTo );
final int N = srcPts.size();
// compute the cross-covariance matrix Sigma of the two sets of points
// Sigma = (1/N)*sum(i=1:N,[p*x^T]) + mu_p*mu_x^T
// for performance reasons usage of matrices is postponed
double s11 = 0, s12 = 0;
double s21 = 0, s22 = 0;
// mu*mu^2
double m11 = meanFrom.x * meanTo.x, m12 = meanFrom.x * meanTo.y;
double m21 = meanFrom.y * meanTo.x, m22 = meanFrom.y * meanTo.y;
for( int i = 0; i < N; i++ ) {
Point2D_F64 f = srcPts.get( i );
Point2D_F64 t = dstPts.get( i );
s11 += f.x * t.x;
s12 += f.x * t.y;
s21 += f.y * t.x;
s22 += f.y * t.y;
}
s11 = s11 / N - m11;
s12 = s12 / N - m12;
s21 = s21 / N - m21;
s22 = s22 / N - m22;
Sigma.data[0] = s11;Sigma.data[1] = s12;
Sigma.data[2] = s21;Sigma.data[3] = s22;
// Compute the SVD of the cross correlation matrix
// The rotation matrix is R = V*U^T
if( !svd.decompose(Sigma) )
return false;
svd.getU(U,false);
svd.getV(V, false);
CommonOps.multTransB(V,U,R);
// There are situations where R might not have a determinant of one and is instead
// a reflection is returned
/**/double det = CommonOps.det(R);
if( det < 0 ) {
for( int i = 0; i < 2; i++ )
V.set( i, 1, -V.get( i, 1 ) );
CommonOps.multTransB(V,U,R);
det = CommonOps.det(R);
if( det < 0 ) {
throw new RuntimeException( "Crap" );
}
}
// extract the yaw from the rotation matrix
double yaw = Math.atan2( R.get( 1, 0 ), R.get( 0, 0 ) );
// save the results
GeometryMath_F64.rotate( yaw, meanFrom, meanFrom );
motion.getTranslation().x = meanTo.x - meanFrom.x;
motion.getTranslation().y = meanTo.y - meanFrom.y;
motion.setYaw( yaw );
return true;
}
@Override
public int getMinimumPoints() {
return 3;
}
}