/*
 * Copyright (c) 2011-2015, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.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 boofcv.alg.geo.pose;

import boofcv.struct.geo.AssociatedPair;
import georegression.geometry.GeometryMath_F64;
import georegression.struct.point.Point2D_F64;
import georegression.struct.point.Point3D_F64;
import georegression.struct.point.Vector3D_F64;
import georegression.struct.se.Se3_F64;
import org.ejml.data.DenseMatrix64F;
import org.ejml.factory.DecompositionFactory;
import org.ejml.interfaces.decomposition.SingularValueDecomposition;
import org.ejml.ops.CommonOps;
import org.ejml.ops.SingularOps;

import java.util.List;


/**
 * 

* Estimates the camera motion using linear algebra given a set of N associated point observations and the * depth (z-coordinate) of each object, where N ≥ 6. Note this is similar to, but not exactly the PnP problem. *

* *

* Output from this class is a rotation and translation that converts a point from the first to second * camera's reference frame:

* {@code X' = R*X+T}
* where R is a rotation matrix, T is a translation matrix, X is a coordinate in 1st reference frame, and X' * in the second. * *

* This approach is a modified version of the approach discussed in [1]. It is derived by using * bilinear and trilinear constraints, as is discussed in Section 8.3. It has been modified to remove * redundant rows and so that the computed rotation matrix is row major. The solution is derived from * the equations below and by computing the null space from the resulting matrix:
* cross(x2)*(R*x1) + cross(x2)*T/λi=0
* where cross(x) is the cross product matrix of X, xi is the calibrated image pixel coordinate in the * ith image, R is rotation and T translation. *

* *

* [1] "An Invitation to 3-D Vision, From Images to Geometric Models" 1st Ed. 2004. Springer. *

* * @author Peter Abeles */ public class PoseFromPairLinear6 { // The rank 11 linear system private DenseMatrix64F A = new DenseMatrix64F(1,12); // used to decompose and compute the null space of A private SingularValueDecomposition svd = DecompositionFactory.svd(0, 0, true, true, false); // parameterized rotation and translation private DenseMatrix64F x = new DenseMatrix64F(12,1); // the found motion private Se3_F64 motion = new Se3_F64(); /** * Computes the transformation between two camera frames using a linear equation. Both the * observed feature locations in each camera image and the depth (z-coordinate) of each feature * must be known. Feature locations are in calibrated image coordinates. * * @param observations List of observations on the image plane in calibrated coordinates. * @param locations List of object locations. One for each observation pair. */ public void process( List observations , List locations ) { if( observations.size() != locations.size() ) throw new IllegalArgumentException("Number of observations and locations must match."); if( observations.size() < 6 ) throw new IllegalArgumentException("At least (if not more than) six points are required."); setupA(observations,locations); computeTransform(A); // make sure R is really a rotation matrix massageResults(); } /** * Estimated motion * * @return rigid body motion */ public Se3_F64 getMotion() { return motion; } /** * Matrix used internally. */ protected DenseMatrix64F getA() { return A; } private void setupA(List observations , List locations) { A.reshape(2*observations.size(),12,false); for( int i = 0; i < observations.size(); i++ ) { AssociatedPair p = observations.get(i); Point3D_F64 loc = locations.get(i); Point2D_F64 pt1 = p.p1; Point2D_F64 pt2 = p.p2; // normalize the points int w=i*2; double alpha = 1.0/loc.z; A.set( w , 3 , -pt1.x); A.set( w , 4 , -pt1.y); A.set( w , 5 , -1); A.set( w , 6 , pt2.y*pt1.x); A.set( w , 7 , pt2.y*pt1.y); A.set( w , 8 , pt2.y); A.set( w , 9 , 0); A.set( w , 10 , -alpha); A.set( w , 11 , alpha*pt2.y); w++; A.set( w , 0 , pt1.x); A.set( w , 1 , pt1.y); A.set( w , 2 , 1); A.set( w , 6 , -pt2.x*pt1.x); A.set( w , 7 , -pt2.x*pt1.y); A.set( w , 8 , -pt2.x); A.set( w , 9 , alpha); A.set( w , 10 , 0); A.set( w , 11 , -alpha*pt2.x); } } /** * Computes the null space of A and extracts the transform. */ private void computeTransform( DenseMatrix64F A ) { if( !svd.decompose(A) ) throw new RuntimeException("SVD failed?"); SingularOps.nullVector(svd,true,x); DenseMatrix64F R = motion.getR(); Vector3D_F64 T = motion.getT(); // extract the results System.arraycopy(x.data,0,R.data,0,9); T.x = x.data[9]; T.y = x.data[10]; T.z = x.data[11]; } /** *

* Since the linear solution is probably not an exact rotation matrix, this code finds the best * approximation. *

* * See page 280 of [1] */ private void massageResults() { DenseMatrix64F R = motion.getR(); Vector3D_F64 T = motion.getT(); if( !svd.decompose(R)) throw new RuntimeException("SVD Failed"); CommonOps.multTransB(svd.getU(null,false),svd.getV(null,false),R); // determinant should be +1 double det = CommonOps.det(R); if( det < 0 ) CommonOps.scale(-1,R); // compute the determinant of the singular matrix double b = 1.0; double s[] = svd.getSingularValues(); for( int i = 0; i < svd.numberOfSingularValues(); i++ ) { b *= s[i]; } b = Math.signum(det)/Math.pow(b,1.0/3.0); GeometryMath_F64.scale(T,b); } }

Ads help maintain this website.