212 lines
6.2 KiB
C++
212 lines
6.2 KiB
C++
// Copyright (c) 2023 Andrey Ryzhikov
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "nav2_costmap_2d/denoise_layer.hpp"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
#include "rclcpp/rclcpp.hpp"
|
|
|
|
namespace nav2_costmap_2d
|
|
{
|
|
|
|
void
|
|
DenoiseLayer::onInitialize()
|
|
{
|
|
// Enable/disable plugin
|
|
declareParameter("enabled", rclcpp::ParameterValue(true));
|
|
// Smaller groups should be filtered
|
|
declareParameter("minimal_group_size", rclcpp::ParameterValue(2));
|
|
// Pixels connectivity type
|
|
declareParameter("group_connectivity_type", rclcpp::ParameterValue(8));
|
|
|
|
const auto node = node_.lock();
|
|
|
|
if (!node) {
|
|
throw std::runtime_error("DenoiseLayer::onInitialize: Failed to lock node");
|
|
}
|
|
node->get_parameter(name_ + "." + "enabled", enabled_);
|
|
|
|
auto getInt = [&](const std::string & parameter_name) {
|
|
int param{};
|
|
node->get_parameter(name_ + "." + parameter_name, param);
|
|
return param;
|
|
};
|
|
|
|
const int minimal_group_size_param = getInt("minimal_group_size");
|
|
|
|
if (minimal_group_size_param <= 1) {
|
|
RCLCPP_WARN(
|
|
logger_,
|
|
"DenoiseLayer::onInitialize(): param minimal_group_size: %i."
|
|
" A value of 1 or less means that all map cells will be left as they are.",
|
|
minimal_group_size_param);
|
|
minimal_group_size_ = 1;
|
|
} else {
|
|
minimal_group_size_ = static_cast<size_t>(minimal_group_size_param);
|
|
}
|
|
|
|
const int group_connectivity_type_param = getInt("group_connectivity_type");
|
|
|
|
if (group_connectivity_type_param == 4) {
|
|
group_connectivity_type_ = ConnectivityType::Way4;
|
|
} else {
|
|
group_connectivity_type_ = ConnectivityType::Way8;
|
|
|
|
if (group_connectivity_type_param != 8) {
|
|
RCLCPP_WARN(
|
|
logger_, "DenoiseLayer::onInitialize(): param group_connectivity_type: %i."
|
|
" Possible values are 4 (neighbors pixels are connected horizontally and vertically) "
|
|
"or 8 (neighbors pixels are connected horizontally, vertically and diagonally)."
|
|
"The default value 8 will be used",
|
|
group_connectivity_type_param);
|
|
}
|
|
}
|
|
|
|
current_ = true;
|
|
}
|
|
|
|
void
|
|
DenoiseLayer::reset()
|
|
{
|
|
current_ = false;
|
|
}
|
|
|
|
bool
|
|
DenoiseLayer::isClearable()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void
|
|
DenoiseLayer::updateBounds(
|
|
double /*robot_x*/, double /*robot_y*/, double /*robot_yaw*/,
|
|
double * /*min_x*/, double * /*min_y*/,
|
|
double * /*max_x*/, double * /*max_y*/) {}
|
|
|
|
void
|
|
DenoiseLayer::updateCosts(
|
|
nav2_costmap_2d::Costmap2D & master_grid, int min_x, int min_y, int max_x, int max_y)
|
|
{
|
|
if (!enabled_) {
|
|
return;
|
|
}
|
|
|
|
if (min_x >= max_x || min_y >= max_y) {
|
|
return;
|
|
}
|
|
no_information_is_obstacle_ = master_grid.getDefaultValue() != NO_INFORMATION;
|
|
|
|
// wrap roi_image over existing costmap2d buffer
|
|
unsigned char * master_array = master_grid.getCharMap();
|
|
const int step = static_cast<int>(master_grid.getSizeInCellsX());
|
|
|
|
const size_t width = max_x - min_x;
|
|
const size_t height = max_y - min_y;
|
|
Image<uint8_t> roi_image(height, width, master_array + min_y * step + min_x, step);
|
|
|
|
try {
|
|
denoise(roi_image);
|
|
} catch (std::exception & ex) {
|
|
RCLCPP_ERROR(logger_, "%s", (std::string("Inner error: ") + ex.what()).c_str());
|
|
}
|
|
|
|
current_ = true;
|
|
}
|
|
|
|
void
|
|
DenoiseLayer::denoise(Image<uint8_t> & image) const
|
|
{
|
|
if (image.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (minimal_group_size_ <= 1) {
|
|
return; // A smaller group cannot exist. No one pixel will be changed
|
|
}
|
|
|
|
if (minimal_group_size_ == 2) {
|
|
// Performs fast filtration based on erosion function
|
|
removeSinglePixels(image);
|
|
} else {
|
|
// Performs a slower segmentation-based operation
|
|
removeGroups(image);
|
|
}
|
|
}
|
|
|
|
void
|
|
DenoiseLayer::removeGroups(Image<uint8_t> & image) const
|
|
{
|
|
groups_remover_.removeGroups(
|
|
image, buffer_, group_connectivity_type_, minimal_group_size_,
|
|
[this](uint8_t pixel) {return isBackground(pixel);});
|
|
}
|
|
|
|
void
|
|
DenoiseLayer::removeSinglePixels(Image<uint8_t> & image) const
|
|
{
|
|
// Building a map of 4 or 8-connected neighbors.
|
|
// The pixel of the map is 255 if there is an obstacle nearby
|
|
uint8_t * buf = buffer_.get<uint8_t>(image.rows() * image.columns());
|
|
Image<uint8_t> max_neighbors_image(image.rows(), image.columns(), buf, image.columns());
|
|
|
|
// If NO_INFORMATION (=255) isn't obstacle, we can't use a simple max() to check
|
|
// any obstacle nearby. In this case, we interpret NO_INFORMATION as an empty space.
|
|
if (!no_information_is_obstacle_) {
|
|
auto replace_to_free = [](uint8_t v) {
|
|
return v == NO_INFORMATION ? FREE_SPACE : v;
|
|
};
|
|
auto max = [&](const std::initializer_list<uint8_t> lst) {
|
|
std::array<uint8_t, 3> buf = {
|
|
replace_to_free(*lst.begin()),
|
|
replace_to_free(*(lst.begin() + 1)),
|
|
replace_to_free(*(lst.begin() + 2))
|
|
};
|
|
return *std::max_element(buf.begin(), buf.end());
|
|
};
|
|
dilate(image, max_neighbors_image, group_connectivity_type_, max);
|
|
} else {
|
|
auto max = [](const std::initializer_list<uint8_t> lst) {
|
|
return std::max(lst);
|
|
};
|
|
dilate(image, max_neighbors_image, group_connectivity_type_, max);
|
|
}
|
|
|
|
max_neighbors_image.convert(
|
|
image, [this](uint8_t maxNeighbor, uint8_t & img) {
|
|
if (!isBackground(img) && isBackground(maxNeighbor)) {
|
|
img = FREE_SPACE;
|
|
}
|
|
});
|
|
}
|
|
|
|
bool DenoiseLayer::isBackground(uint8_t pixel) const
|
|
{
|
|
bool is_obstacle =
|
|
pixel == LETHAL_OBSTACLE ||
|
|
pixel == INSCRIBED_INFLATED_OBSTACLE ||
|
|
(pixel == NO_INFORMATION && no_information_is_obstacle_);
|
|
return !is_obstacle;
|
|
}
|
|
|
|
} // namespace nav2_costmap_2d
|
|
|
|
// This is the macro allowing a DenoiseLayer class
|
|
// to be registered in order to be dynamically loadable of base type nav2_costmap_2d::Layer.
|
|
#include "pluginlib/class_list_macros.hpp"
|
|
PLUGINLIB_EXPORT_CLASS(nav2_costmap_2d::DenoiseLayer, nav2_costmap_2d::Layer)
|