/*
 * 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 georegression.geometry.ConvertRotation3D_F64;
import georegression.struct.point.Point2D_F64;
import georegression.struct.se.Se3_F64;
import org.ddogleg.optimization.FactoryOptimization;
import org.ddogleg.optimization.UnconstrainedLeastSquares;
import org.ejml.data.DenseMatrix64F;

import java.util.ArrayList;
import java.util.List;

/**
 * 

* Full implementation of the Zhang99 camera calibration algorithm using planar calibration targets. First * linear approximations of camera parameters are computed, which are then refined using non-linear estimation. * One difference from the original paper is that tangential distortion can be included. No linear estimate * if found for tangential, they are estimated by initializing the non-linear estimate with all zero. *

* *

* When processing the results be sure to take in account the coordinate system being left or right handed. Calibration * works just fine with either coordinate system, but most 3D geometric algorithms assume a right handed coordinate * system while most images are left handed. *

* *

* A listener can be provide that will give status updates and allows requests for early termination. If a request * for early termination is made then a RuntimeException will be thrown. *

* *

* [1] Zhengyou Zhang, "Flexible Camera Calibration By Viewing a Plane From Unknown Orientations,", * International Conference on Computer Vision (ICCV'99), Corfu, Greece, pages 666-673, September 1999. *

* * @author Peter Abeles */ public class CalibrationPlanarGridZhang99 { // estimation algorithms private Zhang99ComputeTargetHomography computeHomography; private Zhang99CalibrationMatrixFromHomographies computeK; private RadialDistortionEstimateLinear computeRadial; private Zhang99DecomposeHomography decomposeH = new Zhang99DecomposeHomography(); // contains found parameters private Zhang99ParamAll optimized; // optimization algorithm private UnconstrainedLeastSquares optimizer; // provides information on calibration status private Listener listener; // where calibration points are layout on the target. private List layout; /** * Configures calibration process. * * @param layout Layout of calibration points on the target * @param assumeZeroSkew Should it assumed the camera has zero skew. Typically true. * @param numRadialParam Number of radial distortion parameters to consider. Typically 0,1,2. * @param includeTangential Should it include tangential distortion? */ public CalibrationPlanarGridZhang99(List layout, boolean assumeZeroSkew, int numRadialParam, boolean includeTangential ) { this.layout = layout; computeHomography = new Zhang99ComputeTargetHomography(layout); computeK = new Zhang99CalibrationMatrixFromHomographies(assumeZeroSkew); computeRadial = new RadialDistortionEstimateLinear(layout,numRadialParam); optimized = new Zhang99ParamAll(assumeZeroSkew,numRadialParam,includeTangential); } /** * Used to listen in on progress and request that processing be stopped * * @param listener The listener */ public void setListener(Listener listener) { this.listener = listener; } /** * Processes observed calibration point coordinates and computes camera intrinsic and extrinsic * parameters. * * @param observations Set of observed grid locations in pixel coordinates. * @return true if successful and false if it failed */ public boolean process( List observations ) { optimized.setNumberOfViews(observations.size()); // compute initial parameter estimates using linear algebra Zhang99ParamAll initial = initialParam(observations); if( initial == null ) return false; status("Non-linear refinement"); // perform non-linear optimization to improve results if( !optimizedParam(observations,layout,initial,optimized,optimizer)) return false; return true; } /** * Find an initial estimate for calibration parameters using linear techniques. */ protected Zhang99ParamAll initialParam( List observations ) { status("Estimating Homographies"); List homographies = new ArrayList(); List motions = new ArrayList(); for( CalibrationObservation obs : observations ) { if( !computeHomography.computeHomography(obs) ) return null; DenseMatrix64F H = computeHomography.getHomography(); homographies.add(H); } status("Estimating Calibration Matrix"); computeK.process(homographies); DenseMatrix64F K = computeK.getCalibrationMatrix(); decomposeH.setCalibrationMatrix(K); for( DenseMatrix64F H : homographies ) { motions.add(decomposeH.decompose(H)); } status("Estimating Radial Distortion"); computeRadial.process(K, homographies, observations); double distort[] = computeRadial.getParameters(); return convertIntoZhangParam(motions, K,optimized.assumeZeroSkew, distort, optimized.includeTangential); } private void status( String message ) { if( listener != null ) { if( !listener.zhangUpdate(message) ) throw new RuntimeException("User requested termination of calibration"); } } /** * Use non-linear optimization to improve the parameter estimates * * @param observations Observations of calibration points in each image * @param grid Location of calibration points on calibration target * @param initial Initial estimate of calibration parameters. * @param found The refined calibration parameters. * @param optimizer Algorithm used to optimize parameters */ public boolean optimizedParam( List observations , List grid , Zhang99ParamAll initial , Zhang99ParamAll found , UnconstrainedLeastSquares optimizer ) { if( optimizer == null ) { // optimizer = FactoryOptimization.leastSquaresTrustRegion(1, // RegionStepType.DOG_LEG_FTF,true); optimizer = FactoryOptimization.leastSquaresLM(1e-3,true); // optimizer = FactoryOptimization.leastSquareLevenberg(1e-3); } double model[] = new double[ initial.numParameters() ]; initial.convertToParam(model); Zhang99OptimizationFunction func = new Zhang99OptimizationFunction( initial.createNew(), grid,observations); Zhang99OptimizationJacobian jacobian = new Zhang99OptimizationJacobian( initial.assumeZeroSkew,initial.radial.length,initial.includeTangential, observations,grid); optimizer.setFunction(func,jacobian); optimizer.initialize(model,1e-10,1e-25*observations.size()); for( int i = 0; i < 500; i++ ) { if( optimizer.iterate() ) { break; } else { if( i % 25 == 0 ) status("Progress "+(100*i/500.0)+"%"); } } double param[] = optimizer.getParameters(); found.setFromParam(param); return true; } /** * Converts results fond in the linear algorithms into {@link Zhang99ParamAll} */ public static Zhang99ParamAll convertIntoZhangParam(List motions, DenseMatrix64F K, boolean assumeZeroSkew, double[] distort, boolean includeTangential ) { Zhang99ParamAll ret = new Zhang99ParamAll(); ret.assumeZeroSkew = assumeZeroSkew; ret.a = K.get(0,0); ret.b = K.get(1,1); ret.c = K.get(0,1); ret.x0 = K.get(0,2); ret.y0 = K.get(1,2); ret.radial = distort; ret.includeTangential = includeTangential; ret.views = new Zhang99ParamAll.View[motions.size()]; for( int i = 0; i < ret.views.length; i++ ) { Se3_F64 m = motions.get(i); Zhang99ParamAll.View v = new Zhang99ParamAll.View(); v.T = m.getT(); ConvertRotation3D_F64.matrixToRodrigues(m.getR(), v.rotation); ret.views[i] = v; } return ret; } /** * Applies radial and tangential distortion to the normalized image coordinate. * * @param normPt point in normalized image coordinates * @param radial radial distortion parameters * @param t1 tangential parameter * @param t2 tangential parameter */ public static void applyDistortion(Point2D_F64 normPt, double[] radial, double t1 , double t2 ) { final double x = normPt.x; final double y = normPt.y; double a = 0; double r2 = x*x + y*y; double r2i = r2; for( int i = 0; i < radial.length; i++ ) { a += radial[i]*r2i; r2i *= r2; } normPt.x = x + x*a + 2*t1*x*y + t2*(r2 + 2*x*x); normPt.y = y + y*a + t1*(r2 + 2*y*y) + 2*t2*x*y; } /** * Specify which optimization algorithm to use */ public void setOptimizer(UnconstrainedLeastSquares optimizer) { this.optimizer = optimizer; } public Zhang99ParamAll getOptimized() { return optimized; } public static int totalPoints( List observations ) { int total = 0; for (int i = 0; i < observations.size(); i++) { total += observations.get(i).size(); } return total; } public interface Listener { /** * Updated to update the status and request that processing be stopped * * @param taskName Name of the task being performed * @return true to continue and false to request a stop */ public boolean zhangUpdate( String taskName ); } }

Ads help maintain this website.