15 #ifndef NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_
16 #define NAV2_COSTMAP_2D__DENOISE__IMAGE_PROCESSING_HPP_
60 T *
get(std::size_t count);
64 inline void allocate(
size_t bytes);
72 namespace imgproc_impl
75 class EquivalenceLabelTrees;
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);
82 using ShapeBuffer3x3 = std::array<uint8_t, 9>;
83 inline Image<uint8_t> createShape(ShapeBuffer3x3 & buffer,
ConnectivityType connectivity);
100 using namespace imgproc_impl;
101 ShapeBuffer3x3 shape_buffer;
103 morphologyOperation(input, output, shape, max_function);
130 template<ConnectivityType connectivity,
class Label,
class IsBg>
134 IsBg && is_background);
143 alignof(std::max_align_t) >=
alignof(T),
144 "T alignment is more than the fundamental alignment of the platform");
146 const size_t required_bytes =
sizeof(T) * count;
148 if (size_ < required_bytes) {
149 allocate(required_bytes);
151 return static_cast<T *
>(data_);
154 void MemoryBuffer::reset()
156 ::operator
delete(data_);
160 void MemoryBuffer::allocate(
size_t bytes)
163 data_ = ::operator
new(bytes);
167 namespace imgproc_impl
188 template<
class T,
class Bin>
190 histogram(
const Image<T> & image, T image_max, Bin bin_max)
195 std::vector<Bin> histogram(
size_t(image_max) + 1);
198 auto add_pixel_value = [&histogram, bin_max](T pixel) {
199 auto & h = histogram[pixel];
200 h = std::min(Bin(h + 1), bin_max);
203 image.forEach(add_pixel_value);
207 namespace out_of_bounds_policy
219 T & up(T * v)
const {
return *v;}
220 T & down(T * v)
const {
return *v;}
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} {}
249 if (up_row_start_ ==
nullptr) {
252 return replaceOutOfBounds(v, up_row_start_, up_row_end_);
261 return replaceOutOfBounds(v, down_row_start_, down_row_end_);
269 T & replaceOutOfBounds(T * v,
const T * begin,
const T * end)
271 if (v < begin || v >= end) {
277 const T * up_row_start_;
278 const T * up_row_end_;
279 const T * down_row_start_;
280 const T * down_row_end_;
297 template<
class T,
template<
class>
class Border>
307 inline Window(T * up_row, T * down_row, Border<T> border = {})
308 : up_row_{up_row}, down_row_{down_row}, border_{border} {}
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_;}
332 T * dropConst(
const T * ptr)
334 return const_cast<T *
>(ptr);
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)
355 dropConst(up_row) + offset, dropConst(down_row) + offset,
356 out_of_bounds_policy::ReplaceToZero<T>{up_row, down_row, columns}
369 Window<T, out_of_bounds_policy::DoNothing> makeUnsafeWindow(
const T * up_row,
const T * down_row)
371 return {dropConst(up_row), dropConst(down_row)};
382 : std::runtime_error(message) {}
392 template<
class Label>
405 const size_t max_labels_count = maxLabels(rows, columns, connectivity);
407 labels_size_ =
static_cast<Label
>(
408 std::min(max_labels_count,
size_t(std::numeric_limits<Label>::max()))
411 labels_.reserve(labels_size_);
415 labels_.push_back(Label{});
427 if (next_free_ == labels_size_) {
428 throw LabelOverflow(
"EquivalenceLabelTrees: Can't create new label");
430 labels_.push_back(next_free_);
443 Label root = findRoot(i);
446 Label root_j = findRoot(j);
447 root = std::min(root, root_j);
464 for (Label i = 1; i < next_free_; ++i) {
465 if (labels_[i] < i) {
466 labels_[i] = labels_[labels_[i]];
484 static size_t maxLabels(
const size_t rows,
const size_t columns,
ConnectivityType connectivity)
491 max_labels = (rows * columns) / 2 + 1;
502 max_labels = (rows * columns) / 3 + 1;
505 max_labels = std::min(max_labels,
size_t(std::numeric_limits<Label>::max()));
510 Label findRoot(Label i)
513 for (; labels_[root] < root; root = labels_[root]) { }
518 void setRoot(Label i, Label root)
520 while (labels_[i] < i) {
538 std::vector<Label> labels_;
539 Label labels_size_{};
544 template<ConnectivityType connectivity>
561 template<
class ImageWindow,
class LabelsWindow,
class Label,
class IsBg>
566 Label & current = label.e();
569 if (!is_bg(image.e())) {
573 if (!is_bg(image.c())) {
574 if (!is_bg(image.a())) {
575 current = eq_trees.
unionTrees(label.c(), label.a());
577 if (!is_bg(image.d())) {
578 current = eq_trees.
unionTrees(label.c(), label.d());
584 if (!is_bg(image.a())) {
587 if (!is_bg(image.d())) {
615 template<
class ImageWindow,
class LabelsWindow,
class Label,
class IsBg>
620 Label & current = label.e();
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());
631 if (!is_bg(image.d())) {
659 template<
class Apply>
663 const uint8_t * shape, Apply touch_fn)
665 const size_t rows = input.
rows() - std::max(first_input_row, first_output_row);
666 const size_t columns = input.
columns();
668 auto apply_shape = [&shape](uint8_t value, uint8_t index) -> uint8_t {
669 return value & shape[index];
672 auto get_input_row = [&input, first_input_row](
size_t row) {
673 return input.
row(row + first_input_row);
675 auto get_output_row = [&output, first_output_row](
size_t row) {
676 return output.
row(row + first_output_row);
680 for (
size_t i = 0; i < rows; ++i) {
682 auto overlay = {uint8_t(0), apply_shape(*get_input_row(i), 1), uint8_t(0)};
683 touch_fn(*get_output_row(i), overlay);
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);
693 auto overlay = {uint8_t(0), apply_shape(*in, 1), apply_shape(*(in + 1), 2)};
694 touch_fn(*out, overlay);
700 for (; in != last_column_pixel; ++in, ++out) {
702 apply_shape(*(in - 1), 0),
703 apply_shape(*(in), 1),
704 apply_shape(*(in + 1), 2)
706 touch_fn(*out, overlay);
711 auto overlay = {apply_shape(*(in - 1), 0), apply_shape(*(in), 1), uint8_t(0)};
712 touch_fn(*out, overlay);
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)
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");
744 if (shape.rows() != 3 || shape.columns() != 3) {
745 throw std::logic_error(
"morphologyOperation: wrong shape size");
753 auto set = [&](uint8_t & res, std::initializer_list<uint8_t> lst) {res = aggregate(lst);};
755 auto update = [&](uint8_t & res, std::initializer_list<uint8_t> lst) {
756 res = aggregate({res, aggregate(lst), 0});
763 probeRows(input, 0, output, 0, shape.row(1), set);
765 if (input.rows() > 1) {
770 probeRows(input, 0, output, 1, shape.row(0), update);
774 probeRows(input, 1, output, 0, shape.row(2), update);
782 Image<uint8_t> createShape(ShapeBuffer3x3 & buffer,
ConnectivityType connectivity)
789 static constexpr uint8_t u = 255;
790 static constexpr uint8_t i = 0;
803 return Image<uint8_t>(3, 3, buffer.data(), 3);
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)
815 using namespace imgproc_impl;
816 using PixelPass = ProcessPixel<connectivity>;
821 auto img = makeSafeWindow<uint8_t>(
nullptr, image.row(0), image.columns());
822 auto lbl = makeSafeWindow<Label>(
nullptr, labels.row(0), image.columns());
824 const uint8_t * first_row_end = image.row(0) + image.columns();
826 for (; img.anchor() < first_row_end; img.next(), lbl.next()) {
827 PixelPass::pass(img, lbl, label_trees, is_background);
832 for (
size_t row = 0; row < image.rows() - 1; ++row) {
834 Window<Label, out_of_bounds_policy::DoNothing> label_mask{labels.row(row), labels.row(row + 1)};
836 auto up = image.row(row);
837 auto current = image.row(row + 1);
841 auto img = makeSafeWindow(up, current, image.columns());
842 PixelPass::pass(img, label_mask, label_trees, is_background);
848 auto img = makeUnsafeWindow(std::next(up), std::next(current));
849 const uint8_t * current_row_last_element = current + image.columns() - 1;
851 for (; img.anchor() < current_row_last_element; img.next(), label_mask.next()) {
852 PixelPass::pass(img, label_mask, label_trees, is_background);
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);
866 const std::vector<Label> & labels_map = label_trees.getLabels();
873 return labels_map.size();
887 label_trees_ = std::make_unique<imgproc_impl::EquivalenceLabelTrees<uint16_t>>();
905 const IsBg & is_background)
const
908 removeGroupsPickLabelType<ConnectivityType::Way4>(
909 image, buffer, minimal_group_size,
912 removeGroupsPickLabelType<ConnectivityType::Way8>(
913 image, buffer, minimal_group_size,
926 template<ConnectivityType connectivity,
class IsBg>
927 void removeGroupsPickLabelType(
929 size_t minimal_group_size,
const IsBg & is_background)
const
936 success = tryRemoveGroupsWithLabelType<connectivity>(
937 image, buffer, minimal_group_size,
938 *label_trees16, is_background,
false);
943 dynamic_cast<imgproc_impl::EquivalenceLabelTrees<uint32_t> *
>(label_trees_.get());
945 if (!label_trees32) {
946 label_trees_ = std::make_unique<imgproc_impl::EquivalenceLabelTrees<uint32_t>>();
948 dynamic_cast<imgproc_impl::EquivalenceLabelTrees<uint32_t> *
>(label_trees_.get());
950 tryRemoveGroupsWithLabelType<connectivity>(
951 image, buffer, minimal_group_size, *label_trees32,
952 is_background,
true);
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
972 removeGroupsImpl<connectivity>(image, buffer, label_trees, minimal_group_size, is_background);
974 }
catch (imgproc_impl::LabelOverflow &) {
975 if (throw_on_label_overflow) {
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
990 auto labels = connectedComponents<connectivity>(
991 image, buffer, label_trees,
992 is_background, groups_count);
996 const Label max_label_value = groups_count - 1;
997 std::vector<size_t> groups_sizes = histogram(
998 labels, max_label_value,
size_t(minimal_group_size + 1));
1003 if (!groups_sizes.empty()) {
1004 groups_sizes.front() = 0;
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;
1014 groups_sizes.begin(), groups_sizes.end(), noise_labels_table.begin(),
1019 image, [&](Label src, uint8_t & trg) {
1020 if (!is_background(trg) && noise_labels_table[src]) {
1027 mutable std::unique_ptr<imgproc_impl::EquivalenceLabelTreesBase> label_trees_;
1032 template<ConnectivityType connectivity,
class Label,
class IsBg>
1034 const Image<uint8_t> & image, MemoryBuffer & buffer,
1035 imgproc_impl::EquivalenceLabelTrees<Label> & label_trees,
1036 const IsBg & is_background,
1037 Label & total_labels)
1039 using namespace imgproc_impl;
1040 const size_t pixels = image.rows() * image.columns();
1044 return Image<Label>{};
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,
Image with pixels of type T Сan own data, be a wrapper over some memory buffer, or refer to a fragmen...
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...