Formula Student Autonomous Systems
The code for the main driverless system
Loading...
Searching...
No Matches
graph_slam_instance.cpp
Go to the documentation of this file.
2
3#include <gtsam/base/Vector.h>
4#include <gtsam/geometry/Point2.h>
5#include <gtsam/geometry/Pose2.h>
6#include <gtsam/geometry/Rot2.h>
7#include <gtsam/inference/Symbol.h>
8#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
9#include <gtsam/nonlinear/Marginals.h>
10#include <gtsam/nonlinear/Values.h>
11#include <gtsam/sam/BearingFactor.h>
12#include <gtsam/sam/BearingRangeFactor.h>
13#include <gtsam/sam/RangeFactor.h>
14#include <gtsam/slam/BetweenFactor.h>
15#include <gtsam/slam/PriorFactor.h>
16#include <gtsam/slam/ProjectionFactor.h>
17
21
23
25
27 return this->_graph_values_;
28}
29
30const gtsam::Pose2 GraphSLAMInstance::get_pose() const {
31 return this->_graph_values_.at<gtsam::Pose2>(gtsam::Symbol('x', this->_pose_counter_));
32}
33
34Eigen::VectorXd GraphSLAMInstance::get_state_vector() const {
35 Eigen::VectorXd state(this->_landmark_counter_ * 2 + 3);
36 const gtsam::Pose2 current_pose =
37 this->_graph_values_.at<gtsam::Pose2>(gtsam::Symbol('x', this->_pose_counter_));
38 state(0) = current_pose.x();
39 state(1) = current_pose.y();
40 state(2) = current_pose.theta();
41 unsigned int landmark_id = 3;
42 for (auto it = _graph_values_.begin(); it != _graph_values_.end(); ++it) {
43 // Iterate through the landmarks in the _graph_values_
44 if (gtsam::Symbol(it->key).chr() == 'l') { // If the key is a landmark (l)
45 gtsam::Point2 landmark = it->value.cast<gtsam::Point2>();
46 state(landmark_id++) = landmark.x();
47 state(landmark_id++) = landmark.y();
48 }
49 }
50 return state;
51}
52
53unsigned int GraphSLAMInstance::get_landmark_counter() const { return this->_landmark_counter_; }
54
55unsigned int GraphSLAMInstance::get_pose_counter() const { return this->_pose_counter_; }
56
58 // if (this->_covariance_up_to_date_) {
59 // return _covariance_;
60 // }
61 // const gtsam::Marginals marginals(this->_factor_graph_, this->_graph_values_);
62
63 // // Create a gtsam::KeyVector with keys of all landmarks and the current pose, current pose
64 // // first, landmarks ordered by id
65 // gtsam::KeyVector keys;
66 // keys.push_back(gtsam::Symbol('x', this->_pose_counter_));
67 // for (unsigned int i = 1; i <= this->_landmark_counter_; i++) {
68 // keys.push_back(gtsam::Symbol('l', i));
69 // }
70
71 // this->_covariance_ = marginals.jointMarginalCovariance(keys).fullMatrix();
72 // this->_covariance_up_to_date_ = true;
73
74 // return _covariance_;
75 return Eigen::MatrixXd::Zero(2 * this->_landmark_counter_, 2 * this->_landmark_counter_);
76}
77
79 std::shared_ptr<BaseOptimizer> optimizer)
80 : _params_(params), _optimizer_(optimizer), _pose_timestamps_(params.max_pose_history_graph) {
81 // Create a new factor graph
82 _factor_graph_ = gtsam::NonlinearFactorGraph();
83 const gtsam::Pose2 prior_pose(0.0, 0.0,
84 0.0); // Create initial prior pose at origin, to bind graph
85 const gtsam::Symbol pose_symbol('x', ++(this->_pose_counter_));
86 _pose_timestamps_.push(TimedPose{this->_pose_counter_, rclcpp::Time(0)});
87 const gtsam::noiseModel::Diagonal::shared_ptr prior_noise =
88 gtsam::noiseModel::Diagonal::Sigmas(gtsam::Vector3(0.0, 0.0, 0.0));
89 _factor_graph_.add(gtsam::PriorFactor<gtsam::Pose2>(pose_symbol, prior_pose, prior_noise));
90 if (const auto optimizer_ptr = std::dynamic_pointer_cast<ISAM2Optimizer>(this->_optimizer_)) {
91 optimizer_ptr->_new_factors_.add(
92 gtsam::PriorFactor<gtsam::Pose2>(pose_symbol, prior_pose, prior_noise));
93 optimizer_ptr->_new_values_.insert(pose_symbol, prior_pose);
94 }
95
96 // Create a new values object
97 _graph_values_ = gtsam::Values();
98 _graph_values_.insert(pose_symbol, prior_pose);
99 _new_pose_node_ = true;
100}
101
115
117 if (this == &other) return *this; // Prevent self-assignment
118
119 // Copy each member individually
126 _optimizer_ = other._optimizer_->clone(); // because it's a shared_ptr
127 _params_ = other._params_;
130
131 return *this;
132}
133
134void GraphSLAMInstance::process_new_pose(const Eigen::Vector3d& pose_difference,
135 const Eigen::Vector3d& noise_vector,
136 const Eigen::Vector3d& new_pose,
137 const rclcpp::Time& pose_timestamp) {
138 RCLCPP_DEBUG(rclcpp::get_logger("slam"), "GraphSLAMInstance - Processing new pose %lf %lf %lf",
139 new_pose(0), new_pose(1), new_pose(2));
140 gtsam::Pose2 new_pose_gtsam = eigen_to_gtsam_pose(new_pose);
141
142 // X means pose node, l means landmark node
143 gtsam::Symbol new_pose_symbol('x', ++(this->_pose_counter_));
144
145 // Store the timestamp of the new pose in the circular buffer
146 this->_pose_timestamps_.push(TimedPose{this->_pose_counter_, pose_timestamp});
147
148 // Add the prior pose to the values
149 _graph_values_.insert(new_pose_symbol, new_pose_gtsam);
150 this->_new_pose_node_ = true;
151
152 if (const auto optimizer_ptr = std::dynamic_pointer_cast<ISAM2Optimizer>(this->_optimizer_)) {
153 optimizer_ptr->_new_values_.insert(new_pose_symbol, new_pose_gtsam);
154 }
155
156 RCLCPP_DEBUG(rclcpp::get_logger("slam"),
157 "GraphSLAMInstance - Adding prior pose %lf %lf %lf with noise %lf %lf %lf and "
158 "pose difference %lf %lf %lf",
159 new_pose_gtsam.x(), new_pose_gtsam.y(), new_pose_gtsam.theta(), noise_vector(0),
160 noise_vector(1), noise_vector(2), pose_difference(0), pose_difference(1),
161 pose_difference(2));
162
163 this->process_pose_difference(pose_difference, noise_vector);
164}
165
166void GraphSLAMInstance::process_pose_difference(const Eigen::Vector3d& pose_difference,
167 const Eigen::Vector3d& noise_vector,
168 unsigned int before_pose_id,
169 unsigned int after_pose_id) {
170 gtsam::Symbol before_pose_symbol('x', before_pose_id);
171 gtsam::Symbol after_pose_symbol('x', after_pose_id);
172 gtsam::noiseModel::Diagonal::shared_ptr motion_noise = gtsam::noiseModel::Diagonal::Sigmas(
173 gtsam::Vector3(noise_vector(0), noise_vector(1), noise_vector(2)));
174 gtsam::Pose2 pose_difference_gtsam = eigen_to_gtsam_pose(pose_difference);
175 this->_factor_graph_.add(gtsam::BetweenFactor<gtsam::Pose2>(before_pose_symbol, after_pose_symbol,
176 pose_difference_gtsam, motion_noise));
177 if (const auto optimizer_ptr = std::dynamic_pointer_cast<ISAM2Optimizer>(this->_optimizer_)) {
178 optimizer_ptr->_new_factors_.add(gtsam::BetweenFactor<gtsam::Pose2>(
179 before_pose_symbol, after_pose_symbol, pose_difference_gtsam, motion_noise));
180 }
181}
182
183void GraphSLAMInstance::process_pose_difference(const Eigen::Vector3d& pose_difference,
184 const Eigen::Vector3d& noise_vector,
185 unsigned int before_pose_id) {
186 this->process_pose_difference(pose_difference, noise_vector, before_pose_id,
187 this->_pose_counter_);
188}
189
190void GraphSLAMInstance::process_pose_difference(const Eigen::Vector3d& pose_difference,
191 const Eigen::Vector3d& noise_vector) {
192 this->process_pose_difference(pose_difference, noise_vector, this->_pose_counter_ - 1,
193 this->_pose_counter_);
194}
195
197 Eigen::VectorXd& observations = *(observation_data.observations_);
198 Eigen::VectorXi& associations = *(observation_data.associations_);
199 Eigen::VectorXd& observations_global = *(observation_data.observations_global_);
200 bool new_observation_factors = false;
201 unsigned int pose_id_at_observation_time =
202 this->get_landmark_id_at_time(observation_data.timestamp_);
203 RCLCPP_INFO(rclcpp::get_logger("slam"), "locked_landmarks= %s",
204 this->_locked_landmarks_ ? "true" : "false");
205 for (unsigned int i = 0; i < observations.size() / 2; i++) {
206 gtsam::Point2 landmark;
207 gtsam::Symbol landmark_symbol;
208 bool landmark_lost_in_optimization =
209 static_cast<int>(this->_landmark_counter_) < (associations(i)) / 2;
210 if (associations(i) == -2) {
211 // No association
212 continue;
213 } else if ((associations(i) == -1 || landmark_lost_in_optimization) &&
214 !this->_locked_landmarks_) {
215 // Create new landmark
216 landmark_symbol = gtsam::Symbol('l', (this->_landmark_counter_)++);
217 landmark = gtsam::Point2(observations_global(i * 2), observations_global(i * 2 + 1));
218 this->_graph_values_.insert(landmark_symbol, landmark);
219 if (const auto optimizer_ptr = std::dynamic_pointer_cast<ISAM2Optimizer>(this->_optimizer_)) {
220 optimizer_ptr->_new_values_.insert(
221 landmark_symbol, landmark); // Most efficient way I found to deal with ISAM2
222 }
223 } else if (associations(i) >= 0) {
224 if (!this->new_pose_factors()) { // Only add old observations if the vehicle has moved
225 continue;
226 }
227 // Association to previous landmark
228 landmark_symbol = gtsam::Symbol('l', (associations(i)) / 2); // Convert to landmark id
229 } else {
230 continue;
231 }
233 const Eigen::Vector2d observation_cartesian(observations[i * 2], observations[i * 2 + 1]);
234 Eigen::Vector2d observation_cylindrical = common_lib::maths::cartesian_to_cylindrical(
235 observation_cartesian); // Convert to cylindrical coordinates
236 const gtsam::Rot2 observation_rotation(observation_cylindrical(1));
237
238 const gtsam::noiseModel::Diagonal::shared_ptr observation_noise =
239 gtsam::noiseModel::Diagonal::Sigmas(gtsam::Vector2(this->_params_.observation_y_noise_,
240 this->_params_.observation_x_noise_));
241
242 if (const auto optimizer_ptr = std::dynamic_pointer_cast<ISAM2Optimizer>(this->_optimizer_)) {
243 optimizer_ptr->_new_factors_.add(gtsam::BearingRangeFactor<gtsam::Pose2, gtsam::Point2>(
244 gtsam::Symbol('x', pose_id_at_observation_time), landmark_symbol, observation_rotation,
245 observation_cylindrical(0),
246 observation_noise)); // Again, just for ISAM2, because
247 // it's incremental
248 }
249 this->_factor_graph_.add(gtsam::BearingRangeFactor<gtsam::Pose2, gtsam::Point2>(
250 gtsam::Symbol('x', pose_id_at_observation_time), landmark_symbol, observation_rotation,
251 observation_cylindrical(0), observation_noise));
252 }
255}
256
257void GraphSLAMInstance::load_initial_state(const Eigen::VectorXd& map, const Eigen::Vector3d& pose,
258 double preloaded_map_noise) {
259 RCLCPP_INFO(rclcpp::get_logger("slam"), "GraphSLAMInstance - Loading map ");
260 this->_pose_counter_ = 0;
261 this->_landmark_counter_ = 0;
262 _factor_graph_ = gtsam::NonlinearFactorGraph();
263 const gtsam::Pose2 prior_pose(pose(0), pose(1), pose(2));
264 const gtsam::Symbol pose_symbol('x', ++(this->_pose_counter_));
265 const gtsam::noiseModel::Diagonal::shared_ptr prior_noise =
266 gtsam::noiseModel::Diagonal::Sigmas(gtsam::Vector3(0.0, 0.0, 0.0));
267 _factor_graph_.add(gtsam::PriorFactor<gtsam::Pose2>(pose_symbol, prior_pose, prior_noise));
268 _pose_timestamps_.push(TimedPose{this->_pose_counter_, rclcpp::Time(0)});
269
270 _graph_values_ = gtsam::Values();
271 _graph_values_.insert(pose_symbol, prior_pose);
272 _new_pose_node_ = true;
273
274 unsigned int num_landmarks = map.size() / 2;
275 for (unsigned int i = 0; i < num_landmarks; i++) {
276 gtsam::Point2 landmark(map(i * 2), map(i * 2 + 1));
277 gtsam::Symbol landmark_symbol('l', (this->_landmark_counter_)++);
278 _graph_values_.insert(landmark_symbol, landmark);
279 const gtsam::noiseModel::Diagonal::shared_ptr landmark_noise =
280 gtsam::noiseModel::Diagonal::Sigmas(
281 gtsam::Vector2(preloaded_map_noise, preloaded_map_noise));
282 _factor_graph_.add(
283 gtsam::PriorFactor<gtsam::Point2>(landmark_symbol, landmark, landmark_noise));
284 }
285}
286
288 RCLCPP_DEBUG(rclcpp::get_logger("slam"),
289 "GraphSLAMInstance - Optimizing graph with %ld factors and %ld values",
290 this->_factor_graph_.size(), this->_graph_values_.size());
291 if (!this->_new_observation_factors_) return;
292
293 this->_graph_values_ = this->_optimizer_->optimize(
295 this->_new_observation_factors_ = false;
296}
297
299 for (unsigned int i = 0; i < _landmark_counter_; i++) {
300 gtsam::Symbol l('l', i);
301
302 if (!_graph_values_.exists(l)) {
303 RCLCPP_INFO(rclcpp::get_logger("slam"),
304 "GraphSLAMInstance - Landmark %d does not exist, skipping lock", i);
305 continue;
306 }
307
308 auto noise = gtsam::noiseModel::Constrained::All(2);
309 auto value = _graph_values_.at<gtsam::Point2>(l);
310
311 _factor_graph_.add(gtsam::PriorFactor<gtsam::Point2>(l, value, noise));
312 }
313 this->_locked_landmarks_ = true;
314}
315
316unsigned int GraphSLAMInstance::get_landmark_id_at_time(const rclcpp::Time& timestamp) const {
317 if (_pose_timestamps_.size() == 0) {
318 throw std::runtime_error("No poses in buffer");
319 }
320
321 for (size_t i = 0; i < _pose_timestamps_.size(); ++i) {
322 const TimedPose& curr = _pose_timestamps_.from_end(i);
323
324 if (curr.timestamp.seconds() <= timestamp.seconds()) {
325 if (i == 0) return curr.pose_id; // Avoid out-of-bounds
326 const TimedPose& next = _pose_timestamps_.from_end(i - 1);
327
328 auto diff_curr = timestamp.seconds() - curr.timestamp.seconds();
329 auto diff_next = next.timestamp.seconds() - timestamp.seconds();
330 return (diff_curr <= diff_next) ? curr.pose_id : next.pose_id;
331 }
332 }
333
334 // If no pose <= timestamp, return oldest pose
335 return _pose_timestamps_.from_end(_pose_timestamps_.size() - 1).pose_id;
336}
Graph SLAM instance class - class to hold the factor graph and the values.
unsigned int get_landmark_id_at_time(const rclcpp::Time &timestamp) const
const gtsam::Values & get_graph_values_reference() const
Get the graph values reference.
unsigned int get_landmark_counter() const
Get the number of landmarks.
GraphSLAMInstance & operator=(const GraphSLAMInstance &other)
Eigen::VectorXd get_state_vector() const
Get the state vector, EKF style.
unsigned int get_pose_counter() const
Get the number of poses.
void lock_landmarks()
Soft locks the positions of the landmarks, can be used after the first lap.
bool new_pose_factors() const
Checks if new observation factors should be added.
unsigned int _landmark_counter_
void load_initial_state(const Eigen::VectorXd &map, const Eigen::Vector3d &pose, double preloaded_map_noise)
Loads a map and an initial pose into the graph.
gtsam::NonlinearFactorGraph _factor_graph_
void process_observations(const ObservationData &observation_data)
Add observation factors to the graph.
void process_pose_difference(const Eigen::Vector3d &pose_difference, const Eigen::Vector3d &noise_vector, unsigned int before_pose_id, unsigned int after_pose_id)
Process a pose difference and add the respective factor to the graph.
void process_new_pose(const Eigen::Vector3d &pose_difference, const Eigen::Vector3d &noise_vector, const Eigen::Vector3d &new_pose, const rclcpp::Time &pose_timestamp)
Process a new pose and respective pose difference.
std::shared_ptr< BaseOptimizer > _optimizer_
void optimize()
Runs optimization on the graph.
const gtsam::Pose2 get_pose() const
Get the current pose estimate.
CircularBuffer< TimedPose > _pose_timestamps_
GraphSLAMInstance()=default
bool new_observation_factors() const
Checks if it is worth running optimization.
Eigen::MatrixXd get_covariance_matrix() const
Get the covariance matrix of the graph.
Eigen::Vector2d cartesian_to_cylindrical(const Eigen::Vector2d &cartesian)
Function to convert cartesian coordinates to cylindrical coordinates.
gtsam::Pose2 eigen_to_gtsam_pose(const Eigen::Vector3d &pose)
Definition utils.cpp:3
Struct containing observation data.
std::shared_ptr< Eigen::VectorXi > associations_
std::shared_ptr< Eigen::VectorXd > observations_
std::shared_ptr< Eigen::VectorXd > observations_global_
Parameters for the SLAM node.