Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
denoise_layer.cpp
1 // Copyright (c) 2023 Andrey Ryzhikov
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 #include "nav2_costmap_2d/denoise_layer.hpp"
16 
17 #include <string>
18 #include <vector>
19 #include <algorithm>
20 #include <memory>
21 
22 #include "rclcpp/rclcpp.hpp"
23 
24 namespace nav2_costmap_2d
25 {
26 
27 void
29 {
30  const auto node = node_.lock();
31 
32  if (!node) {
33  throw std::runtime_error("DenoiseLayer::onInitialize: Failed to lock node");
34  }
35  enabled_ = node->declare_or_get_parameter(name_ + "." + "enabled", true);
36  // Smaller groups should be filtered
37  const int minimal_group_size_param = node->declare_or_get_parameter(
38  name_ + "." + "minimal_group_size", 2);
39  // Pixels connectivity type
40  const int group_connectivity_type_param = node->declare_or_get_parameter(
41  name_ + "." + "group_connectivity_type", 8);
42 
43  if (minimal_group_size_param <= 1) {
44  RCLCPP_WARN(
45  logger_,
46  "DenoiseLayer::onInitialize(): param minimal_group_size: %i."
47  " A value of 1 or less means that all map cells will be left as they are.",
48  minimal_group_size_param);
49  minimal_group_size_ = 1;
50  } else {
51  minimal_group_size_ = static_cast<size_t>(minimal_group_size_param);
52  }
53 
54  if (group_connectivity_type_param == 4) {
55  group_connectivity_type_ = ConnectivityType::Way4;
56  } else {
57  group_connectivity_type_ = ConnectivityType::Way8;
58 
59  if (group_connectivity_type_param != 8) {
60  RCLCPP_WARN(
61  logger_, "DenoiseLayer::onInitialize(): param group_connectivity_type: %i."
62  " Possible values are 4 (neighbors pixels are connected horizontally and vertically) "
63  "or 8 (neighbors pixels are connected horizontally, vertically and diagonally)."
64  "The default value 8 will be used",
65  group_connectivity_type_param);
66  }
67  }
68 
69  setCurrent(true);
70 }
71 
72 void
74 {
75  setCurrent(false);
76 }
77 
78 bool
80 {
81  return false;
82 }
83 
84 void
86  double /*robot_x*/, double /*robot_y*/, double /*robot_yaw*/,
87  double * /*min_x*/, double * /*min_y*/,
88  double * /*max_x*/, double * /*max_y*/) {}
89 
90 void
92  nav2_costmap_2d::Costmap2D & master_grid, int min_x, int min_y, int max_x, int max_y)
93 {
94  if (!enabled_) {
95  return;
96  }
97 
98  if (min_x >= max_x || min_y >= max_y) {
99  return;
100  }
101  no_information_is_obstacle_ = master_grid.getDefaultValue() != NO_INFORMATION;
102 
103  // wrap roi_image over existing costmap2d buffer
104  unsigned char * master_array = master_grid.getCharMap();
105  const int step = static_cast<int>(master_grid.getSizeInCellsX());
106 
107  const size_t width = max_x - min_x;
108  const size_t height = max_y - min_y;
109  Image<uint8_t> roi_image(height, width, master_array + min_y * step + min_x, step);
110 
111  try {
112  denoise(roi_image);
113  } catch (std::exception & ex) {
114  RCLCPP_ERROR(logger_, "%s", (std::string("Inner error: ") + ex.what()).c_str());
115  }
116 
117  setCurrent(true);
118 }
119 
120 void
121 DenoiseLayer::denoise(Image<uint8_t> & image) const
122 {
123  if (image.empty()) {
124  return;
125  }
126 
127  if (minimal_group_size_ <= 1) {
128  return; // A smaller group cannot exist. No one pixel will be changed
129  }
130 
131  if (minimal_group_size_ == 2) {
132  // Performs fast filtration based on erosion function
133  removeSinglePixels(image);
134  } else {
135  // Performs a slower segmentation-based operation
136  removeGroups(image);
137  }
138 }
139 
140 void
141 DenoiseLayer::removeGroups(Image<uint8_t> & image) const
142 {
143  groups_remover_.removeGroups(
144  image, buffer_, group_connectivity_type_, minimal_group_size_,
145  [this](uint8_t pixel) {return isBackground(pixel);});
146 }
147 
148 void
149 DenoiseLayer::removeSinglePixels(Image<uint8_t> & image) const
150 {
151  // Building a map of 4 or 8-connected neighbors.
152  // The pixel of the map is 255 if there is an obstacle nearby
153  uint8_t * buf = buffer_.get<uint8_t>(image.rows() * image.columns());
154  Image<uint8_t> max_neighbors_image(image.rows(), image.columns(), buf, image.columns());
155 
156  // If NO_INFORMATION (=255) isn't obstacle, we can't use a simple max() to check
157  // any obstacle nearby. In this case, we interpret NO_INFORMATION as an empty space.
158  if (!no_information_is_obstacle_) {
159  auto replace_to_free = [](uint8_t v) {
160  return v == NO_INFORMATION ? FREE_SPACE : v;
161  };
162  auto max = [&](const std::initializer_list<uint8_t> lst) {
163  std::array<uint8_t, 3> rbuf = {
164  replace_to_free(*lst.begin()),
165  replace_to_free(*(lst.begin() + 1)),
166  replace_to_free(*(lst.begin() + 2))
167  };
168  return *std::max_element(rbuf.begin(), rbuf.end());
169  };
170  dilate(image, max_neighbors_image, group_connectivity_type_, max);
171  } else {
172  auto max = [](const std::initializer_list<uint8_t> lst) {
173  return std::max(lst);
174  };
175  dilate(image, max_neighbors_image, group_connectivity_type_, max);
176  }
177 
178  max_neighbors_image.convert(
179  image, [this](uint8_t maxNeighbor, uint8_t & img) {
180  if (!isBackground(img) && isBackground(maxNeighbor)) {
181  img = FREE_SPACE;
182  }
183  });
184 }
185 
186 bool DenoiseLayer::isBackground(uint8_t pixel) const
187 {
188  bool is_obstacle =
189  pixel == LETHAL_OBSTACLE ||
190  pixel == INSCRIBED_INFLATED_OBSTACLE ||
191  (pixel == NO_INFORMATION && no_information_is_obstacle_);
192  return !is_obstacle;
193 }
194 
195 } // namespace nav2_costmap_2d
196 
197 // This is the macro allowing a DenoiseLayer class
198 // to be registered in order to be dynamically loadable of base type nav2_costmap_2d::Layer.
199 #include "pluginlib/class_list_macros.hpp"
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:260
unsigned int getSizeInCellsX() const
Accessor for the x size of the costmap in cells.
Definition: costmap_2d.cpp:548
unsigned char getDefaultValue()
Get the default background value of the costmap.
Definition: costmap_2d.hpp:309
Layer filters noise-induced standalone obstacles (white costmap pixels) or small obstacles groups.
void updateCosts(nav2_costmap_2d::Costmap2D &master_grid, int min_x, int min_y, int max_x, int max_y) override
Filters noise-induced obstacles in the selected region of the costmap The method is called when costm...
void onInitialize() override
Initializes the layer on startup This method is called at the end of plugin initialization....
void updateBounds(double robot_x, double robot_y, double robot_yaw, double *min_x, double *min_y, double *max_x, double *max_y) override
Reports that no expansion is required The method is called to ask the plugin: which area of costmap i...
bool isClearable() override
Reports that no clearing operation is required.
void reset() override
Reset this layer.
Image with pixels of type T Сan own data, be a wrapper over some memory buffer, or refer to a fragmen...
Definition: image.hpp:33
bool empty() const
Definition: image.hpp:67
Abstract class for layered costmap plugin implementations.
Definition: layer.hpp:60
void setCurrent(bool current)
Set whether the data in the layer is up to date.
Definition: layer.hpp:147
T * get(std::size_t count)
Return a pointer to an uninitialized array of count elements Delete the old block of memory and alloc...
void removeGroups(Image< uint8_t > &image, MemoryBuffer &buffer, ConnectivityType group_connectivity_type, size_t minimal_group_size, const IsBg &is_background) const
Calls removeGroupsPickLabelType with the Way4/Way8 template parameter based on the runtime value of g...
@ Way4
neighbors pixels are connected horizontally and vertically
@ Way8
neighbors pixels are connected horizontally, vertically and diagonally
void dilate(const Image< uint8_t > &input, Image< uint8_t > &output, ConnectivityType connectivity, Max &&max_function)
Perform morphological dilation.