NASA Astrobee Robot Software  0.19.1
Flight software for the Astrobee robots operating inside the International Space Station.
robust_smart_projection_pose_factor.h
Go to the documentation of this file.
1 /* Copyright (c) 2017, United States Government, as represented by the
2  * Administrator of the National Aeronautics and Space Administration.
3  *
4  * All rights reserved.
5  *
6  * The Astrobee platform is licensed under the Apache License, Version 2.0
7  * (the "License"); you may not use this file except in compliance with the
8  * License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15  * License for the specific language governing permissions and limitations
16  * under the License.
17  */
18 
19 #ifndef GRAPH_FACTORS_ROBUST_SMART_PROJECTION_POSE_FACTOR_H_
20 #define GRAPH_FACTORS_ROBUST_SMART_PROJECTION_POSE_FACTOR_H_
21 
23 
24 #include <gtsam/slam/SmartProjectionPoseFactor.h>
25 
26 #include <utility>
27 #include <vector>
28 
29 namespace gtsam {
30 template <class CALIBRATION>
31 class RobustSmartProjectionPoseFactor : public SmartProjectionPoseFactor<CALIBRATION>, public CumulativeFactor {
32  typedef PinholePose<CALIBRATION> Camera;
33  typedef SmartFactorBase<Camera> Base;
34  typedef typename Camera::Measurement Z;
35  static const int Dim = traits<Camera>::dimension;
36  static const int ZDim = traits<Z>::dimension;
37 
38  public:
39  // For serialization
41 
48  RobustSmartProjectionPoseFactor(const SharedNoiseModel& sharedNoiseModel, const boost::shared_ptr<CALIBRATION> K,
49  const boost::optional<Pose3> body_P_sensor,
50  const SmartProjectionParams& params = SmartProjectionParams(),
51  const bool rotation_only_fallback = false, const bool robust = true,
52  const double huber_k = 1.0)
53  : SmartProjectionPoseFactor<CALIBRATION>(sharedNoiseModel, K, body_P_sensor, params),
54  sharedNoiseModel_(sharedNoiseModel),
55  params_(params),
56  rotation_only_fallback_(rotation_only_fallback),
57  robust_(robust),
58  huber_k_(huber_k) {
59  // From SmartFactorBase
60  if (!sharedNoiseModel) throw std::runtime_error("RobustSmartProjectionPoseFactor: sharedNoiseModel is required");
61  SharedIsotropic sharedIsotropic = boost::dynamic_pointer_cast<noiseModel::Isotropic>(sharedNoiseModel);
62  if (!sharedIsotropic) throw std::runtime_error("RobustSmartProjectionPoseFactor: needs isotropic");
63  noise_inv_sigma_ = 1.0 / sharedIsotropic->sigma();
64  triangulation_params_ = params.triangulation;
65  }
66 
67  // Helper function to return copy of factor with only measurements for allowed keys.
68  boost::shared_ptr<NonlinearFactor> PrunedCopy(const std::unordered_set<gtsam::Key>& keys_to_remove) const final {
69  boost::shared_ptr<RobustSmartProjectionPoseFactor> pruned_factor(
70  new RobustSmartProjectionPoseFactor(sharedNoiseModel_, this->calibration(), this->body_P_sensor(), params_,
71  rotation_only_fallback_, robust_, huber_k_));
72  for (int i = 0; i < this->keys().size(); ++i) {
73  if (keys_to_remove.count(this->keys()[i]) == 0) pruned_factor->add(this->measured()[i], this->keys()[i]);
74  }
75  // If too few measurements, factor is invalid
76  if (pruned_factor->measured().size() < 2) return nullptr;
77  return pruned_factor;
78  }
79 
80  boost::shared_ptr<GaussianFactor> linearize(const Values& values) const override {
81  typename Base::Cameras cameras = this->cameras(values);
82  // if (!this->triangulateForLinearize(cameras)) return boost::make_shared<JacobianFactorSVD<Dim, 2>>(this->keys());
83  const auto result = this->triangulateSafe(cameras);
84  // Adapted from SmartFactorBase::CreateJacobianSVDFactor
85  size_t m = this->keys().size();
86  typename Base::FBlocks F;
87  Vector b;
88  const size_t M = ZDim * m;
89  Matrix E0(M, M - 3);
90 
91  // Handle behind camera result with rotation only factors (see paper)
92  // Degenerate result tends to lead to solve failures, so return empty factor in this case
93  if (result.valid()) {
94  this->computeJacobiansSVD(F, E0, b, cameras, *(this->point()));
95  } else if (useForRotationOnly(result)) { // Rotation only factor
96  Unit3 backProjected = cameras[0].backprojectPointAtInfinity(this->measured().at(0));
97  // Cheirality error can still occur with backprojection
98  try {
99  this->computeJacobiansSVD(F, E0, b, cameras, backProjected);
100  } catch (...) {
101  return boost::make_shared<JacobianFactorSVD<Dim, 2>>(this->keys());
102  }
103  } else { // Empty factor // NOLINT
104  return boost::make_shared<JacobianFactorSVD<Dim, 2>>(this->keys());
105  }
106  return createRegularJacobianFactorSVD<Dim, ZDim>(this->keys(), F, E0, b);
107  }
108 
109  bool useForRotationOnly(const gtsam::TriangulationResult& result) const {
110  // Use rotation only for all failure cases
111  return true;
112  }
113 
114  double error(const Values& values) const override {
115  if (this->active(values)) {
116  try {
117  const double total_reprojection_loss = this->totalReprojectionError(this->cameras(values));
118  const auto result = this->point();
119  if (!result.valid() && !useForRotationOnly(result)) return 0.0;
120  // Multiply by 2 since totalReporjectionError divides mahal distance by 2, and robust_model_->loss
121  // expects mahal distance
122  const double loss = robust_ ? robustLoss(2.0 * total_reprojection_loss) : total_reprojection_loss;
123  return loss;
124  } catch (...) {
125  // Catch cheirality and other errors, zero on errors
126  return 0.0;
127  }
128  } else { // Inactive
129  return 0.0;
130  }
131  }
132 
133  // These "serialized" functions are only needed due to an error in gtsam for serializing result_.
134  // Call these instead of error() and point() for a smart factor that has been serialized.
135  // TODO(rsoussan): Remove these when gtsam bug fixed
136  double serialized_error(const Values& values) const {
137  if (this->active(values)) {
138  try {
139  const auto point = serialized_point(values);
140  const double total_reprojection_loss = this->totalReprojectionError(this->cameras(values), point);
141  if (!point.valid() && !useForRotationOnly(point)) return 0.0;
142  // Multiply by 2 since totalReporjectionError divides mahal distance by 2, and robust_model_->loss
143  // expects mahal distance
144  const double loss = robust_ ? robustLoss(2.0 * total_reprojection_loss) : total_reprojection_loss;
145  return loss;
146  } catch (...) {
147  // Catch cheirality and other errors, zero on errors
148  return 0.0;
149  }
150  } else { // Inactive
151  return 0.0;
152  }
153  }
154 
155  TriangulationResult serialized_point(const Values& values) const {
156  return gtsam::triangulateSafe(this->cameras(values), this->measured(), triangulation_params_);
157  }
158 
159  bool robust() const { return robust_; }
160 
161  double noise_inv_sigma() const { return noise_inv_sigma_; }
162 
163  // More efficient implementation of robust loss (also avoids inheritance calls)
164  double robustLoss(const double mahal_distance) const {
165  const double sqrt_mahal_distance = std::sqrt(mahal_distance);
166  const double absError = std::abs(sqrt_mahal_distance);
167  if (absError <= huber_k_) { // |x| <= k
168  return mahal_distance / 2;
169  } else { // |x| > k
170  return huber_k_ * (absError - (huber_k_ / 2));
171  }
172  }
173 
174  bool valid(const Values& values) const {
175  typename Base::Cameras cameras = this->cameras(values);
176  const auto point = this->triangulateSafe(cameras);
177  if (point.valid()) {
178  return true;
179  } else if (useForRotationOnly(point)) {
180  Unit3 backProjected = cameras[0].backprojectPointAtInfinity(this->measured().at(0));
181  try {
182  cameras.reprojectionError(backProjected, this->measured());
183  } catch (...) {
184  return false;
185  }
186  return true;
187  } else {
188  return false;
189  }
190  // Shouldn't get here
191  return false;
192  }
193 
194  private:
195  template <size_t D, size_t ZDim>
196  boost::shared_ptr<RegularJacobianFactor<D>> createRegularJacobianFactorSVD(
197  const KeyVector& keys,
198  const std::vector<Eigen::Matrix<double, ZDim, D>, Eigen::aligned_allocator<Eigen::Matrix<double, ZDim, D>>>&
199  Fblocks,
200  const Matrix& Enull, const Vector& b) const {
201  typedef std::pair<Key, Matrix> KeyMatrix;
202 
203  Vector reduced_error = Enull.transpose() * b;
204  // Apply noise whitening and robust weighting manually to more efficiently robustify
205  // error vector and jacobians. Equivalent to calling whitenSystem with a robust noise model.
206  // Assumes noise is diagonal (required for this factor anyway).
207  reduced_error *= noise_inv_sigma_;
208  double reduced_matrix_weight = noise_inv_sigma_;
209  if (robust_) {
210  const double robust_weight = robustWeight(reduced_error.norm());
211  reduced_error *= robust_weight;
212  reduced_matrix_weight *= robust_weight;
213  }
214 
215  size_t numKeys = Enull.rows() / ZDim;
216  size_t m2 = Enull.cols();
217  std::vector<KeyMatrix> reduced_matrices;
218  reduced_matrices.reserve(numKeys);
219  for (size_t k = 0; k < Fblocks.size(); ++k) {
220  Key key = keys[k];
221  reduced_matrices.emplace_back(
222  KeyMatrix(key, (Enull.transpose()).block(0, ZDim * k, m2, ZDim) * Fblocks[k] * reduced_matrix_weight));
223  }
224 
225  return boost::make_shared<RegularJacobianFactor<D>>(reduced_matrices, reduced_error);
226  }
227 
228  // TODO(rsoussan): profile these calls vs. gtsam better, is there a significant improvement?
229  // More efficient implementation of robust weight (also avoids inheritance calls)
230  double robustWeight(const double error_norm) const {
231  const double squared_weight = (error_norm <= huber_k_) ? (1.0) : (huber_k_ / error_norm);
232  return std::sqrt(squared_weight);
233  }
234 
237  template <class ARCHIVE>
238  void serialize(ARCHIVE& ar, const unsigned int /*version*/) {
239  ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP(SmartProjectionPoseFactor<CALIBRATION>);
240  ar& BOOST_SERIALIZATION_NVP(noise_inv_sigma_);
241  ar& BOOST_SERIALIZATION_NVP(huber_k_);
242  ar& BOOST_SERIALIZATION_NVP(robust_);
243  ar& BOOST_SERIALIZATION_NVP(rotation_only_fallback_);
244  ar& BOOST_SERIALIZATION_NVP(triangulation_params_);
245  }
246 
247  bool rotation_only_fallback_;
248  double robust_;
249  double huber_k_;
250  double noise_inv_sigma_;
251  // TODO(rsoussan): Remove once result_ serialization bug in gtsam fixed
252  TriangulationParameters triangulation_params_;
253  // Copies stored here to access for empty measurement copy creation
254  SharedNoiseModel sharedNoiseModel_;
255  SmartProjectionParams params_;
256 };
257 } // namespace gtsam
258 
259 #endif // GRAPH_FACTORS_ROBUST_SMART_PROJECTION_POSE_FACTOR_H_
gtsam::RobustSmartProjectionPoseFactor::RobustSmartProjectionPoseFactor
RobustSmartProjectionPoseFactor()
Definition: robust_smart_projection_pose_factor.h:40
gtsam::RobustSmartProjectionPoseFactor::PrunedCopy
boost::shared_ptr< NonlinearFactor > PrunedCopy(const std::unordered_set< gtsam::Key > &keys_to_remove) const final
Definition: robust_smart_projection_pose_factor.h:68
gtsam::RobustSmartProjectionPoseFactor::error
double error(const Values &values) const override
Definition: robust_smart_projection_pose_factor.h:114
gtsam::RobustSmartProjectionPoseFactor::serialized_point
TriangulationResult serialized_point(const Values &values) const
Definition: robust_smart_projection_pose_factor.h:155
gtsam::RobustSmartProjectionPoseFactor::noise_inv_sigma
double noise_inv_sigma() const
Definition: robust_smart_projection_pose_factor.h:161
gtsam::RobustSmartProjectionPoseFactor::access
friend class boost::serialization::access
Serialization function.
Definition: robust_smart_projection_pose_factor.h:236
gtsam::CumulativeFactor
Definition: cumulative_factor.h:30
gtsam::RobustSmartProjectionPoseFactor::RobustSmartProjectionPoseFactor
RobustSmartProjectionPoseFactor(const SharedNoiseModel &sharedNoiseModel, const boost::shared_ptr< CALIBRATION > K, const boost::optional< Pose3 > body_P_sensor, const SmartProjectionParams &params=SmartProjectionParams(), const bool rotation_only_fallback=false, const bool robust=true, const double huber_k=1.0)
Definition: robust_smart_projection_pose_factor.h:48
gtsam::RobustSmartProjectionPoseFactor::robust
bool robust() const
Definition: robust_smart_projection_pose_factor.h:159
gtsam::RobustSmartProjectionPoseFactor::valid
bool valid(const Values &values) const
Definition: robust_smart_projection_pose_factor.h:174
gtsam::RobustSmartProjectionPoseFactor::robustLoss
double robustLoss(const double mahal_distance) const
Definition: robust_smart_projection_pose_factor.h:164
gtsam::RobustSmartProjectionPoseFactor::linearize
boost::shared_ptr< GaussianFactor > linearize(const Values &values) const override
Definition: robust_smart_projection_pose_factor.h:80
gtsam
Definition: cumulative_factor.h:26
cumulative_factor.h
gtsam::RobustSmartProjectionPoseFactor::useForRotationOnly
bool useForRotationOnly(const gtsam::TriangulationResult &result) const
Definition: robust_smart_projection_pose_factor.h:109
calibration
Definition: camera_target_based_intrinsics_calibrator.h:42
gtsam::RobustSmartProjectionPoseFactor
Definition: robust_smart_projection_pose_factor.h:31
gtsam::RobustSmartProjectionPoseFactor::serialized_error
double serialized_error(const Values &values) const
Definition: robust_smart_projection_pose_factor.h:136