Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
image_processing.hpp
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 #ifndef NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_
16 #define NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_
17 
18 #include "image.hpp"
19 #include <algorithm>
20 #include <vector>
21 #include <array>
22 #include <memory>
23 #include <limits>
24 #include <string>
25 #include <utility>
26 
27 namespace nav2_costmap_2d
28 {
29 
35 enum class ConnectivityType : int
36 {
38  Way4 = 4,
40  Way8 = 8
41 };
42 
47 {
48 public:
50  inline ~MemoryBuffer() {reset();}
59  template<class T>
60  T * get(std::size_t count);
61 
62 private:
63  inline void reset();
64  inline void allocate(size_t bytes);
65 
66 private:
67  void * data_{};
68  size_t size_{};
69 };
70 
71 // forward declarations
72 namespace imgproc_impl
73 {
74 template<class Label>
75 class EquivalenceLabelTrees;
76 
77 template<class AggregateFn>
78 void morphologyOperation(
79  const Image<uint8_t> & input, Image<uint8_t> & output,
80  const Image<uint8_t> & shape, AggregateFn aggregate);
81 
82 using ShapeBuffer3x3 = std::array<uint8_t, 9>; // NOLINT
83 inline Image<uint8_t> createShape(ShapeBuffer3x3 & buffer, ConnectivityType connectivity);
84 } // namespace imgproc_impl
85 
95 template<class Max>
96 inline void dilate(
97  const Image<uint8_t> & input, Image<uint8_t> & output,
98  ConnectivityType connectivity, Max && max_function)
99 {
100  using namespace imgproc_impl; // NOLINT
101  ShapeBuffer3x3 shape_buffer;
102  Image<uint8_t> shape = createShape(shape_buffer, connectivity);
103  morphologyOperation(input, output, shape, max_function);
104 }
105 
130 template<ConnectivityType connectivity, class Label, class IsBg>
131 std::pair<Image<Label>, Label> connectedComponents(
132  const Image<uint8_t> & image, MemoryBuffer & buffer,
134  IsBg && is_background);
135 
136 // Implementation
137 
138 template<class T>
139 T * MemoryBuffer::get(std::size_t count)
140 {
141  // Check the memory allocated by ::operator new can be used to store the type T
142  static_assert(
143  alignof(std::max_align_t) >= alignof(T),
144  "T alignment is more than the fundamental alignment of the platform");
145 
146  const size_t required_bytes = sizeof(T) * count;
147 
148  if (size_ < required_bytes) {
149  allocate(required_bytes);
150  }
151  return static_cast<T *>(data_);
152 }
153 
154 void MemoryBuffer::reset()
155 {
156  ::operator delete(data_);
157  size_ = 0;
158 }
159 
160 void MemoryBuffer::allocate(size_t bytes)
161 {
162  reset();
163  data_ = ::operator new(bytes);
164  size_ = bytes;
165 }
166 
167 namespace imgproc_impl
168 {
169 
188 template<class T, class Bin>
189 std::vector<Bin>
190 histogram(const Image<T> & image, T image_max, Bin bin_max)
191 {
192  if (image.empty()) {
193  return {};
194  }
195  std::vector<Bin> histogram(size_t(image_max) + 1);
196 
197  // Increases the bin value corresponding to the pixel by one
198  auto add_pixel_value = [&histogram, bin_max](T pixel) {
199  auto & h = histogram[pixel];
200  h = std::min(Bin(h + 1), bin_max);
201  };
202 
203  image.forEach(add_pixel_value);
204  return histogram;
205 }
206 
207 namespace out_of_bounds_policy
208 {
209 
216 template<class T>
217 struct DoNothing
218 {
219  T & up(T * v) const {return *v;}
220  T & down(T * v) const {return *v;}
221 };
222 
229 template<class T>
231 {
232 public:
239  ReplaceToZero(const T * up_row_start, const T * down_row_start, size_t columns)
240  : up_row_start_{up_row_start}, up_row_end_{up_row_start + columns},
241  down_row_start_{down_row_start}, down_row_end_{down_row_start + columns} {}
242 
247  T & up(T * v)
248  {
249  if (up_row_start_ == nullptr) {
250  return zero_;
251  }
252  return replaceOutOfBounds(v, up_row_start_, up_row_end_);
253  }
254 
259  T & down(T * v)
260  {
261  return replaceOutOfBounds(v, down_row_start_, down_row_end_);
262  }
263 
264 private:
269  T & replaceOutOfBounds(T * v, const T * begin, const T * end)
270  {
271  if (v < begin || v >= end) {
272  return zero_;
273  }
274  return *v;
275  }
276 
277  const T * up_row_start_;
278  const T * up_row_end_;
279  const T * down_row_start_;
280  const T * down_row_end_;
281  T zero_{};
282 };
283 
284 } // namespace out_of_bounds_policy
285 
297 template<class T, template<class> class Border>
298 class Window
299 {
300 public:
307  inline Window(T * up_row, T * down_row, Border<T> border = {})
308  : up_row_{up_row}, down_row_{down_row}, border_{border} {}
309 
310  inline T & a() {return border_.up(up_row_ - 1);}
311  inline T & b() {return border_.up(up_row_);}
312  inline T & c() {return border_.up(up_row_ + 1);}
313  inline T & d() {return border_.down(down_row_ - 1);}
314  inline T & e() {return *down_row_;}
315  inline const T * anchor() const {return down_row_;}
316 
318  inline void next()
319  {
320  ++up_row_;
321  ++down_row_;
322  }
323 
324 private:
325  T * up_row_;
326  T * down_row_;
327  Border<T> border_;
328 };
329 
331 template<class T>
332 T * dropConst(const T * ptr)
333 {
334  return const_cast<T *>(ptr);
335 }
336 
350 template<class T>
351 Window<T, out_of_bounds_policy::ReplaceToZero> makeSafeWindow(
352  const T * up_row, const T * down_row, size_t columns, size_t offset = 0)
353 {
354  return {
355  dropConst(up_row) + offset, dropConst(down_row) + offset,
356  out_of_bounds_policy::ReplaceToZero<T>{up_row, down_row, columns}
357  };
358 }
359 
368 template<class T>
369 Window<T, out_of_bounds_policy::DoNothing> makeUnsafeWindow(const T * up_row, const T * down_row)
370 {
371  return {dropConst(up_row), dropConst(down_row)};
372 }
373 
375 {
376  virtual ~EquivalenceLabelTreesBase() = default;
377 };
378 
379 struct LabelOverflow : public std::runtime_error
380 {
381  explicit LabelOverflow(const std::string & message)
382  : std::runtime_error(message) {}
383 };
384 
392 template<class Label>
394 {
395 public:
402  void reset(const size_t rows, const size_t columns, ConnectivityType connectivity)
403  {
404  // Trying to reserve memory with a margin
405  const size_t max_labels_count = maxLabels(rows, columns, connectivity);
406  // Number of labels cannot exceed std::numeric_limits<Label>::max()
407  labels_size_ = static_cast<Label>(
408  std::min(max_labels_count, size_t(std::numeric_limits<Label>::max()))
409  );
410 
411  labels_.reserve(labels_size_);
412 
413  // Label 0 is reserved for the background pixels, i.e. labels[0] is always 0
414  labels_.clear();
415  labels_.push_back(Label{});
416  next_free_ = 1;
417  }
418 
424  Label makeLabel()
425  {
426  // Check the next_free_ counter does not overflow.
427  if (next_free_ == labels_size_) {
428  throw LabelOverflow("EquivalenceLabelTrees: Can't create new label");
429  }
430  labels_.push_back(next_free_);
431  return next_free_++;
432  }
433 
441  Label unionTrees(Label i, Label j)
442  {
443  Label root = findRoot(i);
444 
445  if (i != j) {
446  Label root_j = findRoot(j);
447  root = std::min(root, root_j);
448  setRoot(j, root);
449  }
450  setRoot(i, root);
451  return root;
452  }
453 
461  const std::vector<Label> & getLabels()
462  {
463  Label k = 1;
464  for (Label i = 1; i < next_free_; ++i) {
465  if (labels_[i] < i) {
466  labels_[i] = labels_[labels_[i]];
467  } else {
468  labels_[i] = k;
469  ++k;
470  }
471  }
472  labels_.resize(k);
473  return labels_;
474  }
475 
476 private:
484  static size_t maxLabels(const size_t rows, const size_t columns, ConnectivityType connectivity)
485  {
486  size_t max_labels{};
487 
488  if (connectivity == ConnectivityType::Way4) {
489  /* The maximum of individual components will be reached in the chessboard image,
490  * where the white cells correspond to obstacle pixels */
491  max_labels = (rows * columns) / 2 + 1;
492  } else {
493  /* The maximum of individual components will be reached in image like this:
494  * x.x.x.x~
495  * .......~
496  * x.x.x.x~
497  * .......~
498  * x.x.x.x~
499  * ~
500  * where 'x' - pixel with obstacle, '.' - background pixel,
501  * '~' - row continuation in the same style */
502  max_labels = (rows * columns) / 3 + 1;
503  }
504  ++max_labels; // add zero label
505  max_labels = std::min(max_labels, size_t(std::numeric_limits<Label>::max()));
506  return max_labels;
507  }
508 
510  Label findRoot(Label i)
511  {
512  Label root = i;
513  for (; labels_[root] < root; root = labels_[root]) { /*do nothing*/}
514  return root;
515  }
516 
518  void setRoot(Label i, Label root)
519  {
520  while (labels_[i] < i) {
521  auto j = labels_[i];
522  labels_[i] = root;
523  i = j;
524  }
525  labels_[i] = root;
526  }
527 
528 private:
538  std::vector<Label> labels_;
539  Label labels_size_{};
540  Label next_free_{};
541 };
542 
544 template<ConnectivityType connectivity>
546 
548 template<>
550 {
561  template<class ImageWindow, class LabelsWindow, class Label, class IsBg>
562  static void pass(
563  ImageWindow & image, LabelsWindow & label, EquivalenceLabelTrees<Label> & eq_trees,
564  IsBg && is_bg)
565  {
566  Label & current = label.e();
567 
568  // The decision tree traversal. See reference article for details
569  if (!is_bg(image.e())) {
570  if (label.b()) {
571  current = label.b();
572  } else {
573  if (!is_bg(image.c())) {
574  if (!is_bg(image.a())) {
575  current = eq_trees.unionTrees(label.c(), label.a());
576  } else {
577  if (!is_bg(image.d())) {
578  current = eq_trees.unionTrees(label.c(), label.d());
579  } else {
580  current = label.c();
581  }
582  }
583  } else {
584  if (!is_bg(image.a())) {
585  current = label.a();
586  } else {
587  if (!is_bg(image.d())) {
588  current = label.d();
589  } else {
590  current = eq_trees.makeLabel();
591  }
592  }
593  }
594  }
595  } else {
596  current = 0;
597  }
598  }
599 };
600 
602 template<>
604 {
615  template<class ImageWindow, class LabelsWindow, class Label, class IsBg>
616  static void pass(
617  ImageWindow & image, LabelsWindow & label, EquivalenceLabelTrees<Label> & eq_trees,
618  IsBg && is_bg)
619  {
620  Label & current = label.e();
621 
622  // Simplified decision tree traversal. See reference article for details
623  if (!is_bg(image.e())) {
624  if (!is_bg(image.b())) {
625  if (!is_bg(image.d())) {
626  current = eq_trees.unionTrees(label.d(), label.b());
627  } else {
628  current = label.b();
629  }
630  } else {
631  if (!is_bg(image.d())) {
632  current = label.d();
633  } else {
634  current = eq_trees.makeLabel();
635  }
636  }
637  } else {
638  current = 0;
639  }
640  }
641 };
642 
659 template<class Apply>
660 void probeRows(
661  const Image<uint8_t> & input, size_t first_input_row,
662  Image<uint8_t> & output, size_t first_output_row,
663  const uint8_t * shape, Apply touch_fn)
664 {
665  const size_t rows = input.rows() - std::max(first_input_row, first_output_row);
666  const size_t columns = input.columns();
667 
668  auto apply_shape = [&shape](uint8_t value, uint8_t index) -> uint8_t {
669  return value & shape[index];
670  };
671 
672  auto get_input_row = [&input, first_input_row](size_t row) {
673  return input.row(row + first_input_row);
674  };
675  auto get_output_row = [&output, first_output_row](size_t row) {
676  return output.row(row + first_output_row);
677  };
678 
679  if (columns == 1) {
680  for (size_t i = 0; i < rows; ++i) {
681  // process single column. Interpret pixel from column -1 and 1 as 0
682  auto overlay = {uint8_t(0), apply_shape(*get_input_row(i), 1), uint8_t(0)};
683  touch_fn(*get_output_row(i), overlay);
684  }
685  } else {
686  for (size_t i = 0; i < rows; ++i) {
687  const uint8_t * in = get_input_row(i);
688  const uint8_t * last_column_pixel = in + columns - 1;
689  uint8_t * out = get_output_row(i);
690 
691  // process first column. Interpret pixel from column -1 as 0
692  {
693  auto overlay = {uint8_t(0), apply_shape(*in, 1), apply_shape(*(in + 1), 2)};
694  touch_fn(*out, overlay);
695  ++in;
696  ++out;
697  }
698 
699  // process next columns up to last
700  for (; in != last_column_pixel; ++in, ++out) {
701  auto overlay = {
702  apply_shape(*(in - 1), 0),
703  apply_shape(*(in), 1),
704  apply_shape(*(in + 1), 2)
705  };
706  touch_fn(*out, overlay);
707  }
708 
709  // process last column
710  {
711  auto overlay = {apply_shape(*(in - 1), 0), apply_shape(*(in), 1), uint8_t(0)};
712  touch_fn(*out, overlay);
713  ++in;
714  ++out;
715  }
716  }
717  }
718 }
719 
734 template<class AggregateFn>
735 void morphologyOperation(
736  const Image<uint8_t> & input, Image<uint8_t> & output,
737  const Image<uint8_t> & shape, AggregateFn aggregate)
738 {
739  if (input.rows() != output.rows() || input.columns() != output.columns()) {
740  throw std::logic_error(
741  "morphologyOperation: the sizes of the input and output images are different");
742  }
743 
744  if (shape.rows() != 3 || shape.columns() != 3) {
745  throw std::logic_error("morphologyOperation: wrong shape size");
746  }
747 
748  if (input.empty()) {
749  return;
750  }
751 
752  // Simple write the pixel of the output image (first pass only)
753  auto set = [&](uint8_t & res, std::initializer_list<uint8_t> lst) {res = aggregate(lst);};
754  // Update the pixel of the output image
755  auto update = [&](uint8_t & res, std::initializer_list<uint8_t> lst) {
756  res = aggregate({res, aggregate(lst), 0});
757  };
758 
759  // Apply the central shape row.
760  // This operation is applicable to all rows of the image,
761  // because at any position of the sliding window,
762  // its central row is located on the image. So we start from the zero line of input and output
763  probeRows(input, 0, output, 0, shape.row(1), set);
764 
765  if (input.rows() > 1) {
766  // Apply the top shape row.
767  // In the uppermost position of the sliding window, its first row is outside the image border.
768  // Therefore, we start filling the output image starting from the line 1 and will process
769  // input.rows() - 1 lines in total
770  probeRows(input, 0, output, 1, shape.row(0), update);
771  // Apply the bottom shape row.
772  // Similarly, the input image starting from the line 1 and will process
773  // input.rows() - 1 lines in total
774  probeRows(input, 1, output, 0, shape.row(2), update);
775  }
776 }
777 
782 Image<uint8_t> createShape(ShapeBuffer3x3 & buffer, ConnectivityType connectivity)
783 {
789  static constexpr uint8_t u = 255;
790  static constexpr uint8_t i = 0;
791 
792  if (connectivity == ConnectivityType::Way8) {
793  buffer = {
794  u, u, u,
795  u, i, u,
796  u, u, u};
797  } else {
798  buffer = {
799  i, u, i,
800  u, i, u,
801  i, u, i};
802  }
803  return Image<uint8_t>(3, 3, buffer.data(), 3);
804 }
805 
810 template<ConnectivityType connectivity, class Label, class IsBg>
811 Label connectedComponentsImpl(
812  const Image<uint8_t> & image, Image<Label> & labels,
813  imgproc_impl::EquivalenceLabelTrees<Label> & label_trees, const IsBg & is_background)
814 {
815  using namespace imgproc_impl; // NOLINT
816  using PixelPass = ProcessPixel<connectivity>; // NOLINT
817 
818  // scanning phase
819  // scan row 0
820  {
821  auto img = makeSafeWindow<uint8_t>(nullptr, image.row(0), image.columns());
822  auto lbl = makeSafeWindow<Label>(nullptr, labels.row(0), image.columns());
823 
824  const uint8_t * first_row_end = image.row(0) + image.columns();
825 
826  for (; img.anchor() < first_row_end; img.next(), lbl.next()) {
827  PixelPass::pass(img, lbl, label_trees, is_background);
828  }
829  }
830 
831  // scan rows 1, 2, ...
832  for (size_t row = 0; row < image.rows() - 1; ++row) {
833  // we can safely ignore checks label_mask for first column
834  Window<Label, out_of_bounds_policy::DoNothing> label_mask{labels.row(row), labels.row(row + 1)};
835 
836  auto up = image.row(row);
837  auto current = image.row(row + 1);
838 
839  // scan column 0
840  {
841  auto img = makeSafeWindow(up, current, image.columns());
842  PixelPass::pass(img, label_mask, label_trees, is_background);
843  }
844 
845  // scan columns 1, 2... image.columns() - 2
846  label_mask.next();
847 
848  auto img = makeUnsafeWindow(std::next(up), std::next(current));
849  const uint8_t * current_row_last_element = current + image.columns() - 1;
850 
851  for (; img.anchor() < current_row_last_element; img.next(), label_mask.next()) {
852  PixelPass::pass(img, label_mask, label_trees, is_background);
853  }
854 
855  // scan last column
856  if (image.columns() > 1) {
857  auto last_img = makeSafeWindow(up, current, image.columns(), image.columns() - 1);
858  auto last_label = makeSafeWindow(
859  labels.row(row), labels.row(row + 1),
860  image.columns(), image.columns() - 1);
861  PixelPass::pass(last_img, last_label, label_trees, is_background);
862  }
863  }
864 
865  // analysis phase
866  const std::vector<Label> & labels_map = label_trees.getLabels();
867 
868  // labeling phase
869  labels.forEach(
870  [&](Label & l) {
871  l = labels_map[l];
872  });
873  return labels_map.size();
874 }
875 
882 {
883 public:
886  {
887  label_trees_ = std::make_unique<imgproc_impl::EquivalenceLabelTrees<uint16_t>>();
888  }
889 
901  template<class IsBg>
903  Image<uint8_t> & image, MemoryBuffer & buffer,
904  ConnectivityType group_connectivity_type, size_t minimal_group_size,
905  const IsBg & is_background) const
906  {
907  if (group_connectivity_type == ConnectivityType::Way4) {
908  removeGroupsPickLabelType<ConnectivityType::Way4>(
909  image, buffer, minimal_group_size,
910  is_background);
911  } else {
912  removeGroupsPickLabelType<ConnectivityType::Way8>(
913  image, buffer, minimal_group_size,
914  is_background);
915  }
916  }
917 
918 private:
926  template<ConnectivityType connectivity, class IsBg>
927  void removeGroupsPickLabelType(
928  Image<uint8_t> & image, MemoryBuffer & buffer,
929  size_t minimal_group_size, const IsBg & is_background) const
930  {
931  bool success{};
932  auto label_trees16 =
933  dynamic_cast<imgproc_impl::EquivalenceLabelTrees<uint16_t> *>(label_trees_.get());
934 
935  if (label_trees16) {
936  success = tryRemoveGroupsWithLabelType<connectivity>(
937  image, buffer, minimal_group_size,
938  *label_trees16, is_background, false);
939  }
940 
941  if (!success) {
942  auto label_trees32 =
943  dynamic_cast<imgproc_impl::EquivalenceLabelTrees<uint32_t> *>(label_trees_.get());
944 
945  if (!label_trees32) {
946  label_trees_ = std::make_unique<imgproc_impl::EquivalenceLabelTrees<uint32_t>>();
947  label_trees32 =
948  dynamic_cast<imgproc_impl::EquivalenceLabelTrees<uint32_t> *>(label_trees_.get());
949  }
950  tryRemoveGroupsWithLabelType<connectivity>(
951  image, buffer, minimal_group_size, *label_trees32,
952  is_background, true);
953  }
954  }
963  template<ConnectivityType connectivity, class Label, class IsBg>
964  bool tryRemoveGroupsWithLabelType(
965  Image<uint8_t> & image, MemoryBuffer & buffer, size_t minimal_group_size,
966  imgproc_impl::EquivalenceLabelTrees<Label> & label_trees,
967  const IsBg & is_background,
968  bool throw_on_label_overflow) const
969  {
970  bool success{};
971  try {
972  removeGroupsImpl<connectivity>(image, buffer, label_trees, minimal_group_size, is_background);
973  success = true;
974  } catch (imgproc_impl::LabelOverflow &) {
975  if (throw_on_label_overflow) {
976  throw;
977  }
978  }
979  return success;
980  }
982  template<ConnectivityType connectivity, class Label, class IsBg>
983  void removeGroupsImpl(
984  Image<uint8_t> & image, MemoryBuffer & buffer,
985  imgproc_impl::EquivalenceLabelTrees<Label> & label_trees, size_t minimal_group_size,
986  const IsBg & is_background) const
987  {
988  // Creates an image labels in which each obstacles group is labeled with a unique code
989  Label groups_count;
990  auto labels = connectedComponents<connectivity>(
991  image, buffer, label_trees,
992  is_background, groups_count);
993 
994  // Calculates the size of each group.
995  // Group size is equal to the number of pixels with the same label
996  const Label max_label_value = groups_count - 1; // It's safe. groups_count always non-zero
997  std::vector<size_t> groups_sizes = histogram(
998  labels, max_label_value, size_t(minimal_group_size + 1));
999 
1000  // The group of pixels labeled 0 corresponds to empty map cells.
1001  // Zero bin of the histogram is equal to the number of pixels in this group.
1002  // Because the values of empty map cells should not be changed, we will reset this bin
1003  if (!groups_sizes.empty()) {
1004  groups_sizes.front() = 0; // don't change image background value
1005  }
1006 
1007 
1008  // noise_labels_table[i] = true if group with label i is noise
1009  std::vector<bool> noise_labels_table(groups_sizes.size());
1010  auto transform_fn = [&minimal_group_size](size_t bin_value) {
1011  return bin_value < minimal_group_size;
1012  };
1013  std::transform(
1014  groups_sizes.begin(), groups_sizes.end(), noise_labels_table.begin(),
1015  transform_fn);
1016 
1017  // Replace the pixel values from the small groups to background code
1018  labels.convert(
1019  image, [&](Label src, uint8_t & trg) {
1020  if (!is_background(trg) && noise_labels_table[src]) {
1021  trg = 0;
1022  }
1023  });
1024  }
1025 
1026 private:
1027  mutable std::unique_ptr<imgproc_impl::EquivalenceLabelTreesBase> label_trees_;
1028 };
1029 
1030 } // namespace imgproc_impl
1031 
1032 template<ConnectivityType connectivity, class Label, class IsBg>
1033 Image<Label> connectedComponents(
1034  const Image<uint8_t> & image, MemoryBuffer & buffer,
1035  imgproc_impl::EquivalenceLabelTrees<Label> & label_trees,
1036  const IsBg & is_background,
1037  Label & total_labels)
1038 {
1039  using namespace imgproc_impl; // NOLINT
1040  const size_t pixels = image.rows() * image.columns();
1041 
1042  if (pixels == 0) {
1043  total_labels = 0;
1044  return Image<Label>{};
1045  }
1046 
1047  Label * image_buffer = buffer.get<Label>(pixels);
1048  Image<Label> labels(image.rows(), image.columns(), image_buffer, image.columns());
1049  label_trees.reset(image.rows(), image.columns(), connectivity);
1050  total_labels = connectedComponentsImpl<connectivity>(
1051  image, labels, label_trees,
1052  is_background);
1053  return labels;
1054 }
1055 
1056 } // namespace nav2_costmap_2d
1057 
1058 #endif // NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_
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
T * row(size_t row)
Definition: image.hpp:145
size_t columns() const
Definition: image.hpp:64
size_t rows() const
Definition: image.hpp:61
A memory buffer that can grow to an upper-bounded capacity.
~MemoryBuffer()
Free memory allocated for the buffer.
T * get(std::size_t count)
Return a pointer to an uninitialized array of count elements Delete the old block of memory and alloc...
Union-find data structure Implementation of union-find data structure, described in reference article...
void reset(const size_t rows, const size_t columns, ConnectivityType connectivity)
Reset labels tree to initial state.
Label unionTrees(Label i, Label j)
Unite the two trees containing nodes i and j and return the new root See union function in reference ...
const std::vector< Label > & getLabels()
Convert union-find trees to labels lookup table.
Label makeLabel()
Creates new next unused label and returns it back.
Object to eliminate grouped noise on the image Stores a label tree that is reused.
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...
GroupsRemover()
Constructs the object and initializes the label tree.
Forward scan mask sliding window Provides an interface for access to neighborhood of the current pixe...
Window(T *up_row, T *down_row, Border< T > border={})
void next()
Shifts the window to the right.
Boundary case object. Used as parameter of class Window. Dereferences a pointer to a existing pixel....
ReplaceToZero(const T *up_row_start, const T *down_row_start, size_t columns)
Create an object that will replace pointers outside the specified range.
T & down(T *v)
Return ref to pixel or to zero value if the pointer is out of bounds.
T & up(T *v)
Return ref to pixel or to zero value if up_row_start_ is nullptr or the pointer is out of bounds.
@ 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.
std::pair< Image< Label >, Label > connectedComponents(const Image< uint8_t > &image, MemoryBuffer &buffer, imgproc_impl::EquivalenceLabelTrees< Label > &label_trees, IsBg &&is_background)
Compute the connected components labeled image of binary image Implements the SAUF algorithm (Two Str...
static void pass(ImageWindow &image, LabelsWindow &label, EquivalenceLabelTrees< Label > &eq_trees, IsBg &&is_bg)
Set the label of the current pixel image.e() based on labels in its neighborhood.
static void pass(ImageWindow &image, LabelsWindow &label, EquivalenceLabelTrees< Label > &eq_trees, IsBg &&is_bg)
Set the label of the current pixel image.e() based on labels in its neighborhood.
The specializations of this class provide the definition of the pixel label.
Boundary case object stub. Used as parameter of class Window. Dereferences a pointer to a pixel witho...