|
| 1 | +/* Copyright (c) 2022, Gonzalo Ferrer |
| 2 | + * |
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | + * you may not use this file except in compliance with the License. |
| 5 | + * You may obtain a copy of the License at |
| 6 | + * |
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | + * |
| 9 | + * Unless required by applicable law or agreed to in writing, software |
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | + * See the License for the specific language governing permissions and |
| 13 | + * limitations under the License. |
| 14 | + * |
| 15 | + * |
| 16 | + * EigenFactorPlaneDenseHomog.cpp |
| 17 | + * |
| 18 | + * Created on: Aug 23, 2023 |
| 19 | + * Author: Gonzalo Ferrer |
| 20 | + * g.ferrer@skoltech.ru |
| 21 | + * Mobile Robotics Lab. |
| 22 | + */ |
| 23 | + |
| 24 | + |
| 25 | +#include "mrob/factors/EigenFactorPlaneDenseHomog.hpp" |
| 26 | + |
| 27 | +#include <iostream> |
| 28 | +#include <Eigen/Eigenvalues> |
| 29 | +#include "mrob/SE3.hpp" |
| 30 | +#include "mrob/utils_lie_differentiation.hpp" |
| 31 | + |
| 32 | +using namespace mrob; |
| 33 | + |
| 34 | +EigenFactorPlaneDenseHomog::EigenFactorPlaneDenseHomog(Factor::robustFactorType robust_type): |
| 35 | + EigenFactorPlaneBase(robust_type) |
| 36 | +{ |
| 37 | +} |
| 38 | + |
| 39 | +void EigenFactorPlaneDenseHomog::evaluate_residuals() |
| 40 | +{ |
| 41 | + this->estimate_plane(); |
| 42 | +} |
| 43 | + |
| 44 | +void EigenFactorPlaneDenseHomog::evaluate_jacobians() |
| 45 | +{ |
| 46 | + // Assumes residuals evaluated beforehand |
| 47 | + J_.clear(); |
| 48 | + H_.clear(); |
| 49 | + gradQ_xi_times_pi_.clear(); |
| 50 | + for (auto &Qt: Q_) |
| 51 | + { |
| 52 | + Mat61 jacobian = Mat61::Zero(); |
| 53 | + Mat6 hessian = Mat6::Zero(); |
| 54 | + |
| 55 | + // calculate gradient |
| 56 | + Mat<4,6> grad; |
| 57 | + grad = gradient_Q_x_pi(Qt,planeEstimation_); |
| 58 | + gradQ_xi_times_pi_.push_back(grad); |
| 59 | + jacobian = grad.transpose() * planeEstimation_; |
| 60 | + |
| 61 | + // calculate hessian ONLY Upper Trianlar view |
| 62 | + //tested: pi_t_x_hessian_Q_x_pi(),coincident with EFcenter-element-by-element implementation |
| 63 | + Mat6 pi_t_G_time_Q_grad; |
| 64 | + pi_t_G_time_Q_grad.triangularView<Eigen::Upper>() = 2.0*pi_t_times_lie_generatives(planeEstimation_)*grad; |
| 65 | + |
| 66 | + // Cross term dpi * dQ*pi, where dpi/dxi_i = Q^-1 dQ/dxi_i pi. Due to symetry, both temrs are equal and sum (-> 2.0*) |
| 67 | + Mat6 grad_pi_time_Q_grad; |
| 68 | + grad_pi_time_Q_grad.triangularView<Eigen::Upper>() = 2.0*grad.transpose()*Q_inv_no_kernel_*grad; |
| 69 | + |
| 70 | + // sum of all terms |
| 71 | + hessian.triangularView<Eigen::Upper>() = |
| 72 | + pi_t_x_hessian_Q_x_pi(Qt,planeEstimation_) + |
| 73 | + grad_pi_time_Q_grad + // crosterm due to plane x dQ |
| 74 | + pi_t_G_time_Q_grad; //slihglty better than EFcenter |
| 75 | + |
| 76 | + |
| 77 | + J_.push_back(jacobian); |
| 78 | + H_.push_back(hessian); |
| 79 | + // need to store |
| 80 | + //std::cout << "Jacobian =\n" << hessian <<std::endl; |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +void EigenFactorPlaneDenseHomog::evaluate_chi2() |
| 85 | +{ |
| 86 | + // Point 2 plane exact error requires chi2 = pi' Q pi |
| 87 | + chi2_ = planeEstimation_.dot( accumulatedQ_ * planeEstimation_ ); |
| 88 | + //std::cout << "plane = " << planeEstimation_ << "\n Q = \n" << accumulatedQ_ << std::endl; |
| 89 | +} |
| 90 | + |
| 91 | +void EigenFactorPlaneDenseHomog::estimate_plane() |
| 92 | +{ |
| 93 | + calculate_all_matrices_S(); |
| 94 | + calculate_all_matrices_Q(); |
| 95 | + |
| 96 | + // Check for empty EF (no points) |
| 97 | + if (accumulatedQ_.sum()< 1e-4) |
| 98 | + { |
| 99 | + planeEstimation_.setZero(); |
| 100 | + Q_inv_no_kernel_.setZero(); |
| 101 | + return; |
| 102 | + } |
| 103 | + |
| 104 | + |
| 105 | + // Center the plane requires a transformation (translation) such that |
| 106 | + // pi_centered = [n, 0], such that T^{-\top} * pi_centered = pi, |
| 107 | + // This only hold for when T^{-\top} = [I, -n d]. |
| 108 | + // and n d = - sum{p} / N = -E{x} from the centered calculation of a plane |
| 109 | + Mat4 Tcenter = Mat4::Identity(); |
| 110 | + Tcenter.topRightCorner<3,1>() = -accumulatedQ_.topRightCorner<3,1>()/accumulatedQ_(3,3); |
| 111 | + //std::cout << "T center = " << Tcenter << std::endl; |
| 112 | + |
| 113 | + |
| 114 | + Mat4 accumulatedCenterQ; |
| 115 | + accumulatedCenterQ = Tcenter * accumulatedQ_ * Tcenter.transpose(); |
| 116 | + |
| 117 | + |
| 118 | + // Only needs Lower View from Q (https://eigen.tuxfamily.org/dox/classEigen_1_1SelfAdjointEigenSolver.html) |
| 119 | + Eigen::SelfAdjointEigenSolver<Mat3> es; |
| 120 | + es.computeDirect(accumulatedCenterQ.topLeftCorner<3,3>()); |
| 121 | + // This plane center is more stable when calcualating a solution than its counterpart in 4d |
| 122 | + // Most likely it is how the error leaks from the distance componet and breaks the normal component, |
| 123 | + // that needs to be projected (regenerated) on the 4x4 case. this way, we make sure it is a unit vector by construction. |
| 124 | + // Right after the calcuation, we convert back to the correct reference frame. |
| 125 | + Mat41 planeEstimationCenter; |
| 126 | + planeEstimationCenter.head<3>() = es.eigenvectors().col(0); |
| 127 | + planeEstimationCenter(3) = 0.0; |
| 128 | + |
| 129 | + |
| 130 | + planeEstimation_ = Tcenter.transpose() * planeEstimationCenter; |
| 131 | + |
| 132 | + // Calcualte almost inverse of Q for later derivatives: |
| 133 | + // option 1, full inverse: slightly inacurate solution. |
| 134 | + //Q_inv_no_kernel_ = accumulatedQ_.inverse(); |
| 135 | + |
| 136 | + // Option 2. Direct eig decomposition and inverse |
| 137 | + Eigen::SelfAdjointEigenSolver<Mat4> es4; |
| 138 | + es4.compute(accumulatedQ_); |
| 139 | + matData_t lambda_plane = es4.eigenvalues()(0); |
| 140 | + Mat4 other_eigenvectors, other_eigenvectors_multiplied; |
| 141 | + other_eigenvectors = es4.eigenvectors(); |
| 142 | + other_eigenvectors.col(0) *= 0.0; |
| 143 | + //std::cout << "other eignevect \n" << other_eigenvectors <<std::endl; |
| 144 | + other_eigenvectors_multiplied.col(0) = 0.0 * other_eigenvectors.col(0); |
| 145 | + other_eigenvectors_multiplied.col(1) = 1.0/(lambda_plane - es4.eigenvalues()(1)) * other_eigenvectors.col(1); |
| 146 | + other_eigenvectors_multiplied.col(2) = 1.0/(lambda_plane - es4.eigenvalues()(2)) * other_eigenvectors.col(2); |
| 147 | + other_eigenvectors_multiplied.col(3) = 1.0/(lambda_plane - es4.eigenvalues()(3)) * other_eigenvectors.col(3); |
| 148 | + //std::cout << "other eignevect mult \n" << other_eigenvectors_multiplied <<std::endl; |
| 149 | + //std::cout << "Q_inv_ 4x4 inverse =\n" << other_eigenvectors * other_eigenvectors_multiplied.transpose() <<std::endl; |
| 150 | + Q_inv_no_kernel_ = other_eigenvectors * other_eigenvectors_multiplied.transpose(); |
| 151 | + //std::cout << "Difference =\n" << (other_eigenvectors * other_eigenvectors_multiplied.transpose()-Q_inv_no_kernel_).squaredNorm() <<std::endl; |
| 152 | + |
| 153 | + |
| 154 | + |
| 155 | +} |
| 156 | + |
| 157 | +bool EigenFactorPlaneDenseHomog::get_hessian(MatRef H, mrob::factor_id_t id_i, mrob::factor_id_t id_j) const |
| 158 | +{ |
| 159 | + // this condition should always hold since ids are taken from neubouring nodes, but in case useage changes. |
| 160 | + if (reverseNodeIds_.count(id_i) == 0 || reverseNodeIds_.count(id_j) == 0) |
| 161 | + return false; |
| 162 | + |
| 163 | + uint_t localId1 = reverseNodeIds_.at(id_i); |
| 164 | + uint_t localId2 = reverseNodeIds_.at(id_j); |
| 165 | + if(id_i == id_j) |
| 166 | + { |
| 167 | + H = H_.at(localId1); |
| 168 | + return true; |
| 169 | + } |
| 170 | + else |
| 171 | + { |
| 172 | + // cross terms as |
| 173 | + H = 2.0*gradQ_xi_times_pi_.at(localId1).transpose() * Q_inv_no_kernel_* gradQ_xi_times_pi_.at(localId2); |
| 174 | + return true; |
| 175 | + } |
| 176 | +} |
0 commit comments