diff --git a/nav2_costmap_2d/CMakeLists.txt b/nav2_costmap_2d/CMakeLists.txt index 683cb04fdd..f039cf5d42 100644 --- a/nav2_costmap_2d/CMakeLists.txt +++ b/nav2_costmap_2d/CMakeLists.txt @@ -87,6 +87,7 @@ add_library(layers SHARED src/observation_buffer.cpp plugins/voxel_layer.cpp plugins/range_sensor_layer.cpp + plugins/denoise_layer.cpp ) ament_target_dependencies(layers ${dependencies} diff --git a/nav2_costmap_2d/costmap_plugins.xml b/nav2_costmap_2d/costmap_plugins.xml index e5867d5108..6748cd5fca 100644 --- a/nav2_costmap_2d/costmap_plugins.xml +++ b/nav2_costmap_2d/costmap_plugins.xml @@ -15,6 +15,9 @@ A range-sensor (sonar, IR) based obstacle layer for costmap_2d + + Filters noise-induced freestanding obstacles or small obstacles groups + diff --git a/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image.hpp b/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image.hpp new file mode 100644 index 0000000000..db7ae8fce8 --- /dev/null +++ b/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image.hpp @@ -0,0 +1,207 @@ +// 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. + +#ifndef NAV2_COSTMAP_2D__DENOISE__IMAGE_HPP_ +#define NAV2_COSTMAP_2D__DENOISE__IMAGE_HPP_ + +#include +#include + +namespace nav2_costmap_2d +{ + +/** + * @brief Image with pixels of type T + * Сan own data, be a wrapper over some memory buffer, or refer to a fragment of another image + * Pixels of one row are stored continuity. But rows continuity is not guaranteed. + * The distance (number of elements of type T) from row(i) to row(i + 1) is equal to step() + * @tparam T type of pixel + */ +template +class Image +{ +public: + /// @brief Create empty image + Image() = default; + + /** + * @brief Create image referencing to a third-party buffer + * @param rows number of image rows + * @param columns number of image columns + * @param data existing memory buffer with size at least rows * columns + * @param step offset from row(i) to row(i + 1) in memory buffer (number of elements of type T). + * offset = columns if rows are stored continuity + */ + Image(size_t rows, size_t columns, T * data, size_t step); + + /** + * @brief Create image referencing to the other + * Share image data between new and old object. + * Changing data in a new object will affect the given one and vice versa + */ + Image(Image & other); + + /** + * @brief Create image from the other (move constructor) + */ + Image(Image && other) noexcept; + + /// @return number of image rows + size_t rows() const {return rows_;} + + /// @return number of image columns + size_t columns() const {return columns_;} + + /// @return true if image empty + bool empty() const {return rows_ == 0 || columns_ == 0;} + + /// @return offset (number of elements of type T) from row(i) to row(i + 1) + size_t step() const {return step_;} + + /** + * @return pointer to first pixel of row + * @warning If row >= rows(), the behavior is undefined + */ + T * row(size_t row); + + /// @overload + const T * row(size_t row) const; + + /** + * @brief Read (and modify, if need) each pixel sequentially + * @tparam Functor function object. + * Signature should be equivalent to the following: + * void fn(T& pixel) or void fn(const T& pixel) + * @param fn a function that will be applied to each pixel in the image. Can modify image data + */ + template + void forEach(Functor && fn); + + /** + * @brief Read each pixel sequentially + * @tparam Functor function object. + * Signature should be equivalent to the following: + * void fn(const T& pixel) + * @param fn a function that will be applied to each pixel in the image + */ + template + void forEach(Functor && fn) const; + /** + * @brief Convert each pixel to corresponding pixel of target using a custom function + * + * The source and target must be the same size. + * For calculation of new target value the operation can use source value and + * an optionally current target value. + * This function call operation(this[i, j], target[i, j]) for each pixel + * where target[i, j] is mutable + * @tparam TargetElement type of target pixel + * @tparam Converter function object. + * Signature should be equivalent to the following: + * void fn(const T& src, TargetElement& trg) + * @param target output image with TargetElement-type pixels + * @param operation the binary operation op is applied to pairs of pixels: + * first (const) from source and second (mutable) from target + * @throw std::logic_error if the source and target of different sizes + */ + template + void convert(Image & target, Converter && converter) const; + +private: + T * data_start_{}; + size_t rows_{}; + size_t columns_{}; + size_t step_{}; +}; + +template +Image::Image(size_t rows, size_t columns, T * data, size_t step) +: rows_{rows}, columns_{columns}, step_{step} +{ + data_start_ = data; +} + +template +Image::Image(Image & other) +: data_start_{other.data_start_}, + rows_{other.rows_}, columns_{other.columns_}, step_{other.step_} {} + +template +Image::Image(Image && other) noexcept +: data_start_{other.data_start_}, + rows_{other.rows_}, columns_{other.columns_}, step_{other.step_} {} + +template +T * Image::row(size_t row) +{ + return const_cast( static_cast &>(*this).row(row) ); +} + +template +const T * Image::row(size_t row) const +{ + return data_start_ + row * step_; +} + +template +template +void Image::forEach(Functor && fn) +{ + static_cast &>(*this).forEach( + [&](const T & pixel) { + fn(const_cast(pixel)); + }); +} + +template +template +void Image::forEach(Functor && fn) const +{ + const T * rowPtr = row(0); + + for (size_t row = 0; row < rows(); ++row) { + const T * rowEnd = rowPtr + columns(); + + for (const T * pixel = rowPtr; pixel != rowEnd; ++pixel) { + fn(*pixel); + } + rowPtr += step(); + } +} + +template +template +void Image::convert(Image & target, Converter && converter) const +{ + if (rows() != target.rows() || columns() != target.columns()) { + throw std::logic_error("Image::convert. The source and target images size are different"); + } + const T * source_row = row(0); + TargetElement * target_row = target.row(0); + + for (size_t row = 0; row < rows(); ++row) { + const T * rowInEnd = source_row + columns(); + const T * src = source_row; + TargetElement * trg = target_row; + + for (; src != rowInEnd; ++src, ++trg) { + converter(*src, *trg); + } + source_row += step(); + target_row += target.step(); + } +} + +} // namespace nav2_costmap_2d + +#endif // NAV2_COSTMAP_2D__DENOISE__IMAGE_HPP_ diff --git a/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image_processing.hpp b/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image_processing.hpp new file mode 100644 index 0000000000..ee79894847 --- /dev/null +++ b/nav2_costmap_2d/include/nav2_costmap_2d/denoise/image_processing.hpp @@ -0,0 +1,1051 @@ +// 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. + +#ifndef NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_ +#define NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_ + +#include "image.hpp" +#include +#include +#include +#include +#include + +namespace nav2_costmap_2d +{ + +/** + * @enum nav2_costmap_2d::ConnectivityType + * @brief Describes the type of pixel connectivity (is the way in which + * pixels in image relate to their neighbors) + */ +enum class ConnectivityType : int +{ + /// neighbors pixels are connected horizontally and vertically + Way4 = 4, + /// neighbors pixels are connected horizontally, vertically and diagonally + Way8 = 8 +}; + +/** + * @brief A memory buffer that can grow to an upper-bounded capacity + */ +class MemoryBuffer +{ +public: + /// @brief Free memory allocated for the buffer + inline ~MemoryBuffer() {reset();} + /** + * @brief Return a pointer to an uninitialized array of count elements + * Delete the old block of memory and allocates a new one if the size of the old is too small. + * The returned pointer is valid until the next call to get() or destructor. + * @tparam T type of element + * @param count number of elements + * @throw std::bad_alloc or any other exception thrown by allocator + */ + template + T * get(std::size_t count); + +private: + inline void reset(); + inline void allocate(size_t bytes); + +private: + void * data_{}; + size_t size_{}; +}; + +// forward declarations +namespace imgproc_impl +{ +template +class EquivalenceLabelTrees; + +template +void morphologyOperation( + const Image & input, Image & output, + const Image & shape, AggregateFn aggregate); + +using ShapeBuffer3x3 = std::array; +inline Image createShape(ShapeBuffer3x3 & buffer, ConnectivityType connectivity); +} // namespace imgproc_impl + +/** + * @brief Perform morphological dilation + * @tparam Max function object + * @param input input image + * @param output output image + * @param connectivity selector for selecting structuring element (Way4-> cross, Way8-> rect) + * @param max_function takes as input std::initializer_list with three elements. + * Returns the greatest value in list + */ +template +inline void dilate( + const Image & input, Image & output, + ConnectivityType connectivity, Max && max_function) +{ + using namespace imgproc_impl; + ShapeBuffer3x3 shape_buffer; + Image shape = createShape(shape_buffer, connectivity); + morphologyOperation(input, output, shape, max_function); +} + +/** +* @brief Compute the connected components labeled image of binary image +* Implements the SAUF algorithm +* (Two Strategies to Speed up Connected Component Labeling Algorithms +* Kesheng Wu, Ekow Otoo, Kenji Suzuki). +* @tparam connectivity pixels connectivity type +* @tparam Label integer type of label +* @tparam IsBg functor with signature bool (uint8_t) +* @param image input image +* @param buffer memory block that will be used to store the result (labeled image) +* and the internal buffer for labels trees +* @param label_trees union-find data structure +* @param is_background returns true if the passed pixel value is background +* @throw LabelOverflow if all possible values of the Label type are used and +* it is impossible to create a new unique +* @return pair(labeled image, total number of labels) +* Labeled image has the same size as image. Label 0 represents the background label, +* labels [1, - 1] - separate components. +* Total number of labels == 0 for empty image. +* In other cases, label 0 is always counted, +* even if there is no background in the image. +* For example, for an image of one background pixel, the total number of labels == 2. +* Two labels (0, 1) have been counted, although label 0 is not used) +*/ +template +std::pair, Label> connectedComponents( + const Image & image, MemoryBuffer & buffer, + imgproc_impl::EquivalenceLabelTrees