Program Listing for File anchored-inverse-depth-error-term-inl.h

Return to documentation for file (algorithms/ceres-error-terms/include/ceres-error-terms/anchored-inverse-depth-error-term-inl.h)

#ifndef CERES_ERROR_TERMS_ANCHORED_INVERSE_DEPTH_ERROR_TERM_INL_H_
#define CERES_ERROR_TERMS_ANCHORED_INVERSE_DEPTH_ERROR_TERM_INL_H_

#include <aslam/cameras/camera.h>
#include <maplab-common/geometry.h>
#include <maplab-common/quaternion-math.h>

#include "ceres-error-terms/common.h"

namespace ceres_error_terms {

template <visual::VisualErrorType error_type>
struct is_valid_error_type {
  static const bool value =
      (error_type == visual::VisualErrorType::kLocalKeyframe ||
       error_type == visual::VisualErrorType::kLocalMission ||
       error_type == visual::VisualErrorType::kGlobal);
};

template <
    typename CameraType, typename DistortionType,
    visual::VisualErrorType error_term_type>
bool AIDReprojectionError<CameraType, DistortionType, error_term_type>::
    Evaluate(
        double const* const* parameters, double* residuals,
        double** jacobians) const {
  // Coordinate frames:
  //  M = mission of the keyframe vertex, expressed in G
  //  A = Anchor point of landmark (position of first observation), expressed in
  //  M
  //  I = IMU position of the keyframe vertex, expressed in M
  //  C = Camera position, expressed in I

  // TODO(burrimi): is this still needed?
  static_assert(
      is_valid_error_type<error_term_type>::value,
      "Visual error type must be a valid enum value.");

  // Unpack parameter blocks.
  Eigen::Map<const Eigen::Vector3d> M_p_M_A(parameters[kIdxAnchorPoint]);
  Eigen::Map<const Eigen::Quaterniond> A_u_A_L(parameters[kIdxUnitVector]);
  const double inverse_depth_A = *parameters[kIdxInverseDepth];

  Eigen::Map<const Eigen::Quaterniond> q_M_I(parameters[kIdxImuQ]);
  Eigen::Map<const Eigen::Vector3d> M_p_M_I(parameters[kIdxImuP]);
  Eigen::Map<const Eigen::Quaterniond> q_C_I(parameters[kIdxCameraToImuQ]);
  Eigen::Map<const Eigen::Vector3d> C_p_C_I(parameters[kIdxCameraToImuP]);
  Eigen::Map<const Eigen::Matrix<double, CameraType::parameterCount(), 1>>
      intrinsics_map(parameters[kIdxCameraIntrinsics]);
  Eigen::Matrix<double, Eigen::Dynamic, 1> distortion_map;

  if (DistortionType::parameterCount() > 0) {
    distortion_map = Eigen::Map<
        const Eigen::Matrix<double, DistortionType::parameterCount(), 1>>(
        parameters[kIdxCameraDistortion]);
  }

  // Jacobian of landmark pose in camera system w.r.t. keyframe pose
  Eigen::Matrix<double, visual::kPositionBlockSize, 3> J_p_C_fi_wrt_q_M_I;
  Eigen::Matrix<double, visual::kPositionBlockSize, 3> J_p_C_fi_wrt_p_M_I;

  // Jacobian of landmark pose in camera system w.r.t. cam-to-IMU transformation
  Eigen::Matrix<double, visual::kPositionBlockSize, 3> J_p_C_fi_wrt_q_C_I;
  Eigen::Matrix<double, visual::kPositionBlockSize, 3> J_p_C_fi_wrt_p_C_I;

  // Jacobians w.r.t. camera intrinsics and distortion coefficients
  JacobianWrtIntrinsicsType J_keypoint_wrt_intrinsics(
      visual::kResidualSize, CameraType::parameterCount());
  JacobianWrtDistortionType J_keypoint_wrt_distortion(
      visual::kResidualSize, DistortionType::parameterCount());

  // To avoid division by zero (landmarks at infinity) we calculate a scaled
  // version of p_C_fi.
  // This is identical to using the non scaled value due to the projection to
  // the image plane.
  // C_p_CL_scaled = C_p_CL * inverse_depth_A =
  //   R_CI * R_MI' * (inverse_depth_A * (M_p_MA - M_p_MI) + A_u_AL) +
  //   inverse_depth_A * C_p_CI

  // Do the calculations and cache some intermediate steps for the Jacobians
  const Eigen::Matrix3d R_C_M = (q_C_I * q_M_I.inverse()).toRotationMatrix();
  const Eigen::Vector3d M_p_I_A = M_p_M_A - M_p_M_I;
  const Eigen::Vector3d A_u_A_L_normal_vector = Unit3::GetNormalVector(A_u_A_L);

  // Note: R_MA is identity and left out.
  const Eigen::Vector3d M_p_I_L_scaled =
      inverse_depth_A * M_p_I_A + A_u_A_L_normal_vector;
  const Eigen::Vector3d C_p_I_L_scaled = R_C_M * M_p_I_L_scaled;
  // Finally we get the landmark in the camera frame
  const Eigen::Vector3d C_p_C_L_scaled =
      C_p_I_L_scaled + inverse_depth_A * C_p_C_I;

  // TODO(burrimi): Is this needed in the future?
  //  if (error_term_type == visual::VisualErrorType::kLocalKeyframe) {
  //    // The landmark baseframe is in fact our keyframe.
  //  } else {
  //  }

  VisualJacobianType J_keypoint_wrt_p_C_fi;
  // TODO(schneith): is this copy needed?
  Eigen::VectorXd intrinsics = intrinsics_map;
  Eigen::VectorXd distortion = distortion_map;

  // Only evaluate the jacobian if requested.
  VisualJacobianType* J_keypoint_wrt_p_C_fi_ptr = nullptr;
  if (jacobians) {
    J_keypoint_wrt_p_C_fi_ptr = &J_keypoint_wrt_p_C_fi;
  }
  JacobianWrtIntrinsicsType* J_keypoint_wrt_intrinsics_ptr = nullptr;
  if (jacobians && jacobians[kIdxCameraIntrinsics]) {
    J_keypoint_wrt_intrinsics_ptr = &J_keypoint_wrt_intrinsics;
  }
  JacobianWrtDistortionType* J_keypoint_wrt_distortion_ptr = nullptr;
  if (jacobians && DistortionType::parameterCount() > 0 &&
      jacobians[kIdxCameraDistortion]) {
    J_keypoint_wrt_distortion_ptr = &J_keypoint_wrt_distortion;
  }

  Eigen::Vector2d reprojected_landmark;
  aslam::ProjectionResult projection_result;
  projection_result = camera_ptr_->project3Functional(
      C_p_C_L_scaled, &intrinsics, &distortion, &reprojected_landmark,
      J_keypoint_wrt_p_C_fi_ptr, J_keypoint_wrt_intrinsics_ptr,
      J_keypoint_wrt_distortion_ptr);

  // TODO(schneith): This is not really required for the optimization!
  if (!projection_result.isKeypointVisible()) {
    VLOG(200) << "Projection of point failed " << projection_result;
  }

  if (jacobians) {
    // Eigen quaternion parameterization is used
    EigenQuaternionParameterization quat_parameterization;
    Unit3Parameterization unit3_parameterization;

    // Jacobian of landmark pose in camera system w.r.t. inverse depth feature
    // parameterization
    const Eigen::Matrix3d J_p_C_fi_wrt_p_M_A = inverse_depth_A * R_C_M;
    Eigen::Matrix<double, visual::kPositionBlockSize, Unit3::kLocalSize>
        J_p_C_fi_wrt_u_A_L = -R_C_M * common::skew(A_u_A_L_normal_vector) *
                             Unit3::GetBasis(A_u_A_L);
    const Eigen::Vector3d J_p_C_fi_wrt_inv_depth = R_C_M * M_p_I_A + C_p_C_I;

    // Jacobian of landmark pose in camera system w.r.t. inverse depth feature
    // parameterization
    const Eigen::Matrix3d J_p_C_fi_wrt_q_M_I =
        R_C_M * common::skew(M_p_I_L_scaled);
    const Eigen::Matrix3d J_p_C_fi_wrt_p_M_I = -J_p_C_fi_wrt_p_M_A;

    // Jacobian of landmark pose in camera system w.r.t. inverse depth feature
    // parameterization
    const Eigen::Matrix3d J_p_C_fi_wrt_q_C_I = -common::skew(C_p_I_L_scaled);
    const Eigen::Matrix3d J_p_C_fi_wrt_p_C_I =
        inverse_depth_A * Eigen::Matrix3d::Identity();

    // Jacobian w.r.t. landmark position expressed in landmark base frame.
    if (jacobians[kIdxAnchorPoint]) {
      Eigen::Map<PositionJacobian> J(jacobians[kIdxAnchorPoint]);
      J = J_keypoint_wrt_p_C_fi * J_p_C_fi_wrt_p_M_A *
          this->pixel_sigma_inverse_;
    }

    // Jacobian w.r.t. landmark position expressed in landmark base frame.
    if (jacobians[kIdxUnitVector]) {
      // we need to have 3x4 matrix that will be a jacobian of 2-element
      // unit3 w.r.t. quaternion; note that ComputeJacobians needs row major
      Unit3Parameterization::LiftJacobian lift_jacobian;
      unit3_parameterization.ComputeLiftJacobian(
          parameters[kIdxUnitVector], lift_jacobian.data());
      Eigen::Map<Unit3Jacobian> J(jacobians[kIdxUnitVector]);
      J = J_keypoint_wrt_p_C_fi * J_p_C_fi_wrt_u_A_L * lift_jacobian *
          this->pixel_sigma_inverse_;
    }

    // Jacobian w.r.t. landmark position expressed in landmark base frame.
    if (jacobians[kIdxInverseDepth]) {
      Eigen::Map<InverseDepthJacobian> J(jacobians[kIdxInverseDepth]);
      J = J_keypoint_wrt_p_C_fi * J_p_C_fi_wrt_inv_depth *
          this->pixel_sigma_inverse_;
    }

    // Jacobian w.r.t. IMU pose expressed in keyframe mission base frame.
    if (jacobians[kIdxImuQ]) {
      // we need to have 3x4 matrix that will be a jacobian of 3-element
      // rotation w.r.t. quaternion; note that ComputeJacobians needs row major
      EigenQuaternionParameterization::LiftJacobian lift_jacobian;
      quat_parameterization.ComputeLiftJacobian(
          parameters[kIdxImuQ], lift_jacobian.data());
      Eigen::Map<OrientationJacobian> J(jacobians[kIdxImuQ]);

      J = J_keypoint_wrt_p_C_fi * J_p_C_fi_wrt_q_M_I * lift_jacobian *
          this->pixel_sigma_inverse_;
    }

    if (jacobians[kIdxImuP]) {
      Eigen::Map<PositionJacobian> J(jacobians[kIdxImuP]);
      J = J_keypoint_wrt_p_C_fi * J_p_C_fi_wrt_p_M_I *
          this->pixel_sigma_inverse_;
    }

    // Jacobian w.r.t. camera-to-IMU orientation.
    if (jacobians[kIdxCameraToImuQ]) {
      EigenQuaternionParameterization::LiftJacobian lift_jacobian;
      quat_parameterization.ComputeLiftJacobian(
          parameters[kIdxCameraToImuQ], lift_jacobian.data());
      Eigen::Map<OrientationJacobian> J(jacobians[kIdxCameraToImuQ]);
      J = J_keypoint_wrt_p_C_fi * J_p_C_fi_wrt_q_C_I * lift_jacobian *
          this->pixel_sigma_inverse_;
    }

    // Jacobian w.r.t. camera-to-IMU position.
    if (jacobians[kIdxCameraToImuP]) {
      Eigen::Map<PositionJacobian> J(jacobians[kIdxCameraToImuP]);
      J = J_keypoint_wrt_p_C_fi * J_p_C_fi_wrt_p_C_I *
          this->pixel_sigma_inverse_;
    }

    // Jacobian w.r.t. intrinsics.
    if (jacobians[kIdxCameraIntrinsics]) {
      Eigen::Map<IntrinsicsJacobian> J(jacobians[kIdxCameraIntrinsics]);
      J = J_keypoint_wrt_intrinsics * this->pixel_sigma_inverse_;
    }

    // Jacobian w.r.t. distortion.
    if (DistortionType::parameterCount() > 0 &&
        jacobians[kIdxCameraDistortion]) {
      Eigen::Map<DistortionJacobian> J(
          jacobians[kIdxCameraDistortion], visual::kResidualSize,
          DistortionType::parameterCount());
      J = J_keypoint_wrt_distortion * this->pixel_sigma_inverse_;
    }
  }

  // Compute residuals.
  Eigen::Map<Eigen::Vector2d> residual(residuals);
  residual = (reprojected_landmark - measurement_) * this->pixel_sigma_inverse_;

  return true;
}

}  // namespace ceres_error_terms

#endif  // CERES_ERROR_TERMS_ANCHORED_INVERSE_DEPTH_ERROR_TERM_INL_H_