blob: db6f95a198436f1d470330b5947413814c596cb4 [file] [log] [blame]
// Ceres Solver - A fast non-linear least squares minimizer
// Copyright 2022 Google Inc. All rights reserved.
// http://ceres-solver.org/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Author: sameeragarwal@google.com (Sameer Agarwal)
#include "ceres/local_parameterization.h"
#include <algorithm>
#include "Eigen/Geometry"
#include "ceres/internal/eigen.h"
#include "ceres/internal/fixed_array.h"
#include "ceres/internal/householder_vector.h"
#include "ceres/rotation.h"
#include "glog/logging.h"
namespace ceres {
using std::vector;
LocalParameterization::~LocalParameterization() = default;
bool LocalParameterization::MultiplyByJacobian(const double* x,
const int num_rows,
const double* global_matrix,
double* local_matrix) const {
if (LocalSize() == 0) {
return true;
}
Matrix jacobian(GlobalSize(), LocalSize());
if (!ComputeJacobian(x, jacobian.data())) {
return false;
}
MatrixRef(local_matrix, num_rows, LocalSize()) =
ConstMatrixRef(global_matrix, num_rows, GlobalSize()) * jacobian;
return true;
}
IdentityParameterization::IdentityParameterization(const int size)
: size_(size) {
CHECK_GT(size, 0);
}
bool IdentityParameterization::Plus(const double* x,
const double* delta,
double* x_plus_delta) const {
VectorRef(x_plus_delta, size_) =
ConstVectorRef(x, size_) + ConstVectorRef(delta, size_);
return true;
}
bool IdentityParameterization::ComputeJacobian(const double* x,
double* jacobian) const {
MatrixRef(jacobian, size_, size_).setIdentity();
return true;
}
bool IdentityParameterization::MultiplyByJacobian(const double* x,
const int num_cols,
const double* global_matrix,
double* local_matrix) const {
std::copy(
global_matrix, global_matrix + num_cols * GlobalSize(), local_matrix);
return true;
}
SubsetParameterization::SubsetParameterization(
int size, const vector<int>& constant_parameters)
: local_size_(size - constant_parameters.size()), constancy_mask_(size, 0) {
if (constant_parameters.empty()) {
return;
}
vector<int> constant = constant_parameters;
std::sort(constant.begin(), constant.end());
CHECK_GE(constant.front(), 0) << "Indices indicating constant parameter must "
"be greater than equal to zero.";
CHECK_LT(constant.back(), size)
<< "Indices indicating constant parameter must be less than the size "
<< "of the parameter block.";
CHECK(std::adjacent_find(constant.begin(), constant.end()) == constant.end())
<< "The set of constant parameters cannot contain duplicates";
for (int parameter : constant_parameters) {
constancy_mask_[parameter] = 1;
}
}
bool SubsetParameterization::Plus(const double* x,
const double* delta,
double* x_plus_delta) const {
const int global_size = GlobalSize();
for (int i = 0, j = 0; i < global_size; ++i) {
if (constancy_mask_[i]) {
x_plus_delta[i] = x[i];
} else {
x_plus_delta[i] = x[i] + delta[j++];
}
}
return true;
}
bool SubsetParameterization::ComputeJacobian(const double* x,
double* jacobian) const {
if (local_size_ == 0) {
return true;
}
const int global_size = GlobalSize();
MatrixRef m(jacobian, global_size, local_size_);
m.setZero();
for (int i = 0, j = 0; i < global_size; ++i) {
if (!constancy_mask_[i]) {
m(i, j++) = 1.0;
}
}
return true;
}
bool SubsetParameterization::MultiplyByJacobian(const double* x,
const int num_cols,
const double* global_matrix,
double* local_matrix) const {
if (local_size_ == 0) {
return true;
}
const int global_size = GlobalSize();
for (int col = 0; col < num_cols; ++col) {
for (int i = 0, j = 0; i < global_size; ++i) {
if (!constancy_mask_[i]) {
local_matrix[col * local_size_ + j++] =
global_matrix[col * global_size + i];
}
}
}
return true;
}
bool QuaternionParameterization::Plus(const double* x,
const double* delta,
double* x_plus_delta) const {
const double norm_delta =
sqrt(delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]);
if (norm_delta > 0.0) {
const double sin_delta_by_delta = (sin(norm_delta) / norm_delta);
double q_delta[4];
q_delta[0] = cos(norm_delta);
q_delta[1] = sin_delta_by_delta * delta[0];
q_delta[2] = sin_delta_by_delta * delta[1];
q_delta[3] = sin_delta_by_delta * delta[2];
QuaternionProduct(q_delta, x, x_plus_delta);
} else {
for (int i = 0; i < 4; ++i) {
x_plus_delta[i] = x[i];
}
}
return true;
}
bool QuaternionParameterization::ComputeJacobian(const double* x,
double* jacobian) const {
// clang-format off
jacobian[0] = -x[1]; jacobian[1] = -x[2]; jacobian[2] = -x[3];
jacobian[3] = x[0]; jacobian[4] = x[3]; jacobian[5] = -x[2];
jacobian[6] = -x[3]; jacobian[7] = x[0]; jacobian[8] = x[1];
jacobian[9] = x[2]; jacobian[10] = -x[1]; jacobian[11] = x[0];
// clang-format on
return true;
}
bool EigenQuaternionParameterization::Plus(const double* x_ptr,
const double* delta,
double* x_plus_delta_ptr) const {
Eigen::Map<Eigen::Quaterniond> x_plus_delta(x_plus_delta_ptr);
Eigen::Map<const Eigen::Quaterniond> x(x_ptr);
const double norm_delta =
sqrt(delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]);
if (norm_delta > 0.0) {
const double sin_delta_by_delta = sin(norm_delta) / norm_delta;
// Note, in the constructor w is first.
Eigen::Quaterniond delta_q(cos(norm_delta),
sin_delta_by_delta * delta[0],
sin_delta_by_delta * delta[1],
sin_delta_by_delta * delta[2]);
x_plus_delta = delta_q * x;
} else {
x_plus_delta = x;
}
return true;
}
bool EigenQuaternionParameterization::ComputeJacobian(const double* x,
double* jacobian) const {
// clang-format off
jacobian[0] = x[3]; jacobian[1] = x[2]; jacobian[2] = -x[1];
jacobian[3] = -x[2]; jacobian[4] = x[3]; jacobian[5] = x[0];
jacobian[6] = x[1]; jacobian[7] = -x[0]; jacobian[8] = x[3];
jacobian[9] = -x[0]; jacobian[10] = -x[1]; jacobian[11] = -x[2];
// clang-format on
return true;
}
HomogeneousVectorParameterization::HomogeneousVectorParameterization(int size)
: size_(size) {
CHECK_GT(size_, 1) << "The size of the homogeneous vector needs to be "
<< "greater than 1.";
}
bool HomogeneousVectorParameterization::Plus(const double* x_ptr,
const double* delta_ptr,
double* x_plus_delta_ptr) const {
ConstVectorRef x(x_ptr, size_);
ConstVectorRef delta(delta_ptr, size_ - 1);
VectorRef x_plus_delta(x_plus_delta_ptr, size_);
const double norm_delta = delta.norm();
if (norm_delta == 0.0) {
x_plus_delta = x;
return true;
}
// Map the delta from the minimum representation to the over parameterized
// homogeneous vector. See section A6.9.2 on page 624 of Hartley & Zisserman
// (2nd Edition) for a detailed description. Note there is a typo on Page
// 625, line 4 so check the book errata.
const double norm_delta_div_2 = 0.5 * norm_delta;
const double sin_delta_by_delta =
std::sin(norm_delta_div_2) / norm_delta_div_2;
Vector y(size_);
y.head(size_ - 1) = 0.5 * sin_delta_by_delta * delta;
y(size_ - 1) = std::cos(norm_delta_div_2);
Vector v(size_);
double beta;
// NOTE: The explicit template arguments are needed here because
// ComputeHouseholderVector is templated and some versions of MSVC
// have trouble deducing the type of v automatically.
internal::ComputeHouseholderVector<ConstVectorRef, double, Eigen::Dynamic>(
x, &v, &beta);
// Apply the delta update to remain on the unit sphere. See section A6.9.3
// on page 625 of Hartley & Zisserman (2nd Edition) for a detailed
// description.
x_plus_delta = x.norm() * (y - v * (beta * (v.transpose() * y)));
return true;
}
bool HomogeneousVectorParameterization::ComputeJacobian(
const double* x_ptr, double* jacobian_ptr) const {
ConstVectorRef x(x_ptr, size_);
MatrixRef jacobian(jacobian_ptr, size_, size_ - 1);
Vector v(size_);
double beta;
// NOTE: The explicit template arguments are needed here because
// ComputeHouseholderVector is templated and some versions of MSVC
// have trouble deducing the type of v automatically.
internal::ComputeHouseholderVector<ConstVectorRef, double, Eigen::Dynamic>(
x, &v, &beta);
// The Jacobian is equal to J = 0.5 * H.leftCols(size_ - 1) where H is the
// Householder matrix (H = I - beta * v * v').
for (int i = 0; i < size_ - 1; ++i) {
jacobian.col(i) = -0.5 * beta * v(i) * v;
jacobian.col(i)(i) += 0.5;
}
jacobian *= x.norm();
return true;
}
bool ProductParameterization::Plus(const double* x,
const double* delta,
double* x_plus_delta) const {
int x_cursor = 0;
int delta_cursor = 0;
for (const auto& param : local_params_) {
if (!param->Plus(
x + x_cursor, delta + delta_cursor, x_plus_delta + x_cursor)) {
return false;
}
delta_cursor += param->LocalSize();
x_cursor += param->GlobalSize();
}
return true;
}
bool ProductParameterization::ComputeJacobian(const double* x,
double* jacobian_ptr) const {
MatrixRef jacobian(jacobian_ptr, GlobalSize(), LocalSize());
jacobian.setZero();
internal::FixedArray<double> buffer(buffer_size_);
int x_cursor = 0;
int delta_cursor = 0;
for (const auto& param : local_params_) {
const int local_size = param->LocalSize();
const int global_size = param->GlobalSize();
if (!param->ComputeJacobian(x + x_cursor, buffer.data())) {
return false;
}
jacobian.block(x_cursor, delta_cursor, global_size, local_size) =
MatrixRef(buffer.data(), global_size, local_size);
delta_cursor += local_size;
x_cursor += global_size;
}
return true;
}
} // namespace ceres