/*
* 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.ConvertRotation3D_F32;
import georegression.geometry.GeometryMath_F32;
import georegression.geometry.UtilPoint3D_F32;
import georegression.struct.point.Point3D_F32;
import georegression.struct.point.Vector3D_F32;
import georegression.struct.se.Se3_F32;
import georegression.struct.so.Quaternion_F32;
import org.ejml.simple.SimpleEVD;
import org.ejml.simple.SimpleMatrix;
import java.util.List;
/**
* <p>
* Finds the rigid body motion which minimizes the different between the two sets of associated points in 3D. Computes
* the quaternions directly in closed form.
* </p>
* <p>
* The mean square error function that is minimized is:<br>
* f(q) = (1/N) sum( i=1:N , ||x_i - R(q_r)*p_i + q_t||<sup>2</sup> )<br>
* where q is the transformation parameterized with q_r quaternions and q_t translation, x is the set of 'to' points
* and p is the set of 'from' points.
* </p>
* <p>
* The basic cross-covariance method sketch in Paul J. Besl and Neil D. McKay, "A Method for Registration of 3-D Shapes" IEEE
* Transactions on Pattern Analysis and Machine Intelligence, Vol 14, No. 2, Feb. 1992, is implemented below.
* </p>
*
* @author Peter Abeles
*/
public class MotionSe3PointCrossCovariance_F32 implements MotionTransformPoint<Se3_F32, Point3D_F32> {
// first 4 elements are quaternions and last 3 are translation
private float param[] = new float[7];
// rigid body motion
private Se3_F32 motion = new Se3_F32();
// temporarily stores the quaternion
private Quaternion_F32 quat = new Quaternion_F32();
public float[] getParam() {
return param;
}
@Override
public Se3_F32 getTransformSrcToDst() {
return motion;
}
@Override
public boolean process( List<Point3D_F32> srcPts, List<Point3D_F32> 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
Point3D_F32 meanFrom = UtilPoint3D_F32.mean(srcPts, null );
Point3D_F32 meanTo = UtilPoint3D_F32.mean(dstPts, null );
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
float s11 = 0, s12 = 0, s13 = 0;
float s21 = 0, s22 = 0, s23 = 0;
float s31 = 0, s32 = 0, s33 = 0;
// mu*mu^2
float m11 = meanFrom.x * meanTo.x, m12 = meanFrom.x * meanTo.y, m13 = meanFrom.x * meanTo.z;
float m21 = meanFrom.y * meanTo.x, m22 = meanFrom.y * meanTo.y, m23 = meanFrom.y * meanTo.z;
float m31 = meanFrom.z * meanTo.x, m32 = meanFrom.z * meanTo.y, m33 = meanFrom.z * meanTo.z;
for( int i = 0; i < N; i++ ) {
Point3D_F32 f = srcPts.get( i );
Point3D_F32 t = dstPts.get( i );
s11 += f.x * t.x;
s12 += f.x * t.y;
s13 += f.x * t.z;
s21 += f.y * t.x;
s22 += f.y * t.y;
s23 += f.y * t.z;
s31 += f.z * t.x;
s32 += f.z * t.y;
s33 += f.z * t.z;
}
s11 = s11 / N - m11;
s12 = s12 / N - m12;
s13 = s13 / N - m13;
s21 = s21 / N - m21;
s22 = s22 / N - m22;
s23 = s23 / N - m23;
s31 = s31 / N - m31;
s32 = s32 / N - m32;
s33 = s33 / N - m33;
SimpleMatrix Sigma = new SimpleMatrix( 3, 3, true, s11, s12, s13, s21, s22, s23, s31, s32, s33 );
// Sigma.print();
SimpleMatrix Delta = new SimpleMatrix( 3, 1, true, s23 - s32, s31 - s13, s12 - s21 );
SimpleMatrix Q = new SimpleMatrix( 4, 4 );
SimpleMatrix BR = Sigma.plus( Sigma.transpose() ).minus( SimpleMatrix.identity( 3 ).scale( Sigma.trace() ) );
Q.set( 0, 0, Sigma.trace() );
Q.insertIntoThis( 0, 1, Delta.transpose() );
Q.insertIntoThis( 1, 0, Delta );
Q.insertIntoThis( 1, 1, BR );
// Q.print();
extractQuaternionFromQ( Q );
// translation = meanTo - R*meanFrom
GeometryMath_F32.mult( motion.getR(), meanFrom, meanFrom );
Vector3D_F32 T = motion.getT();
param[4] = T.x = meanTo.x - meanFrom.x;
param[5] = T.y = meanTo.y - meanFrom.y;
param[6] = T.z = meanTo.z - meanFrom.z;
return true;
}
/**
* The unit eigenvector corresponding to the maximum eigenvalue of Q is the rotation
* parameterized as a quaternion.
*/
private void extractQuaternionFromQ( SimpleMatrix q ) {
SimpleEVD evd = q.eig();
int indexMax = evd.getIndexMax();
SimpleMatrix v_max = evd.getEigenVector( indexMax );
quat.w = (float) v_max.get( 0 );
quat.x = (float) v_max.get( 1 );
quat.y = (float) v_max.get( 2 );
quat.z = (float) v_max.get( 3 );
quat.normalize();
ConvertRotation3D_F32.quaternionToMatrix( quat, motion.getR() );
}
@Override
public int getMinimumPoints() {
return 3;
}
}