Nav2 Navigation Stack - kilted  kilted
ROS 2 Navigation Stack
smoother.hpp
1 // Copyright (c) 2021 RoboTech Vision
2 // Copyright (c) 2020, Samsung Research America
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License. Reserved.
15 
16 #ifndef NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_HPP_
17 #define NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_HPP_
18 
19 #include <cmath>
20 #include <vector>
21 #include <iostream>
22 #include <memory>
23 #include <queue>
24 #include <utility>
25 #include <deque>
26 #include <limits>
27 #include <algorithm>
28 
29 #include "nav2_constrained_smoother/smoother_cost_function.hpp"
30 #include "nav2_constrained_smoother/utils.hpp"
31 #include "nav2_core/smoother_exceptions.hpp"
32 
33 #include "ceres/ceres.h"
34 #include "Eigen/Core"
35 
36 namespace nav2_constrained_smoother
37 {
38 
43 class Smoother
44 {
45 public:
49  Smoother() {}
50 
54  ~Smoother() {}
55 
60  void initialize(const OptimizerParams params)
61  {
62  debug_ = params.debug;
63 
64  options_.linear_solver_type = params.solver_types.at(params.linear_solver_type);
65 
66  options_.max_num_iterations = params.max_iterations;
67 
68  options_.function_tolerance = params.fn_tol;
69  options_.gradient_tolerance = params.gradient_tol;
70  options_.parameter_tolerance = params.param_tol;
71 
72  if (debug_) {
73  options_.minimizer_progress_to_stdout = true;
74  options_.logging_type = ceres::LoggingType::PER_MINIMIZER_ITERATION;
75  } else {
76  options_.logging_type = ceres::SILENT;
77  }
78  }
79 
89  bool smooth(
90  std::vector<Eigen::Vector3d> & path,
91  const Eigen::Vector2d & start_dir,
92  const Eigen::Vector2d & end_dir,
93  const nav2_costmap_2d::Costmap2D * costmap,
94  const SmootherParams & params)
95  {
96  // Path has always at least 2 points
97  if (path.size() < 2) {
98  throw nav2_core::InvalidPath("Constrained smoother: Path must have at least 2 points");
99  }
100 
101  options_.max_solver_time_in_seconds = params.max_time;
102 
103  ceres::Problem problem;
104  std::vector<Eigen::Vector3d> path_optim;
105  std::vector<bool> optimized;
106  if (buildProblem(path, costmap, params, problem, path_optim, optimized)) {
107  // solve the problem
108  ceres::Solver::Summary summary;
109  ceres::Solve(options_, &problem, &summary);
110  if (debug_) {
111  RCLCPP_INFO(rclcpp::get_logger("smoother_server"), "%s", summary.FullReport().c_str());
112  }
113  if (!summary.IsSolutionUsable() || summary.initial_cost - summary.final_cost < 0.0) {
114  throw nav2_core::FailedToSmoothPath("Solution is not usable");
115  }
116  } else {
117  RCLCPP_INFO(rclcpp::get_logger("smoother_server"), "Path too short to optimize");
118  }
119 
120  upsampleAndPopulate(path_optim, optimized, start_dir, end_dir, params, path);
121 
122  return true;
123  }
124 
125 private:
136  bool buildProblem(
137  const std::vector<Eigen::Vector3d> & path,
138  const nav2_costmap_2d::Costmap2D * costmap,
139  const SmootherParams & params,
140  ceres::Problem & problem,
141  std::vector<Eigen::Vector3d> & path_optim,
142  std::vector<bool> & optimized)
143  {
144  // Create costmap grid
145  costmap_grid_ = std::make_shared<ceres::Grid2D<unsigned char>>(
146  costmap->getCharMap(), 0, costmap->getSizeInCellsY(), 0, costmap->getSizeInCellsX());
147  auto costmap_interpolator =
148  std::make_shared<ceres::BiCubicInterpolator<ceres::Grid2D<unsigned char>>>(*costmap_grid_);
149 
150  // Create residual blocks
151  const double cusp_half_length = params.cusp_zone_length / 2;
152  ceres::LossFunction * loss_function = NULL;
153  path_optim = path;
154  optimized = std::vector<bool>(path.size());
155  optimized[0] = true;
156  int prelast_i = -1;
157  int last_i = 0;
158  double last_direction = path_optim[0][2];
159  bool last_was_cusp = false;
160  bool last_is_reversing = false;
161  std::deque<std::pair<double, SmootherCostFunction *>> potential_cusp_funcs;
162  double last_segment_len = EPSILON;
163  double potential_cusp_funcs_len = 0;
164  double len_since_cusp = std::numeric_limits<double>::infinity();
165 
166  for (size_t i = 1; i < path_optim.size(); i++) {
167  auto & pt = path_optim[i];
168  bool is_cusp = false;
169  if (i != path_optim.size() - 1) {
170  is_cusp = pt[2] * last_direction < 0;
171  last_direction = pt[2];
172 
173  // skip to downsample if can be skipped (no forward/reverse direction change)
174  if (!is_cusp &&
175  i > (params.keep_start_orientation ? 1 : 0) &&
176  i < path_optim.size() - (params.keep_goal_orientation ? 2 : 1) &&
177  static_cast<int>(i - last_i) < params.path_downsampling_factor)
178  {
179  continue;
180  }
181  }
182 
183  // keep distance inequalities between poses
184  // (some might have been downsampled while others might not)
185  double current_segment_len = (path_optim[i] - path_optim[last_i]).block<2, 1>(0, 0).norm();
186 
187  // forget cost functions which don't have chance to be part of a cusp zone
188  potential_cusp_funcs_len += current_segment_len;
189  while (!potential_cusp_funcs.empty() && potential_cusp_funcs_len > cusp_half_length) {
190  potential_cusp_funcs_len -= potential_cusp_funcs.front().first;
191  potential_cusp_funcs.pop_front();
192  }
193 
194  // update cusp zone costmap weights
195  if (is_cusp) {
196  double len_to_cusp = current_segment_len;
197  for (int i_cusp = potential_cusp_funcs.size() - 1; i_cusp >= 0; i_cusp--) {
198  auto & f = potential_cusp_funcs[i_cusp];
199  double new_weight =
200  params.cusp_costmap_weight * (1.0 - len_to_cusp / cusp_half_length) +
201  params.costmap_weight * len_to_cusp / cusp_half_length;
202  if (std::abs(new_weight - params.cusp_costmap_weight) <
203  std::abs(f.second->getCostmapWeight() - params.cusp_costmap_weight))
204  {
205  f.second->setCostmapWeight(new_weight);
206  }
207  len_to_cusp += f.first;
208  }
209  potential_cusp_funcs_len = 0;
210  potential_cusp_funcs.clear();
211  len_since_cusp = 0;
212  }
213 
214  // add cost function
215  optimized[i] = true;
216  if (prelast_i != -1) {
217  double costmap_weight = params.costmap_weight;
218  if (len_since_cusp <= cusp_half_length) {
219  costmap_weight =
220  params.cusp_costmap_weight * (1.0 - len_since_cusp / cusp_half_length) +
221  params.costmap_weight * len_since_cusp / cusp_half_length;
222  }
223  SmootherCostFunction * cost_function = new SmootherCostFunction(
224  path[last_i].template block<2, 1>(
225  0,
226  0),
227  (last_was_cusp ? -1 : 1) * last_segment_len / current_segment_len,
228  last_is_reversing,
229  costmap,
230  costmap_interpolator,
231  params,
232  costmap_weight
233  );
234  problem.AddResidualBlock(
235  cost_function->AutoDiff(), loss_function,
236  path_optim[last_i].data(), pt.data(), path_optim[prelast_i].data());
237 
238  potential_cusp_funcs.emplace_back(current_segment_len, cost_function);
239  }
240 
241  // shift current to last and last to pre-last
242  last_was_cusp = is_cusp;
243  last_is_reversing = last_direction < 0;
244  prelast_i = last_i;
245  last_i = i;
246  len_since_cusp += current_segment_len;
247  last_segment_len = std::max(EPSILON, current_segment_len);
248  }
249 
250  int posesToOptimize = problem.NumParameterBlocks() - 2; // minus start and goal
251  if (params.keep_goal_orientation) {
252  posesToOptimize -= 1; // minus goal orientation holder
253  }
254  if (params.keep_start_orientation) {
255  posesToOptimize -= 1; // minus start orientation holder
256  }
257  if (posesToOptimize <= 0) {
258  return false; // nothing to optimize
259  }
260  // first two and last two points are constant (to keep start and end direction)
261  problem.SetParameterBlockConstant(path_optim.front().data());
262  if (params.keep_start_orientation) {
263  problem.SetParameterBlockConstant(path_optim[1].data());
264  }
265  if (params.keep_goal_orientation) {
266  problem.SetParameterBlockConstant(path_optim[path_optim.size() - 2].data());
267  }
268  problem.SetParameterBlockConstant(path_optim.back().data());
269  return true;
270  }
271 
281  void upsampleAndPopulate(
282  const std::vector<Eigen::Vector3d> & path_optim,
283  const std::vector<bool> & optimized,
284  const Eigen::Vector2d & start_dir,
285  const Eigen::Vector2d & end_dir,
286  const SmootherParams & params,
287  std::vector<Eigen::Vector3d> & path)
288  {
289  // Populate path, assign orientations, interpolate skipped/upsampled poses
290  path.clear();
291  if (params.path_upsampling_factor > 1) {
292  path.reserve(params.path_upsampling_factor * (path_optim.size() - 1) + 1);
293  }
294  int last_i = 0;
295  int prelast_i = -1;
296  Eigen::Vector2d prelast_dir = {0, 0};
297  for (int i = 1; i <= static_cast<int>(path_optim.size()); i++) {
298  if (i == static_cast<int>(path_optim.size()) || optimized[i]) {
299  if (prelast_i != -1) {
300  Eigen::Vector2d last_dir;
301  auto & prelast = path_optim[prelast_i];
302  auto & last = path_optim[last_i];
303 
304  // Compute orientation of last
305  if (i < static_cast<int>(path_optim.size())) {
306  auto & current = path_optim[i];
307  Eigen::Vector2d tangent_dir = tangentDir<double>(
308  prelast.block<2, 1>(0, 0),
309  last.block<2, 1>(0, 0),
310  current.block<2, 1>(0, 0),
311  prelast[2] * last[2] < 0);
312 
313  last_dir =
314  tangent_dir.dot((current - last).block<2, 1>(0, 0) * last[2]) >= 0 ?
315  tangent_dir :
316  -tangent_dir;
317  last_dir.normalize();
318  } else if (params.keep_goal_orientation) {
319  last_dir = end_dir;
320  } else {
321  last_dir = (last - prelast).block<2, 1>(0, 0) * last[2];
322  last_dir.normalize();
323  }
324  double last_angle = atan2(last_dir[1], last_dir[0]);
325 
326  // Interpolate poses between prelast and last
327  int interp_cnt = (last_i - prelast_i) * params.path_upsampling_factor - 1;
328  if (interp_cnt > 0) {
329  Eigen::Vector2d last_pt = last.block<2, 1>(0, 0);
330  Eigen::Vector2d prelast_pt = prelast.block<2, 1>(0, 0);
331  double dist = (last_pt - prelast_pt).norm();
332  Eigen::Vector2d pt1 = prelast_pt + prelast_dir * dist * 0.4 * prelast[2];
333  Eigen::Vector2d pt2 = last_pt - last_dir * dist * 0.4 * prelast[2];
334  for (int j = 1; j <= interp_cnt; j++) {
335  double interp = j / static_cast<double>(interp_cnt + 1);
336  Eigen::Vector2d pt = cubicBezier(prelast_pt, pt1, pt2, last_pt, interp);
337  path.emplace_back(pt[0], pt[1], 0.0);
338  }
339  }
340  path.emplace_back(last[0], last[1], last_angle);
341 
342  // Assign orientations to interpolated points
343  for (size_t j = path.size() - 1 - interp_cnt; j < path.size() - 1; j++) {
344  Eigen::Vector2d tangent_dir = tangentDir<double>(
345  path[j - 1].block<2, 1>(0, 0),
346  path[j].block<2, 1>(0, 0),
347  path[j + 1].block<2, 1>(0, 0),
348  false);
349  tangent_dir =
350  tangent_dir.dot((path[j + 1] - path[j]).block<2, 1>(0, 0) * prelast[2]) >= 0 ?
351  tangent_dir :
352  -tangent_dir;
353  path[j][2] = atan2(tangent_dir[1], tangent_dir[0]);
354  }
355 
356  prelast_dir = last_dir;
357  } else { // start pose
358  auto & start = path_optim[0];
359  Eigen::Vector2d dir = params.keep_start_orientation ?
360  start_dir :
361  ((path_optim[i] - start).block<2, 1>(0, 0) * start[2]).normalized();
362  path.emplace_back(start[0], start[1], atan2(dir[1], dir[0]));
363  prelast_dir = dir;
364  }
365  prelast_i = last_i;
366  last_i = i;
367  }
368  }
369  }
370 
371  /*
372  Piecewise cubic bezier curve as defined by Adobe in Postscript
373  The two end points are pt0 and pt3
374  Their associated control points are pt1 and pt2
375  */
376  static Eigen::Vector2d cubicBezier(
377  Eigen::Vector2d & pt0, Eigen::Vector2d & pt1,
378  Eigen::Vector2d & pt2, Eigen::Vector2d & pt3, double mu)
379  {
380  Eigen::Vector2d a, b, c, pt;
381 
382  c[0] = 3 * (pt1[0] - pt0[0]);
383  c[1] = 3 * (pt1[1] - pt0[1]);
384  b[0] = 3 * (pt2[0] - pt1[0]) - c[0];
385  b[1] = 3 * (pt2[1] - pt1[1]) - c[1];
386  a[0] = pt3[0] - pt0[0] - c[0] - b[0];
387  a[1] = pt3[1] - pt0[1] - c[1] - b[1];
388 
389  pt[0] = a[0] * mu * mu * mu + b[0] * mu * mu + c[0] * mu + pt0[0];
390  pt[1] = a[1] * mu * mu * mu + b[1] * mu * mu + c[1] * mu + pt0[1];
391 
392  return pt;
393  }
394 
395  bool debug_;
396  ceres::Solver::Options options_;
397  std::shared_ptr<ceres::Grid2D<unsigned char>> costmap_grid_;
398 };
399 
400 } // namespace nav2_constrained_smoother
401 
402 #endif // NAV2_CONSTRAINED_SMOOTHER__SMOOTHER_HPP_
Smoother()
A constructor for nav2_smac_planner::Smoother.
Definition: smoother.hpp:49
bool smooth(std::vector< Eigen::Vector3d > &path, const Eigen::Vector2d &start_dir, const Eigen::Vector2d &end_dir, const nav2_costmap_2d::Costmap2D *costmap, const SmootherParams &params)
Smoother method.
Definition: smoother.hpp:89
void initialize(const OptimizerParams params)
Initialization of the smoother.
Definition: smoother.hpp:60
~Smoother()
A destructor for nav2_smac_planner::Smoother.
Definition: smoother.hpp:54
A 2D costmap provides a mapping between points in the world and their associated "costs".
Definition: costmap_2d.hpp:69
unsigned char * getCharMap() const
Will return a pointer to the underlying unsigned char array used as the costmap.
Definition: costmap_2d.cpp:259
unsigned int getSizeInCellsX() const
Accessor for the x size of the costmap in cells.
Definition: costmap_2d.cpp:546
unsigned int getSizeInCellsY() const
Accessor for the y size of the costmap in cells.
Definition: costmap_2d.cpp:551