/*
* Copyright (c) 2011-2016, 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.calibration;
import boofcv.alg.geo.RodriguesRotationJacobian;
import georegression.geometry.ConvertRotation3D_F64;
import georegression.geometry.GeometryMath_F64;
import georegression.struct.point.Point2D_F64;
import georegression.struct.point.Point3D_F64;
import georegression.struct.se.Se3_F64;
import georegression.struct.so.Rodrigues_F64;
import georegression.transform.se.SePointOps_F64;
import org.ddogleg.optimization.functions.FunctionNtoMxN;
import org.ejml.data.DenseMatrix64F;
import java.util.ArrayList;
import java.util.List;
/**
*
* Analytical Jacobian for optimizing calibration parameters.
*
*
*
* NOTE: Accuracy is tested in a unit test using a numerical Jacobian. A very crude tolerance
* was required to make it pass. I think this implementation is correct, but hand computing the
* Jacobian is error prone. In practice it produces virtually the same final results as the numerical
* Jacobian.
*
*
* @author Peter Abeles
*/
public class Zhang99OptimizationJacobian implements FunctionNtoMxN {
// used to compute the Jacobian from Rodrigues coordinates
RodriguesRotationJacobian rodJacobian = new RodriguesRotationJacobian();
// local variable which stores the predicted location of the feature in the camera frame
Rodrigues_F64 rodrigues = new Rodrigues_F64();
// number of functions and parameters being optimized
private int numParam, numFuncs;
// description of the calibration grid
private List grid = new ArrayList();
// List of observation sets. Required so that it knows the total number of observations in each set
private List observationSets;
// variables for storing intermediate results
private Se3_F64 se = new Se3_F64();
// location of point in camera frame
private Point3D_F64 cameraPt = new Point3D_F64();
// observed point location in undistorted normalized image coordinates
private Point2D_F64 normPt = new Point2D_F64();
// observed point location in distorted normalized image coordinates
private Point2D_F64 dnormPt = new Point2D_F64();
// stores the optimization parameters
private Zhang99ParamCamera param;
// output index for x and y
int indexJacX;
int indexJacY;
Point3D_F64 Xdot = new Point3D_F64();
/**
* Configurations the optimization function.
*
* @param grid Location of points on the calibration grid. z=0
*/
public Zhang99OptimizationJacobian(boolean assumeZeroSkew,
int numRadial ,
boolean includeTangential,
List observationSets,
List grid ) {
this.param = new Zhang99ParamCamera(assumeZeroSkew,numRadial,includeTangential);
this.observationSets = observationSets;
for( Point2D_F64 p : grid ) {
this.grid.add( new Point3D_F64(p.x,p.y,0) );
}
numParam = param.numParameters()+(3+3)*observationSets.size();
numFuncs = CalibrationPlanarGridZhang99.totalPoints(observationSets)*2;
param.zeroNotUsed();
}
@Override
public int getNumOfInputsN() {
return numParam;
}
@Override
public int getNumOfOutputsM() {
return numFuncs;
}
@Override
public void process(double[] input, double[] output) {
int index = param.setFromParam(input);
int indexPoint = 0;
for( int indexView = 0; indexView < observationSets.size(); indexView++ ) {
CalibrationObservation set = observationSets.get(indexView);
// extract rotation and translation parameters
double rodX = input[index++];
double rodY = input[index++];
double rodZ = input[index++];
double tranX = input[index++];
double tranY = input[index++];
double tranZ = input[index++];
rodrigues.setParamVector(rodX,rodY,rodZ);
rodJacobian.process(rodX,rodY,rodZ);
ConvertRotation3D_F64.rodriguesToMatrix(rodrigues, se.getR());
se.T.set(tranX, tranY, tranZ);
for( int i = 0; i < set.size(); i++ , indexPoint++ ) {
int gridIndex = set.points.get(i).index;
// index = (function index)*numParam
indexJacX = (2*indexPoint )*numParam;
indexJacY = (2*indexPoint + 1 )*numParam;
// Put the point in the camera's reference frame
SePointOps_F64.transform(se, grid.get(gridIndex), cameraPt);
// normalized pixel coordinates
normPt.x = cameraPt.x/ cameraPt.z;
normPt.y = cameraPt.y/ cameraPt.z;
// apply distortion to the normalized coordinate
dnormPt.set(normPt);
CalibrationPlanarGridZhang99.applyDistortion(dnormPt, param.radial, param.t1, param.t2);
calibrationGradient(dnormPt,output);
distortGradient(normPt,output);
indexJacX += indexView*6;
indexJacY += indexView*6;
rodriguesGradient(rodJacobian.Rx,grid.get(gridIndex),cameraPt, normPt,output);
rodriguesGradient(rodJacobian.Ry,grid.get(gridIndex),cameraPt, normPt,output);
rodriguesGradient(rodJacobian.Rz,grid.get(gridIndex),cameraPt, normPt,output);
translateGradient(cameraPt, normPt,output);
}
}
}
/**
* Gradient for calibration matrix
*/
private void calibrationGradient( Point2D_F64 distNorm , double[] output ) {
output[indexJacX++] = distNorm.x;
output[indexJacX++] = 0;
if( !param.assumeZeroSkew )
output[indexJacX++] = distNorm.y;
output[indexJacX++] = 1;
output[indexJacX++] = 0;
output[indexJacY++] = 0;
output[indexJacY++] = distNorm.y;
if( !param.assumeZeroSkew )
output[indexJacY++] = 0;
output[indexJacY++] = 0;
output[indexJacY++] = 1;
}
/**
* Gradient for radial and tangential distortion
*
* @param norm undistorted normalized image coordinate
*/
private void distortGradient( Point2D_F64 norm , double[] output ) {
double r2 = norm.x*norm.x + norm.y*norm.y;
double r2i = r2;
for( int i = 0; i < param.radial.length; i++ ) {
double xdot = norm.x*r2i;
double ydot = norm.y*r2i;
output[indexJacX++] = param.a*xdot + param.c*ydot;
output[indexJacY++] = param.b*ydot;
r2i *= r2;
}
if( param.includeTangential ) {
double xy2 = 2.0*norm.x*norm.y;
double r2yy = r2 + 2*norm.y*norm.y;
double r2xx = r2 + 2*norm.x*norm.x;
output[indexJacX++] = param.a*xy2 + param.c*r2yy;
output[indexJacY++] = param.b*r2yy;
output[indexJacX++] = param.a*r2xx + param.c*xy2;
output[indexJacY++] = param.b*xy2;
}
}
/**
* Adds to the Jacobian matrix using the derivative from a Rodrigues parameter.
*
* deriv [x,y] = [distort deriv] - [dist]*dot(z)/(z^2)*(R*X+T) + [dist]*(1/z)*dot(R)*X
*
* where R is rotation matrix, T is translation, z = z-coordinate of point in camera frame
*
* @param Rdot Jacobian for Rodrigues
* @param X Location of point in world coordinates
*/
private void rodriguesGradient( DenseMatrix64F Rdot ,
Point3D_F64 X ,
Point3D_F64 cameraPt ,
Point2D_F64 normPt ,
double[] output ) {
// create short hand for normalized image coordinate
final double x = normPt.x;
final double y = normPt.y;
final double r2 = x*x + y*y;
double r2i = r2;
double rdev = 1;
double sum = 0;
double sumdot = 0;
for( int i = 0; i < param.radial.length; i++ ) {
sum += param.radial[i]*r2i;
sumdot += param.radial[i]*2*(i+1)*rdev;
r2i *= r2;
rdev *= r2;
}
GeometryMath_F64.mult(Rdot,X,Xdot);
// part of radial distortion derivative
double r_dot = (x*Xdot.x + y*Xdot.y)/cameraPt.z - r2*Xdot.z/cameraPt.z;
// derivative of normPt
double n_dot_x = (-x*Xdot.z+Xdot.x)/cameraPt.z;
double n_dot_y = (-y*Xdot.z+Xdot.y)/cameraPt.z;
// double n_dot_z = 0;
// total partial derivative
double xdot = sumdot*r_dot*x + (1 + sum)*n_dot_x;
double ydot = sumdot*r_dot*y + (1 + sum)*n_dot_y;
// double zdot = 0;
if( param.includeTangential ) {
xdot += 2*param.t1*(n_dot_x*y + x*n_dot_y) + 6*param.t2*x*n_dot_x + 2*param.t2*y*n_dot_y;
ydot += 2*param.t1*x*n_dot_x + 6*param.t1*y*n_dot_y + 2*param.t2*(n_dot_x*y + x*n_dot_y);
}
output[indexJacX++] = param.a*xdot + param.c*ydot;
output[indexJacY++] = param.b*ydot;
}
/**
* Gradient for translational motion component
*
* deriv [x,y] = [distort deriv] - [dist]*dot(z)*T/(z^2) + [dist]*dot(T)/z
*
* where T is translation, z = z-coordinate of point in camera frame
*/
private void translateGradient( Point3D_F64 cameraPt ,
Point2D_F64 normPt ,
double[] output ) {
// create short hand for normalized image coordinate
final double x = normPt.x;
final double y = normPt.y;
final double r2 = x*x + y*y;
double r2i = r2;
double rdev = 1;
double sum = 0;
double sumdot = 0;
for( int i = 0; i < param.radial.length; i++ ) {
sum += param.radial[i]*r2i;
sumdot += param.radial[i]*(i+1)*rdev;
r2i *= r2;
rdev *= r2;
}
// Partial T.x
double xdot = sumdot*2*x*x/cameraPt.z + (1+sum)/cameraPt.z;
double ydot = sumdot*2*x*y/cameraPt.z;
// double zdot = 0
if( param.includeTangential ) {
xdot += (2*param.t1*y + param.t2*6*x)/cameraPt.z;
ydot += (2*param.t1*x + 2*y*param.t2)/cameraPt.z;
}
output[indexJacX++] = param.a*xdot + param.c*ydot;
output[indexJacY++] = param.b*ydot;
// Partial T.y
xdot = sumdot*2*y*x/cameraPt.z;
ydot = sumdot*2*y*y/cameraPt.z + (1 + sum)/cameraPt.z;
if( param.includeTangential ) {
xdot += (2*param.t1*x + param.t2*2*y)/cameraPt.z;
ydot += (6*param.t1*y + 2*x*param.t2)/cameraPt.z;
}
output[indexJacX++] = param.a*xdot + param.c*ydot;
output[indexJacY++] = param.b*ydot;
// Partial T.z
xdot = -sumdot*2*r2*x/cameraPt.z;
ydot = -sumdot*2*r2*y/cameraPt.z;
xdot += -(1 + sum)*x/cameraPt.z;
ydot += -(1 + sum)*y/cameraPt.z;
if( param.includeTangential ) {
xdot += -(4*param.t1*x*y + 6*param.t2*x*x + 2*param.t2*y*y)/cameraPt.z;
ydot += -(2*param.t1*x*x + 6*param.t1*y*y + 4*x*y*param.t2)/cameraPt.z;
}
output[indexJacX++] = param.a*xdot + param.c*ydot;
output[indexJacY++] = param.b*ydot;
}
}
Ads help maintain this website.