|
Packit |
ea1746 |
.. default-domain:: cpp
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. cpp:namespace:: ceres
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. _chapter-interfacing_with_automatic_differentiation:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
Interfacing with Automatic Differentiation
|
|
Packit |
ea1746 |
==========================================
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
Automatic differentiation is straightforward to use in cases where an
|
|
Packit |
ea1746 |
explicit expression for the cost function is available. But this is
|
|
Packit |
ea1746 |
not always possible. Often one has to interface with external routines
|
|
Packit |
ea1746 |
or data. In this chapter we will consider a number of different ways
|
|
Packit |
ea1746 |
of doing so.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
To do this, we will consider the problem of finding parameters
|
|
Packit |
ea1746 |
:math:`\theta` and :math:`t` that solve an optimization problem of the
|
|
Packit |
ea1746 |
form:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. math::
|
|
Packit |
ea1746 |
\min & \quad \sum_i \left \|y_i - f\left (\|q_{i}\|^2\right) q_i
|
|
Packit |
ea1746 |
\right \|^2\\
|
|
Packit |
ea1746 |
\text{such that} & \quad q_i = R(\theta) x_i + t
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
Here, :math:`R` is a two dimensional rotation matrix parameterized
|
|
Packit |
ea1746 |
using the angle :math:`\theta` and :math:`t` is a two dimensional
|
|
Packit |
ea1746 |
vector. :math:`f` is an external distortion function.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
We begin by considering the case, where we have a templated function
|
|
Packit |
ea1746 |
:code:`TemplatedComputeDistortion` that can compute the function
|
|
Packit |
ea1746 |
:math:`f`. Then the implementation of the corresponding residual
|
|
Packit |
ea1746 |
functor is straightforward and will look as follows:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. code-block:: c++
|
|
Packit |
ea1746 |
:emphasize-lines: 21
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
template <typename T> T TemplatedComputeDistortion(const T r2) {
|
|
Packit |
ea1746 |
const double k1 = 0.0082;
|
|
Packit |
ea1746 |
const double k2 = 0.000023;
|
|
Packit |
ea1746 |
return 1.0 + k1 * y2 + k2 * r2 * r2;
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
struct Affine2DWithDistortion {
|
|
Packit |
ea1746 |
Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
|
|
Packit |
ea1746 |
x[0] = x_in[0];
|
|
Packit |
ea1746 |
x[1] = x_in[1];
|
|
Packit |
ea1746 |
y[0] = y_in[0];
|
|
Packit |
ea1746 |
y[1] = y_in[1];
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
template <typename T>
|
|
Packit |
ea1746 |
bool operator()(const T* theta,
|
|
Packit |
ea1746 |
const T* t,
|
|
Packit |
ea1746 |
T* residuals) const {
|
|
Packit |
ea1746 |
const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
|
|
Packit |
ea1746 |
const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
|
|
Packit |
ea1746 |
const T f = TemplatedComputeDistortion(q_0 * q_0 + q_1 * q_1);
|
|
Packit |
ea1746 |
residuals[0] = y[0] - f * q_0;
|
|
Packit |
ea1746 |
residuals[1] = y[1] - f * q_1;
|
|
Packit |
ea1746 |
return true;
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
double x[2];
|
|
Packit |
ea1746 |
double y[2];
|
|
Packit |
ea1746 |
};
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
So far so good, but let us now consider three ways of defining
|
|
Packit |
ea1746 |
:math:`f` which are not directly amenable to being used with automatic
|
|
Packit |
ea1746 |
differentiation:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
#. A non-templated function that evaluates its value.
|
|
Packit |
ea1746 |
#. A function that evaluates its value and derivative.
|
|
Packit |
ea1746 |
#. A function that is defined as a table of values to be interpolated.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
We will consider them in turn below.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
A function that returns its value
|
|
Packit |
ea1746 |
----------------------------------
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
Suppose we were given a function :code:`ComputeDistortionValue` with
|
|
Packit |
ea1746 |
the following signature
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. code-block:: c++
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
double ComputeDistortionValue(double r2);
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
that computes the value of :math:`f`. The actual implementation of the
|
|
Packit |
ea1746 |
function does not matter. Interfacing this function with
|
|
Packit |
ea1746 |
:code:`Affine2DWithDistortion` is a three step process:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
1. Wrap :code:`ComputeDistortionValue` into a functor
|
|
Packit |
ea1746 |
:code:`ComputeDistortionValueFunctor`.
|
|
Packit |
ea1746 |
2. Numerically differentiate :code:`ComputeDistortionValueFunctor`
|
|
Packit |
ea1746 |
using :class:`NumericDiffCostFunction` to create a
|
|
Packit |
ea1746 |
:class:`CostFunction`.
|
|
Packit |
ea1746 |
3. Wrap the resulting :class:`CostFunction` object using
|
|
Packit |
ea1746 |
:class:`CostFunctionToFunctor`. The resulting object is a functor
|
|
Packit |
ea1746 |
with a templated :code:`operator()` method, which pipes the
|
|
Packit |
ea1746 |
Jacobian computed by :class:`NumericDiffCostFunction` into the
|
|
Packit |
ea1746 |
approproate :code:`Jet` objects.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
An implementation of the above three steps looks as follows:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. code-block:: c++
|
|
Packit |
ea1746 |
:emphasize-lines: 15,16,17,18,19,20, 29
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
struct ComputeDistortionValueFunctor {
|
|
Packit |
ea1746 |
bool operator()(const double* r2, double* value) const {
|
|
Packit |
ea1746 |
*value = ComputeDistortionValue(r2[0]);
|
|
Packit |
ea1746 |
return true;
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
};
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
struct Affine2DWithDistortion {
|
|
Packit |
ea1746 |
Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
|
|
Packit |
ea1746 |
x[0] = x_in[0];
|
|
Packit |
ea1746 |
x[1] = x_in[1];
|
|
Packit |
ea1746 |
y[0] = y_in[0];
|
|
Packit |
ea1746 |
y[1] = y_in[1];
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
compute_distortion.reset(new ceres::CostFunctionToFunctor<1, 1>(
|
|
Packit |
ea1746 |
new ceres::NumericDiffCostFunction
|
|
Packit |
ea1746 |
ceres::CENTRAL,
|
|
Packit |
ea1746 |
1,
|
|
Packit |
ea1746 |
1>(
|
|
Packit |
ea1746 |
new ComputeDistortionValueFunctor)));
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
template <typename T>
|
|
Packit |
ea1746 |
bool operator()(const T* theta, const T* t, T* residuals) const {
|
|
Packit |
ea1746 |
const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
|
|
Packit |
ea1746 |
const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
|
|
Packit |
ea1746 |
const T r2 = q_0 * q_0 + q_1 * q_1;
|
|
Packit |
ea1746 |
T f;
|
|
Packit |
ea1746 |
(*compute_distortion)(&r2, &f);
|
|
Packit |
ea1746 |
residuals[0] = y[0] - f * q_0;
|
|
Packit |
ea1746 |
residuals[1] = y[1] - f * q_1;
|
|
Packit |
ea1746 |
return true;
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
double x[2];
|
|
Packit |
ea1746 |
double y[2];
|
|
Packit |
ea1746 |
std::unique_ptr<ceres::CostFunctionToFunctor<1, 1> > compute_distortion;
|
|
Packit |
ea1746 |
};
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
A function that returns its value and derivative
|
|
Packit |
ea1746 |
------------------------------------------------
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
Now suppose we are given a function :code:`ComputeDistortionValue`
|
|
Packit |
ea1746 |
thatis able to compute its value and optionally its Jacobian on demand
|
|
Packit |
ea1746 |
and has the following signature:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. code-block:: c++
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
void ComputeDistortionValueAndJacobian(double r2,
|
|
Packit |
ea1746 |
double* value,
|
|
Packit |
ea1746 |
double* jacobian);
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
Again, the actual implementation of the function does not
|
|
Packit |
ea1746 |
matter. Interfacing this function with :code:`Affine2DWithDistortion`
|
|
Packit |
ea1746 |
is a two step process:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
1. Wrap :code:`ComputeDistortionValueAndJacobian` into a
|
|
Packit |
ea1746 |
:class:`CostFunction` object which we call
|
|
Packit |
ea1746 |
:code:`ComputeDistortionFunction`.
|
|
Packit |
ea1746 |
2. Wrap the resulting :class:`ComputeDistortionFunction` object using
|
|
Packit |
ea1746 |
:class:`CostFunctionToFunctor`. The resulting object is a functor
|
|
Packit |
ea1746 |
with a templated :code:`operator()` method, which pipes the
|
|
Packit |
ea1746 |
Jacobian computed by :class:`NumericDiffCostFunction` into the
|
|
Packit |
ea1746 |
approproate :code:`Jet` objects.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
The resulting code will look as follows:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. code-block:: c++
|
|
Packit |
ea1746 |
:emphasize-lines: 21,22, 33
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
class ComputeDistortionFunction : public ceres::SizedCostFunction<1, 1> {
|
|
Packit |
ea1746 |
public:
|
|
Packit |
ea1746 |
virtual bool Evaluate(double const* const* parameters,
|
|
Packit |
ea1746 |
double* residuals,
|
|
Packit |
ea1746 |
double** jacobians) const {
|
|
Packit |
ea1746 |
if (!jacobians) {
|
|
Packit |
ea1746 |
ComputeDistortionValueAndJacobian(parameters[0][0], residuals, NULL);
|
|
Packit |
ea1746 |
} else {
|
|
Packit |
ea1746 |
ComputeDistortionValueAndJacobian(parameters[0][0], residuals, jacobians[0]);
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
return true;
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
};
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
struct Affine2DWithDistortion {
|
|
Packit |
ea1746 |
Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
|
|
Packit |
ea1746 |
x[0] = x_in[0];
|
|
Packit |
ea1746 |
x[1] = x_in[1];
|
|
Packit |
ea1746 |
y[0] = y_in[0];
|
|
Packit |
ea1746 |
y[1] = y_in[1];
|
|
Packit |
ea1746 |
compute_distortion.reset(
|
|
Packit |
ea1746 |
new ceres::CostFunctionToFunctor<1, 1>(new ComputeDistortionFunction));
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
template <typename T>
|
|
Packit |
ea1746 |
bool operator()(const T* theta,
|
|
Packit |
ea1746 |
const T* t,
|
|
Packit |
ea1746 |
T* residuals) const {
|
|
Packit |
ea1746 |
const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
|
|
Packit |
ea1746 |
const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
|
|
Packit |
ea1746 |
const T r2 = q_0 * q_0 + q_1 * q_1;
|
|
Packit |
ea1746 |
T f;
|
|
Packit |
ea1746 |
(*compute_distortion)(&r2, &f);
|
|
Packit |
ea1746 |
residuals[0] = y[0] - f * q_0;
|
|
Packit |
ea1746 |
residuals[1] = y[1] - f * q_1;
|
|
Packit |
ea1746 |
return true;
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
double x[2];
|
|
Packit |
ea1746 |
double y[2];
|
|
Packit |
ea1746 |
std::unique_ptr<ceres::CostFunctionToFunctor<1, 1> > compute_distortion;
|
|
Packit |
ea1746 |
};
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
A function that is defined as a table of values
|
|
Packit |
ea1746 |
-----------------------------------------------
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
The third and final case we will consider is where the function
|
|
Packit |
ea1746 |
:math:`f` is defined as a table of values on the interval :math:`[0,
|
|
Packit |
ea1746 |
100)`, with a value for each integer.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. code-block:: c++
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
vector<double> distortion_values;
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
There are many ways of interpolating a table of values. Perhaps the
|
|
Packit |
ea1746 |
simplest and most common method is linear interpolation. But it is not
|
|
Packit |
ea1746 |
a great idea to use linear interpolation because the interpolating
|
|
Packit |
ea1746 |
function is not differentiable at the sample points.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
A simple (well behaved) differentiable interpolation is the `Cubic
|
|
Packit |
ea1746 |
Hermite Spline
|
|
Packit |
ea1746 |
<http://en.wikipedia.org/wiki/Cubic_Hermite_spline>`_. Ceres Solver
|
|
Packit |
ea1746 |
ships with routines to perform Cubic & Bi-Cubic interpolation that is
|
|
Packit |
ea1746 |
automatic differentiation friendly.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
Using Cubic interpolation requires first constructing a
|
|
Packit |
ea1746 |
:class:`Grid1D` object to wrap the table of values and then
|
|
Packit |
ea1746 |
constructing a :class:`CubicInterpolator` object using it.
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
The resulting code will look as follows:
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
.. code-block:: c++
|
|
Packit |
ea1746 |
:emphasize-lines: 10,11,12,13, 24, 32,33
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
struct Affine2DWithDistortion {
|
|
Packit |
ea1746 |
Affine2DWithDistortion(const double x_in[2],
|
|
Packit |
ea1746 |
const double y_in[2],
|
|
Packit |
ea1746 |
const std::vector<double>& distortion_values) {
|
|
Packit |
ea1746 |
x[0] = x_in[0];
|
|
Packit |
ea1746 |
x[1] = x_in[1];
|
|
Packit |
ea1746 |
y[0] = y_in[0];
|
|
Packit |
ea1746 |
y[1] = y_in[1];
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
grid.reset(new ceres::Grid1D<double, 1>(
|
|
Packit |
ea1746 |
&distortion_values[0], 0, distortion_values.size()));
|
|
Packit |
ea1746 |
compute_distortion.reset(
|
|
Packit |
ea1746 |
new ceres::CubicInterpolator<ceres::Grid1D<double, 1> >(*grid));
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
template <typename T>
|
|
Packit |
ea1746 |
bool operator()(const T* theta,
|
|
Packit |
ea1746 |
const T* t,
|
|
Packit |
ea1746 |
T* residuals) const {
|
|
Packit |
ea1746 |
const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
|
|
Packit |
ea1746 |
const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
|
|
Packit |
ea1746 |
const T r2 = q_0 * q_0 + q_1 * q_1;
|
|
Packit |
ea1746 |
T f;
|
|
Packit |
ea1746 |
compute_distortion->Evaluate(r2, &f);
|
|
Packit |
ea1746 |
residuals[0] = y[0] - f * q_0;
|
|
Packit |
ea1746 |
residuals[1] = y[1] - f * q_1;
|
|
Packit |
ea1746 |
return true;
|
|
Packit |
ea1746 |
}
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
double x[2];
|
|
Packit |
ea1746 |
double y[2];
|
|
Packit |
ea1746 |
std::unique_ptr<ceres::Grid1D<double, 1> > grid;
|
|
Packit |
ea1746 |
std::unique_ptr<ceres::CubicInterpolator<ceres::Grid1D<double, 1> > > compute_distortion;
|
|
Packit |
ea1746 |
};
|
|
Packit |
ea1746 |
|
|
Packit |
ea1746 |
In the above example we used :class:`Grid1D` and
|
|
Packit |
ea1746 |
:class:`CubicInterpolator` to interpolate a one dimensional table of
|
|
Packit |
ea1746 |
values. :class:`Grid2D` combined with :class:`CubicInterpolator` lets
|
|
Packit |
ea1746 |
the user to interpolate two dimensional tables of values. Note that
|
|
Packit |
ea1746 |
neither :class:`Grid1D` or :class:`Grid2D` are limited to scalar
|
|
Packit |
ea1746 |
valued functions, they also work with vector valued functions.
|