boofcv.alg.geo.calibration.RadialDistortionEstimateLinear Maven / Gradle / Ivy
Show all versions of boofcv-geo Show documentation
/*
* Copyright (c) 2023, 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.GeometryMath_F64;
import georegression.struct.point.Point2D_F64;
import lombok.Setter;
import org.ejml.data.DMatrixRMaj;
import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
import org.ejml.interfaces.linsol.LinearSolverDense;
import java.util.List;
import java.util.Objects;
/**
*
* Estimates radial lens distortion by solving a linear equation with observed features on a calibration grid.
* Typically used as an initial estimate for use in non-linear optimization. An arbitrary number of distortion
* parameters can be solved for, but typically only two are found. The estimated is computed using a
* known intrinsic camera matrix,and homographies relating grid to camera coordinates.
* Based upon the description found in Section 3.3 in [1].
*
*
*
* Radial distortion is modeled using the following equations:
* u' = u + (u - u0)*[ k1(x2 + y2) +k2(x2 + y2)2]
* v' = v + (v - v0)*[ k1(x2 + y2) +k2(x2 + y2)2]
* where (u',v') is the observed distortion in pixel coordinates, (u0,v0) is the image center in
* pixel coordinates, (x,y) is the predicted distortion free calibrated coordinates.
*
*
*
* The algorithm works by solving the system of equations below:
*
* [ (u-u0)*r2 , (u-u0)*r4 ][k1] = [u'-u]
* [ (v-v0)*r2 , (v-v0)*r4 ][k2] = [v'-v]
*
* where r2 = (x2+y2).
*
*
*
* [1] Zhengyou Zhang, "Flexible Camera Calibration By Viewing a Plane From Unknown Orientations," 1999
*
*
* @author Peter Abeles
*/
@SuppressWarnings({"NullAway.Init"})
public class RadialDistortionEstimateLinear {
// matrices in the linear equations
private final DMatrixRMaj A = new DMatrixRMaj(1, 1);
private final DMatrixRMaj B = new DMatrixRMaj(1, 1);
// where the results are stored
private final DMatrixRMaj X;
// linear solver
private final LinearSolverDense solver = LinearSolverFactory_DDRM.leastSquares(0, 0);
/**
* Spatial location of calibration points on each target. Z-axis is assumed to be zero.
*/
private @Setter List> layouts;
/**
* Creates a estimator for the specified number of distortion parameters.
*
* @param numParam Number of radial distortion parameters. Two is a good number.
*/
public RadialDistortionEstimateLinear( int numParam ) {
X = new DMatrixRMaj(numParam, 1);
}
/**
* Computes radial distortion using a linear method.
*
* @param cameraCalibration Camera calibration matrix. Not modified.
* @param observations Observations of calibration grid. Not modified.
*/
public void process( DMatrixRMaj cameraCalibration,
List homographies,
List observations ) {
Objects.requireNonNull(layouts, "Must specify world points first");
init(observations);
setupA_and_B(cameraCalibration, homographies, observations);
if (!solver.setA(A))
throw new RuntimeException("Solver had problems");
solver.solve(B, X);
}
/**
* Declares and sets up data structures
*/
private void init( List observations ) {
int totalPoints = 0;
for (int i = 0; i < observations.size(); i++) {
totalPoints += observations.get(i).size();
}
A.reshape(2*totalPoints, X.numRows, false);
B.reshape(A.numRows, 1, false);
}
private void setupA_and_B( DMatrixRMaj K,
List homographies,
List observations ) {
final int N = observations.size();
// image center in pixels
double u0 = K.get(0, 2); // image center x-coordinate
double v0 = K.get(1, 2); // image center y-coordinate
// projected predicted
var projCalibrated = new Point2D_F64();
var projPixel = new Point2D_F64();
int pointIndex = 0;
for (int indexObs = 0; indexObs < N; indexObs++) {
DMatrixRMaj H = homographies.get(indexObs);
CalibrationObservation set = observations.get(indexObs);
List layout = layouts.get(set.target);
for (int i = 0; i < set.size(); i++) {
int gridIndex = set.get(i).index;
Point2D_F64 obsPixel = set.get(i).p;
// location of grid point in world coordinate (x,y,0) assume z=0
Point2D_F64 gridPt = layout.get(gridIndex);
// compute the predicted location of the point in calibrated units
GeometryMath_F64.mult(H, gridPt, projCalibrated);
// compute the predicted location in (uncalibrated) pixels
GeometryMath_F64.mult(K, projCalibrated, projPixel);
// construct the matrices
double r2 = projCalibrated.x*projCalibrated.x + projCalibrated.y*projCalibrated.y;
double a = 1.0;
for (int j = 0; j < X.numRows; j++) {
a *= r2;
A.set(pointIndex*2 + 0, j, (projPixel.x - u0)*a);
A.set(pointIndex*2 + 1, j, (projPixel.y - v0)*a);
}
// observed location
B.set(pointIndex*2 + 0, 0, obsPixel.x - projPixel.x);
B.set(pointIndex*2 + 1, 0, obsPixel.y - projPixel.y);
pointIndex++;
}
}
}
/**
* Returns radial distortion parameters.
*
* @return radial distortion parameters.
*/
public double[] getParameters() {
return X.data;
}
}