NASA Astrobee Robot Software  0.19.1
Flight software for the Astrobee robots operating inside the International Space Station.
vo_smart_projection_factor_adder.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 FACTOR_ADDERS_VO_SMART_PROJECTION_FACTOR_ADDER_H_
20 #define FACTOR_ADDERS_VO_SMART_PROJECTION_FACTOR_ADDER_H_
21 
29 
30 #include <vector>
31 
32 namespace factor_adders {
33 using SmartFactorCalibration = gtsam::Cal3_S2;
34 using SmartFactorCamera = gtsam::PinholePose<SmartFactorCalibration>;
36 using SharedRobustSmartFactor = boost::shared_ptr<RobustSmartFactor>;
37 template <typename PoseNodeAdderType>
38 // Adds visual-odometry (VO) smart factors for feature tracks using GTSAM smart factors.
39 // Smart factors avoid bundle-adjustment by marginalizing out triangulated feature track
40 // landmark points before adding for optimization, making them much more efficient at the expense
41 // of accuracy. Since the landmark position for the point is not included during optimization,
42 // GTSAM performs retriangulation of the landmark during optimization if poses in the smart
43 // factor have changed by more than a set threshold. Retriangulation also triggers relinearization of the smart factor.
44 // Measurements are downsampled in the feature tracker, avoiding issues of downsampling
45 // from the latest measurement which may introduce many new timestamped pose nodes to
46 // the graph in addition to the latest measurement's timestamp.
48  : public MeasurementBasedFactorAdder<localization_measurements::FeaturePointsMeasurement> {
50 
51  public:
53  std::shared_ptr<PoseNodeAdderType> node_adder);
54 
55  // Default constructor for serialization only.
56  VoSmartProjectionFactorAdder() = default;
57 
58  // Const accesor to feature tracker
60 
61  private:
62  // Add factors using either set measurement spacing or max spacing.
63  int AddMeasurementBasedFactors(const localization_common::Time oldest_allowed_time,
64  const localization_common::Time newest_allowed_time,
65  gtsam::NonlinearFactorGraph& factors) final;
66 
67  // Add factors using downsampled measurements, determined by the measurement_spacing param.
68  // Measurements are in affect downsampled before being used for feature tracks.
69  int AddFactorsUsingDownsampledMeasurements(gtsam::NonlinearFactorGraph& factors) const;
70 
71  // Helper function to add a smart factor given a set of feature track points.
72  // Assumes points are ordered from oldest to latest, so oldest points are
73  // added first and prioritized over later points given a max number of points to add.
74  bool AddSmartFactor(const vision_common::FeaturePoints& feature_track_points,
75  gtsam::NonlinearFactorGraph& factors) const;
76 
77  // Checks the average distance from the mean and the number of points in the track.
78  bool ValidTrack(const vision_common::FeaturePoints& feature_track) const;
79 
80  // Fixes invalid smart factors (that suffer cheirality errors) by removing individual measurements and if that fails
81  // by removing measurement sequences.
82  // If both fail, the smart factor is left in the graph in case it becomes
83  // valid over the course of optimization.
84  void FixSmartFactors(const gtsam::Values& values, gtsam::NonlinearFactorGraph& factors) const;
85 
86  // Attempts to fix smart factors by removing individual measurements.
87  boost::optional<SharedRobustSmartFactor> FixSmartFactorByRemovingIndividualMeasurements(
88  const gtsam::Values& values, const RobustSmartFactor& smart_factor) const;
89 
90  // Attempts to fix smart factors by removing measurement sequences.
91  boost::optional<SharedRobustSmartFactor> FixSmartFactorByRemovingMeasurementSequence(
92  const gtsam::Values& values, const RobustSmartFactor& smart_factor) const;
93 
94  // Helper function for creating and testing a fixed smart factor
95  boost::optional<SharedRobustSmartFactor> FixedSmartFactor(
96  const gtsam::Values& values, gtsam::PinholePose<gtsam::Cal3_S2>::MeasurementVector& measurements,
97  gtsam::KeyVector& keys) const;
98 
99  // Serialization function
101  template <class Archive>
102  void serialize(Archive& ar, const unsigned int file_version) {
103  ar& BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base);
104  ar& BOOST_SERIALIZATION_NVP(node_adder_);
105  ar& BOOST_SERIALIZATION_NVP(params_);
106  ar& BOOST_SERIALIZATION_NVP(feature_tracker_);
107  }
108 
109  std::shared_ptr<PoseNodeAdderType> node_adder_;
111  std::shared_ptr<vision_common::SpacedFeatureTracker> feature_tracker_;
112 };
113 
114 // Implementation
115 template <typename PoseNodeAdderType>
117  const VoSmartProjectionFactorAdderParams& params, std::shared_ptr<PoseNodeAdderType> node_adder)
118  : Base(params), params_(params), node_adder_(node_adder) {
119  feature_tracker_.reset(new vision_common::SpacedFeatureTracker(params.spaced_feature_tracker));
120 }
121 
122 template <typename PoseNodeAdderType>
124  return *feature_tracker_;
125 }
126 
127 template <typename PoseNodeAdderType>
129  const localization_common::Time oldest_allowed_time, const localization_common::Time newest_allowed_time,
130  gtsam::NonlinearFactorGraph& factors) {
131  // Update feature tracker with new measurements and remove these from the measurement buffer if nodes can be created
132  // for them.
133  ProcessMeasurements(oldest_allowed_time, newest_allowed_time,
134  [this](const localization_measurements::FeaturePointsMeasurement& measurement) {
135  // Don't add measurements until pose nodes can be created at their timestamp
136  if (!node_adder_->CanAddNode(measurement.timestamp)) return false;
137  feature_tracker_->Update(measurement.feature_points);
138  return true;
139  });
140  // Remove old feature track points before adding factors
141  feature_tracker_->RemoveOldPoints(oldest_allowed_time);
142  // Remove old smart factors before adding new ones, since otherwise there would be repeat factors
143  // for already existing feature tracks
144  localization_common::DeleteFactors<RobustSmartFactor>(factors);
145 
146  // Create smart factors based on feature tracks
147  int num_added_factors = 0;
148  num_added_factors = AddFactorsUsingDownsampledMeasurements(factors);
149  // Attempt to fix broken factors if enabled
150  if (params_.fix_invalid_factors) FixSmartFactors(node_adder_->nodes().gtsam_values(), factors);
151  return num_added_factors;
152 }
153 
154 template <typename PoseNodeAdderType>
155 int VoSmartProjectionFactorAdder<PoseNodeAdderType>::AddFactorsUsingDownsampledMeasurements(
156  gtsam::NonlinearFactorGraph& factors) const {
157  int num_added_factors = 0;
158  const auto spaced_feature_tracks = feature_tracker_->SpacedFeatureTracks();
159  for (const auto& feature_track : spaced_feature_tracks) {
160  if (num_added_factors >= params_.max_num_factors) break;
161  if (ValidTrack(feature_track)) {
162  if (AddSmartFactor(feature_track, factors)) ++num_added_factors;
163  }
164  }
165  return num_added_factors;
166 }
167 
168 template <typename PoseNodeAdderType>
169 bool VoSmartProjectionFactorAdder<PoseNodeAdderType>::AddSmartFactor(
170  const vision_common::FeaturePoints& feature_track_points, gtsam::NonlinearFactorGraph& factors) const {
171  SharedRobustSmartFactor smart_factor;
172  const int num_feature_track_points = feature_track_points.size();
173  // Optionally scale the noise with the number of points, as a longer track the relies on a
174  // longer history of poses tends to have higher error
175  const double noise_scale =
176  params_.scale_noise_with_num_points ? params_.noise_scale * num_feature_track_points : params_.noise_scale;
177  const auto noise = gtsam::noiseModel::Isotropic::Sigma(2, noise_scale * params_.cam_noise->sigma());
178  smart_factor =
179  boost::make_shared<RobustSmartFactor>(noise, params_.cam_intrinsics, params_.body_T_cam, params_.smart_factor,
180  params_.rotation_only_fallback, params_.robust, params_.huber_k);
181 
182  for (int i = 0; i < static_cast<int>(feature_track_points.size()); ++i) {
183  // Start in reverse so most recent measurements are added first
184  const auto& feature_point = feature_track_points[feature_track_points.size() - 1 - i];
185  if (i >= params_.max_num_points_per_factor) break;
186  const auto& timestamp = feature_point.timestamp;
187  // Add or get pose key
188  if (!node_adder_->AddNode(timestamp, factors)) {
189  LogError("AddSmartFactor: Failed to add node for timestamp " << std::setprecision(15) << timestamp << ".");
190  return false;
191  }
192  const auto keys = node_adder_->Keys(timestamp);
193  if (keys.empty()) {
194  LogError("AddSmartFactor: Failed to get keys for timestamp " << std::setprecision(15) << timestamp << ".");
195  return false;
196  }
197  // Assumes first key is pose
198  const auto& pose_key = keys[0];
199 
200  smart_factor->add(SmartFactorCamera::Measurement(feature_point.image_point), pose_key);
201  }
202  factors.push_back(smart_factor);
203  return true;
204 }
205 
206 template <typename PoseNodeAdderType>
207 bool VoSmartProjectionFactorAdder<PoseNodeAdderType>::ValidTrack(
208  const vision_common::FeaturePoints& feature_track) const {
209  const double average_distance_from_mean = vision_common::AverageDistanceFromMean(feature_track);
210  if (static_cast<int>(feature_track.size()) < params_.min_num_points_per_factor) return false;
211  return (average_distance_from_mean >= params_.min_avg_distance_from_mean);
212 }
213 
214 template <typename PoseNodeAdderType>
215 void VoSmartProjectionFactorAdder<PoseNodeAdderType>::FixSmartFactors(const gtsam::Values& values,
216  gtsam::NonlinearFactorGraph& factors) const {
217  for (auto& factor : factors) {
218  auto smart_factor = dynamic_cast<RobustSmartFactor*>(factor.get());
219  if (!smart_factor) continue;
220  // Can't remove measurements if there are only 2 or fewer
221  if (smart_factor->measured().size() <= 2) continue;
222  const auto point = smart_factor->triangulateSafe(smart_factor->cameras(values));
223  if (point.valid()) continue;
224  // Invalid factor, first attempt to fix by removing individiual measurements.
225  {
226  const auto fixed_smart_factor = FixSmartFactorByRemovingIndividualMeasurements(values, *smart_factor);
227  if (fixed_smart_factor) {
228  factor = *fixed_smart_factor;
229  continue;
230  }
231  }
232  // If removing individiual measurements fails, attempt to fix by removing measurement
233  // sequences.
234  {
235  const auto fixed_smart_factor = FixSmartFactorByRemovingMeasurementSequence(values, *smart_factor);
236  if (fixed_smart_factor) {
237  factor = *fixed_smart_factor;
238  continue;
239  }
240  }
241  // Don't remove factor in case it becomes valid over the course of optimization.
242  LogDebug("SplitSmartFactorsIfNeeded: Failed to fix smart factor");
243  }
244 }
245 
246 template <typename PoseNodeAdderType>
247 boost::optional<SharedRobustSmartFactor>
248 VoSmartProjectionFactorAdder<PoseNodeAdderType>::FixSmartFactorByRemovingIndividualMeasurements(
249  const gtsam::Values& values, const RobustSmartFactor& smart_factor) const {
250  // TODO(rsoussan): Make this more efficient by enabled removal of measurements and keys in smart factor
251  const auto original_measurements = smart_factor.measured();
252  const auto original_keys = smart_factor.keys();
253  int measurement_index_to_remove;
254  // Start with latest measurement
255  for (measurement_index_to_remove = original_measurements.size() - 1; measurement_index_to_remove >= 0;
256  --measurement_index_to_remove) {
257  gtsam::PinholePose<gtsam::Cal3_S2>::MeasurementVector measurements_to_add;
258  gtsam::KeyVector keys_to_add;
259  for (int i = 0; i < static_cast<int>(original_measurements.size()); ++i) {
260  if (i == measurement_index_to_remove) continue;
261  measurements_to_add.emplace_back(original_measurements[i]);
262  keys_to_add.emplace_back(original_keys[i]);
263  }
264  const auto fixed_smart_factor = FixedSmartFactor(values, measurements_to_add, keys_to_add);
265  if (fixed_smart_factor) {
266  LogDebug("FixSmartFactorByRemovingIndividualMeasurements: Fixed by removing measurement "
267  << measurement_index_to_remove << ", num original measurements: " << original_measurements.size());
268  return fixed_smart_factor;
269  }
270  }
271  return boost::none;
272 }
273 
274 template <typename PoseNodeAdderType>
275 boost::optional<SharedRobustSmartFactor>
276 VoSmartProjectionFactorAdder<PoseNodeAdderType>::FixSmartFactorByRemovingMeasurementSequence(
277  const gtsam::Values& values, const RobustSmartFactor& smart_factor) const {
278  constexpr int min_num_measurements = 2;
279  // TODO(rsoussan): Make this more efficient by enabled removal of measurements and keys in smart factor
280  const auto original_measurements = smart_factor.measured();
281  const auto original_keys = smart_factor.keys();
282  int num_measurements_to_add = original_measurements.size() - 1;
283  // Try to remove min number of most recent measurements
284  while (num_measurements_to_add >= min_num_measurements) {
285  gtsam::PinholePose<gtsam::Cal3_S2>::MeasurementVector measurements_to_add;
286  gtsam::KeyVector keys_to_add;
287  for (int i = 0; i < num_measurements_to_add; ++i) {
288  measurements_to_add.emplace_back(original_measurements[i]);
289  keys_to_add.emplace_back(original_keys[i]);
290  }
291  const auto fixed_smart_factor = FixedSmartFactor(values, measurements_to_add, keys_to_add);
292  if (fixed_smart_factor) {
293  LogDebug(
294  "FixSmartFactorByRemovingMeasurementSequence: Fixed smart factor by removing most recent "
295  "measurements. Original "
296  "measurement size: "
297  << original_measurements.size() << ", new size: " << num_measurements_to_add);
298  return fixed_smart_factor;
299  } else {
300  --num_measurements_to_add;
301  }
302  }
303  num_measurements_to_add = original_measurements.size() - 1;
304  // Try to remove min number of oldest measurements
305  while (num_measurements_to_add >= min_num_measurements) {
306  gtsam::PinholePose<gtsam::Cal3_S2>::MeasurementVector measurements_to_add;
307  gtsam::KeyVector keys_to_add;
308  for (int i = original_measurements.size() - num_measurements_to_add;
309  i < static_cast<int>(original_measurements.size()); ++i) {
310  measurements_to_add.emplace_back(original_measurements[i]);
311  keys_to_add.emplace_back(original_keys[i]);
312  }
313  const auto fixed_smart_factor = FixedSmartFactor(values, measurements_to_add, keys_to_add);
314  if (fixed_smart_factor) {
315  LogDebug(
316  "FixSmartFactorByRemovingMeasurementSequence: Fixed smart factor by removing oldest measurements. "
317  "Original "
318  "measurement size: "
319  << original_measurements.size() << ", new size: " << num_measurements_to_add);
320  return fixed_smart_factor;
321  } else {
322  --num_measurements_to_add;
323  }
324  }
325  // Failed to fix smart factor
326  return boost::none;
327  // TODO(rsoussan): delete factor if fail to find acceptable new one?
328  // TODO(rsoussan): attempt to make a second factor with remaining measuremnts!!!
329 }
330 
331 template <typename PoseNodeAdderType>
332 boost::optional<SharedRobustSmartFactor> VoSmartProjectionFactorAdder<PoseNodeAdderType>::FixedSmartFactor(
333  const gtsam::Values& values, gtsam::PinholePose<gtsam::Cal3_S2>::MeasurementVector& measurements,
334  gtsam::KeyVector& keys) const {
335  auto new_smart_factor = boost::make_shared<RobustSmartFactor>(
336  params_.cam_noise, params_.cam_intrinsics, params_.body_T_cam, params_.smart_factor, params_.rotation_only_fallback,
337  params_.robust, params_.huber_k);
338  new_smart_factor->add(measurements, keys);
339  const auto new_point = new_smart_factor->triangulateSafe(new_smart_factor->cameras(values));
340  if (!new_point.valid()) return boost::none;
341  return new_smart_factor;
342 }
343 } // namespace factor_adders
344 #endif // FACTOR_ADDERS_VO_SMART_PROJECTION_FACTOR_ADDER_H_
factor_adders::VoSmartProjectionFactorAdder::feature_tracker
const vision_common::SpacedFeatureTracker & feature_tracker() const
Definition: vo_smart_projection_factor_adder.h:123
localization_measurements::Measurement::timestamp
localization_common::Time timestamp
Definition: measurement.h:29
factor_adders::VoSmartProjectionFactorAdderParams
Definition: vo_smart_projection_factor_adder_params.h:31
factor_adders::VoSmartProjectionFactorAdderParams::spaced_feature_tracker
vision_common::SpacedFeatureTrackerParams spaced_feature_tracker
Definition: vo_smart_projection_factor_adder_params.h:32
LogError
#define LogError(msg)
Definition: logger.h:55
utilities.h
vision_common::SpacedFeatureTracker
Definition: spaced_feature_tracker.h:32
factor_adders::SmartFactorCalibration
gtsam::Cal3_S2 SmartFactorCalibration
Definition: vo_smart_projection_factor_adder.h:33
measurement_based_factor_adder.h
factor_adders::VoSmartProjectionFactorAdder::access
friend class boost::serialization::access
Definition: vo_smart_projection_factor_adder.h:100
vision_common::FeaturePoints
std::vector< FeaturePoint > FeaturePoints
Definition: feature_point.h:56
factor_adders::VoSmartProjectionFactorAdder
Definition: vo_smart_projection_factor_adder.h:47
factor_adders::SmartFactorCamera
gtsam::PinholePose< SmartFactorCalibration > SmartFactorCamera
Definition: vo_smart_projection_factor_adder.h:34
spaced_feature_tracker.h
factor_adders::MeasurementBasedFactorAdder
Definition: measurement_based_factor_adder.h:29
factor_adders
Definition: depth_odometry_factor_adder.h:31
factor_adders::SharedRobustSmartFactor
boost::shared_ptr< RobustSmartFactor > SharedRobustSmartFactor
Definition: vo_smart_projection_factor_adder.h:36
LogDebug
#define LogDebug(msg)
Definition: logger.h:69
utilities.h
gtsam::RobustSmartProjectionPoseFactor
Definition: robust_smart_projection_pose_factor.h:31
factor_adders::RobustSmartFactor
gtsam::RobustSmartProjectionPoseFactor< SmartFactorCalibration > RobustSmartFactor
Definition: vo_smart_projection_factor_adder.h:35
feature_points_measurement.h
factor_adders::VoSmartProjectionFactorAdder::VoSmartProjectionFactorAdder
VoSmartProjectionFactorAdder()=default
localization_common::Time
double Time
Definition: time.h:23
vision_common::AverageDistanceFromMean
double AverageDistanceFromMean(const std::vector< FeaturePoint > &points)
Definition: utilities.cc:77
localization_measurements::FeaturePointsMeasurement
Definition: feature_points_measurement.h:29
localization_measurements::FeaturePointsMeasurement::feature_points
vision_common::FeaturePoints feature_points
Definition: feature_points_measurement.h:30
vo_smart_projection_factor_adder_params.h
robust_smart_projection_pose_factor.h