Ocean
FrameFilterErosion.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  */
7 
8 #ifndef META_OCEAN_CV_FRAME_FILTER_EROSION_H
9 #define META_OCEAN_CV_FRAME_FILTER_EROSION_H
10 
11 #include "ocean/cv/CV.h"
14 #include "ocean/cv/PixelPosition.h"
15 
16 #include "ocean/base/Frame.h"
18 #include "ocean/base/RandomI.h"
19 #include "ocean/base/Worker.h"
20 
21 namespace Ocean
22 {
23 
24 namespace CV
25 {
26 
27 /**
28  * This class implements an erosion filter.
29  * @ingroup cv
30  */
31 class OCEAN_CV_EXPORT FrameFilterErosion : public FrameFilterMorphology
32 {
33  protected:
34 
35  /**
36  * Definition of an unordered set holding pixel positions.
37  */
38  typedef std::unordered_set<CV::PixelPosition, CV::PixelPosition> PixelPositionSet;
39 
40  public:
41 
42  /**
43  * The following comfort class provides comfortable functions simplifying prototyping applications but also increasing binary size of the resulting applications.
44  * Best practice is to avoid using these functions if binary size matters,<br>
45  * as for every comfort function a corresponding function exists with specialized functionality not increasing binary size significantly.<br>
46  */
47  class OCEAN_CV_EXPORT Comfort
48  {
49  public:
50 
51  /**
52  * Closes holes inside a frame using a shrinking/in-bleeding approach based on either a 4-neighborhood or an 8-neighborhood.
53  * The frame and mask will be changed during the filtering process.<br>
54  * Hole pixels are defined by a 0x00 mask value while non-hole pixels have 0xFF as musk value.
55  * Beware: Frame and mask must have the same frame dimension and pixel origin.
56  * @param frame The frame to be handled, must be valid
57  * @param mask The 8 bit mask defining the hole(s), must be valid
58  * @param erosionFilter Defines the shape of the neighborhood
59  * @param randomNoise Optional the maximal random noise (+/-) that will be added to each channel and pixel, with range [0, 255]
60  * @param randomSeed The random seed value to be used when generating random values, with range [0, infinity)
61  * @return True, if succeeded
62  */
63  static bool shrinkMask(Frame& frame, Frame& mask, const MorphologyFilter erosionFilter, const unsigned int randomNoise = 3u, const unsigned int randomSeed = RandomI::random32());
64 
65  /**
66  * Closes a hole inside an 8 bit grayscale frame using a randomized erosion filter.
67  * The frame and mask buffer will be changed during the filtering process.<br>
68  * Hole pixels must have 0x00 as grayscale value, all other pixels must have 0xFF as value.<br>
69  * Beware: Both frame must have the same frame dimension and pixel origin.
70  * @param frame The frame to be handled, must be valid
71  * @param mask The 8 bit mask defining the hole(s), must be valid
72  * @param erosionFilter Defines the shape of the erosion filter mask
73  * @param randomNoise Optional the maximal random noise (+/-) that will be added to each channel and pixel, with range [0, 255]
74  * @param randomSeed The random seed value to be used when generating random values, with range [0, infinity)
75  * @return True, if succeeded
76  */
77  static bool shrinkMaskRandom(Frame& frame, Frame& mask, const MorphologyFilter erosionFilter, const unsigned int randomNoise = 3u, const unsigned int randomSeed = RandomI::random32());
78  };
79 
80  /**
81  * Closes a hole inside a frame by using a shrinking/in-bleeding approach based on a 4-neighborhood.
82  * The frame and mask buffer will be changed during the filtering process.<br>
83  * Hole pixels are defined by a 0x00 mask value while non-hole pixels have 0xFF as musk value.
84  * @param frame The frame to be filtered, must be valid
85  * @param mask The 8 bit mask defining the hole, must be valid
86  * @param width The width of all frames in pixel, with range [2, infinity)
87  * @param height The height of all frames in pixel, with range [2, infinity)
88  * @param framePaddingElements The number of padding elements at the end of each frame row, in elements, with range [0, infinity)
89  * @param maskPaddingElements The number of padding elements at the end of each mask row, in elements, with range [0, infinity)
90  * @param randomNoise Optional the maximal random noise (+/-) that will be added to each channel and pixel, with range [0, 255]
91  * @param randomSeed The random seed value to be used when generating random values, with range [0, infinity)
92  * @return True, if succeeded
93  * @tparam tChannels The number of frame channels with range [1, infinity)
94  * @tparam tUseRandomNoise True, to add/apply random noise to each channel and pixel; False, to create a deterministic result
95  */
96  template <unsigned int tChannels, bool tUseRandomNoise>
97  static bool shrinkMask8BitPerChannel4Neighbor(uint8_t* frame, uint8_t* mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise = 0u, const unsigned int randomSeed = RandomI::random32());
98 
99  /**
100  * Closes a hole inside a frame by using a shrinking/in-bleeding approach based on an 8-neighborhood.
101  * The frame and mask buffer will be changed during the filtering process.<br>
102  * Hole pixels must have 0x00 as grayscale value, all other pixels must have 0xFF as value.<br>
103  * Beware: All three frame buffers must have the same size.
104  * @param frame The frame to be filtered, must be valid
105  * @param mask The 8 bit mask defining the hole, must be valid
106  * @param width The width of all frames in pixel, with range [1, infinity)
107  * @param height The height of all frames in pixel, with range [1, infinity)
108  * @param framePaddingElements The number of padding elements at the end of each frame row, in elements, with range [0, infinity)
109  * @param maskPaddingElements The number of padding elements at the end of each mask row, in elements, with range [0, infinity)
110  * @param randomNoise Optional the maximal random noise (+/-) that will be added to each channel and pixel, with range [0, 255]
111  * @param randomSeed The random seed value to be used when generating random values, with range [0, infinity)
112  * @return True, if succeeded
113  * @tparam tChannels The number of frame channels with range [1, infinity)
114  * @tparam tUseRandomNoise Optional random noise for each channel and pixel, with range [0, 255], 0 to create a deterministic result
115  */
116  template <unsigned int tChannels, bool tUseRandomNoise>
117  static bool shrinkMask8BitPerChannel8Neighbor(uint8_t* frame, uint8_t* mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise = 0u, const unsigned int randomSeed = RandomI::random32());
118 
119  /**
120  * Closes a hole inside a frame by using a randomized shrinking/in-bleeding approach based on an 8-neighborhood.
121  * The frame and mask buffer will be changed during the filtering process.<br>
122  * Hole pixels must have 0x00 as grayscale value, all other pixels must have 0xFF as value.
123  * @param frame The frame to be filtered, must be valid
124  * @param mask The 8 bit mask defining the hole, must be valid
125  * @param width The width of all frames in pixel, with range [2, infinity)
126  * @param height The height of all frames in pixel, with range [2, infinity)
127  * @param framePaddingElements The number of padding elements at the end of each frame row, in elements, with range [0, infinity)
128  * @param maskPaddingElements The number of padding elements at the end of each mask row, in elements, with range [0, infinity)
129  * @param randomNoise Optional the maximal random noise (+/-) that will be added to each channel and pixel, with range [0, 255]
130  * @param randomSeed The random seed value to be used when generating random values, with range [0, infinity)
131  * @tparam tChannels The number of frame channels with range [1, infinity)
132  */
133  template <unsigned int tChannels>
134  static void shrinkMaskRandom8BitPerChannel8Neighbor(uint8_t* frame, uint8_t* mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise = 0u, const unsigned int randomSeed = RandomI::random32());
135 
136  /**
137  * Applies several erosion filter iterations for an 8 bit mask image.
138  * The value of a mask pixel (to be eroded) can be defined, every other pixel value is interpreted as a non-mask pixels.
139  * @param mask The mask frame to be filtered, must be valid
140  * @param width The width of the mask frame in pixel, with range [4, infinity)
141  * @param height The height of the mask frame in pixel, with range [4, infinity)
142  * @param iterations Number of iterations to be applied, best performance when number of iterations is even, with range [1, infinity)
143  * @param maskValue The value of a mask pixel to be eroded, pixels with other values will be untouched, with range [0, 255]
144  * @param maskPaddingElements Optional number of padding elements at the end of each mask row, in elements, with range [0, infinity)
145  * @param worker Optional worker object to distribute the computation
146  * @tparam tErosionFilter The type of the erosion filter to be applied
147  * @see filter1Channel8Bit4Neighbor(), filter1Channel8Bit8Neighbor().
148  */
149  template <MorphologyFilter tErosionFilter>
150  static void filter1Channel8Bit(uint8_t* mask, const unsigned int width, const unsigned int height, const unsigned int iterations, const uint8_t maskValue = 0x00, const unsigned int maskPaddingElements = 0u, Worker* worker = nullptr);
151 
152  /**
153  * Applies one erosion filter iteration in an 8 bit mask image using a 4-neighborhood.
154  * The value of a mask pixel (to be eroded) can be defined, every other pixel value is interpreted as a non-mask pixels.
155  * @param mask The mask frame to be filtered, must be valid
156  * @param target The target frame receiving the filter response, must be valid
157  * @param width The width of the mask frame in pixel, with range [2, infinity)
158  * @param height The height of the mask frame in pixel, with range [2, infinity)
159  * @param maskValue The value of a mask pixel to be eroded, pixels with other values will be untouched, with range [0, 255]
160  * @param maskPaddingElements Optional number of padding elements at the end of each mask row, in elements, with range [0, infinity)
161  * @param targetPaddingElements Optional number of padding elements at the end of each target row, in elements, with range [0, infinity)
162  * @param worker Optional worker object to distribute the computation
163  */
164  static inline void filter1Channel8Bit4Neighbor(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue = 0x00, const unsigned int maskPaddingElements = 0u, const unsigned int targetPaddingElements = 0u, Worker* worker = nullptr);
165 
166  /**
167  * Applies one erosion filter iteration for an 8 bit mask image using a 8-neighborhood, a 3x3 square kernel.
168  * The value of a mask pixel (to be eroded) can be defined, every other pixel value is interpreted as a non-mask pixels.
169  * @param mask The mask frame to be filtered, must be valid
170  * @param target The target frame receiving the filter response, must be valid
171  * @param width The width of the mask frame in pixel, with range [2, infinity)
172  * @param height The height of the mask frame in pixel, with range [2, infinity)
173  * @param maskValue The value of a mask pixel to be eroded, pixels with other values will be untouched, with range [0, 255]
174  * @param maskPaddingElements Optional number of padding elements at the end of each mask row, in elements, with range [0, infinity)
175  * @param targetPaddingElements Optional number of padding elements at the end of each target row, in elements, with range [0, infinity)
176  * @param worker Optional worker object to distribute the computation
177  */
178  static inline void filter1Channel8Bit8Neighbor(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue = 0x00, const unsigned int maskPaddingElements = 0u, const unsigned int targetPaddingElements = 0u, Worker* worker = nullptr);
179 
180  /**
181  * Applies one erosion filter iteration for an 8 bit mask image using a 24-neighborhood, a 5x5 square kernel.
182  * The value of a mask pixel (to be eroded) can be defined, every other pixel value is interpreted as a non-mask pixels.
183  * @param mask The mask frame to be filtered, must be valid
184  * @param target The target frame receiving the filter response, must be valid
185  * @param width The width of the mask frame in pixel, with range [4, infinity)
186  * @param height The height of the mask frame in pixel, with range [4, infinity)
187  * @param maskValue The value of a mask pixel to be eroded, pixels with other values will be untouched, with range [0, 255]
188  * @param maskPaddingElements Optional number of padding elements at the end of each mask row, in elements, with range [0, infinity)
189  * @param targetPaddingElements Optional number of padding elements at the end of each target row, in elements, with range [0, infinity)
190  * @param worker Optional worker object to distribute the computation
191  */
192  static inline void filter1Channel8Bit24Neighbor(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue = 0x00, const unsigned int maskPaddingElements = 0u, const unsigned int targetPaddingElements = 0u, Worker* worker = nullptr);
193 
194  private:
195 
196  /**
197  * Applies one erosion filter iteration in a subset of an 8 bit mask image using a 4-neighborhood.
198  * @param mask The mask frame to be filtered, must be valid
199  * @param target The target frame receiving the filter response, must be valid
200  * @param width The width of the mask frame in pixel, with range [2, infinity)
201  * @param height The height of the mask frame in pixel, with range [2, infinity)
202  * @param maskValue The value of a mask pixel to be eroded, pixels with other values will be untouched, with range [0, 255]
203  * @param maskPaddingElements Optional number of padding elements at the end of each mask row, in elements, with range [0, infinity)
204  * @param targetPaddingElements Optional number of padding elements at the end of each target row, in elements, with range [0, infinity)
205  * @param firstRow First row to be handled, with range [0, height - 1]
206  * @param numberRows Number of rows to be handled, with range [1, height - firstRow]
207  */
208  static void filter1Channel8Bit4NeighborSubset(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, const unsigned int firstRow, const unsigned int numberRows);
209 
210  /**
211  * Applies one erosion filter iteration for an 8 bit mask image using a 8-neighborhood.
212  * @param mask The mask frame to be filtered, must be valid
213  * @param target The target frame receiving the filter response, must be valid
214  * @param width The width of the mask frame in pixel, with range [2, infinity)
215  * @param height The height of the mask frame in pixel, with range [2, infinity)
216  * @param maskValue The value of a mask pixel to be eroded, pixels with other values will be untouched, with range [0, 255]
217  * @param maskPaddingElements Optional number of padding elements at the end of each mask row, in elements, with range [0, infinity)
218  * @param targetPaddingElements Optional number of padding elements at the end of each target row, in elements, with range [0, infinity)
219  * @param firstRow First row to be handled, with range [0, height - 1]
220  * @param numberRows Number of rows to be handled, with range [1, height - firstRow]
221  */
222  static void filter1Channel8Bit8NeighborSubset(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, const unsigned int firstRow, const unsigned int numberRows);
223 
224  /**
225  * Applies one erosion filter iteration for an 8 bit mask image using a 24-neighborhood.
226  * @param mask The mask frame to be filtered, must be valid
227  * @param target The target frame receiving the filter response, must be valid
228  * @param width The width of the mask frame in pixel, with range [4, infinity)
229  * @param height The height of the mask frame in pixel, with range [4, infinity)
230  * @param maskValue The value of a mask pixel to be eroded, pixels with other values will be untouched, with range [0, 255]
231  * @param maskPaddingElements Optional number of padding elements at the end of each mask row, in elements, with range [0, infinity)
232  * @param targetPaddingElements Optional number of padding elements at the end of each target row, in elements, with range [0, infinity)
233  * @param firstRow First row to be handled, with range [0, height - 1]
234  * @param numberRows Number of rows to be handled, with range [1, height - firstRow]
235  */
236  static void filter1Channel8Bit24NeighborSubset(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, const unsigned int firstRow, const unsigned int numberRows);
237 
238  /**
239  * Returns whether at least one of several pixels in a row is not equal to a specified value.
240  * @param maskPixels The first pixel within the row to check, must be valid
241  * @param maskValue The mask value to check, with range [0, 255]
242  * @return True, if so
243  * @tparam tSize The number of pixels in a row to check, with range [1, infinity)
244  */
245  template <unsigned int tSize>
246  static OCEAN_FORCE_INLINE bool onePixelNotEqual(const uint8_t* const maskPixels, const uint8_t maskValue);
247 };
248 
249 template <unsigned int tChannels, bool tUseRandomNoise>
250 bool FrameFilterErosion::shrinkMask8BitPerChannel4Neighbor(uint8_t* frame, uint8_t* mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise, const unsigned int randomSeed)
251 {
252  static_assert(tChannels >= 1u, "Invalid channel number!");
253 
254  ocean_assert(frame != nullptr && mask != nullptr);
255  ocean_assert(width >= 2u && height >= 2u);
256  ocean_assert(randomNoise <= 255u);
257 
258  const unsigned int frameStrideElements = width * tChannels + framePaddingElements;
259  const unsigned int maskStrideElements = width + maskPaddingElements;
260 
261 #ifdef OCEAN_DEBUG
262  for (unsigned int y = 0u; y < height; ++y)
263  {
264  const uint8_t* maskRow = mask + y * maskStrideElements;
265 
266  for (unsigned int x = 0u; x < width; ++x)
267  {
268  ocean_assert(maskRow[x] == 0x00u || maskRow[x] == 0xFFu);
269  }
270  }
271 #endif // OCEAN_DEBUG
272 
273  RandomGenerator randomGenerator(randomSeed);
274 
275  /**
276  * O
277  * O X O
278  * O
279  */
280 
281  Frame intermediateMask(FrameType(width, height, FrameType::FORMAT_Y8, FrameType::ORIGIN_UPPER_LEFT), mask, Frame::CM_COPY_REMOVE_PADDING_LAYOUT, maskPaddingElements);
282  ocean_assert(intermediateMask.isContinuous());
283 
284  bool atLeastOnePixel = true;
285 
286  while (atLeastOnePixel)
287  {
288  atLeastOnePixel = false;
289 
290  const uint8_t* maskMiddle = mask;
291  const uint8_t* maskLower = mask + maskStrideElements;
292 
293  uint8_t* frameMiddle = frame;
294  const uint8_t* frameLower = frame + frameStrideElements;
295 
296  uint8_t* intermediateMaskMiddle = intermediateMask.data<uint8_t>();
297 
298  // upper left pixel, if the anchor pixel is inside the hole and at least one of the filter pixels is outside the hole
299  if (*maskMiddle == 0x00u && (*(maskMiddle + 1) != 0x00u || *maskLower != 0x00u))
300  {
301  const unsigned int weight = *(maskMiddle + 1) + *maskLower;
302  ocean_assert(weight > 0u && weight <= 0xFFu * 2u);
303 
304  for (unsigned int n = 0u; n < tChannels; ++n)
305  {
306  if constexpr (tUseRandomNoise)
307  {
308  ocean_assert(randomNoise != 0u);
309  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskMiddle + 1) * *(frameMiddle + tChannels + n) + *maskLower * *(frameLower + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
310  }
311  else
312  {
313  *(frameMiddle + n) = uint8_t((*(maskMiddle + 1) * *(frameMiddle + tChannels + n) + *maskLower * *(frameLower + n)) / weight);
314  }
315  }
316 
317  *intermediateMaskMiddle = 0xFFu;
318  atLeastOnePixel = true;
319  }
320 
321  frameMiddle += tChannels;
322  frameLower += tChannels;
323  ++maskMiddle;
324  ++maskLower;
325  ++intermediateMaskMiddle;
326 
327 
328  // upper row
329  for (unsigned int i = 1u; i < width - 1u; ++i)
330  {
331  // if the anchor pixel is inside the hole and at least one of the filter pixels is outside the hole
332  if (*maskMiddle == 0x00u && (*(maskMiddle - 1) != 0x00u || *(maskMiddle + 1) != 0x00u || *maskLower != 0x00u))
333  {
334  const unsigned int weight = *(maskMiddle - 1) + *(maskMiddle + 1) + *maskLower;
335  ocean_assert(weight > 0u && weight <= 0xFFu * 3u);
336 
337  for (unsigned int n = 0u; n < tChannels; ++n)
338  {
339  if constexpr (tUseRandomNoise)
340  {
341  ocean_assert(randomNoise != 0u);
342  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) + *maskLower * *(frameLower + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
343  }
344  else
345  {
346  *(frameMiddle + n) = uint8_t((*(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) + *maskLower * *(frameLower + n)) / weight);
347  }
348  }
349 
350  *intermediateMaskMiddle = 0xFFu;
351  atLeastOnePixel = true;
352  }
353 
354  frameMiddle += tChannels;
355  frameLower += tChannels;
356  ++maskMiddle;
357  ++maskLower;
358  ++intermediateMaskMiddle;
359  }
360 
361 
362  // upper right pixel, if the anchor pixel is inside the hole and at least one of the filter pixels is outside the hole
363  if (*maskMiddle == 0 && (*(maskMiddle - 1) != 0u || *maskLower != 0u))
364  {
365  const unsigned int weight = *(maskMiddle - 1) + *maskLower;
366  ocean_assert(weight > 0u && weight <= 0xFFu * 2u);
367 
368  for (unsigned int n = 0u; n < tChannels; ++n)
369  {
370  if constexpr (tUseRandomNoise)
371  {
372  ocean_assert(randomNoise != 0u);
373  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *maskLower * *(frameLower + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
374  }
375  else
376  {
377  *(frameMiddle + n) = uint8_t((*(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *maskLower * *(frameLower + n)) / weight);
378  }
379  }
380 
381  *intermediateMaskMiddle = 0xFFu;
382  atLeastOnePixel = true;
383  }
384 
385  frameMiddle += tChannels + framePaddingElements;
386  frameLower += tChannels + framePaddingElements;
387  maskMiddle += 1u + maskPaddingElements;
388  maskLower += 1u + maskPaddingElements;
389  ++intermediateMaskMiddle; // intermediate mask has no padding
390 
391  ocean_assert(frameMiddle == frame + frameStrideElements);
392  ocean_assert(frameLower == frame + frameStrideElements * 2u);
393  ocean_assert(maskMiddle == mask + maskStrideElements);
394  ocean_assert(maskLower == mask + maskStrideElements * 2u);
395  ocean_assert(intermediateMaskMiddle == intermediateMask.data<uint8_t>() + width);
396 
397 
398  // center rows
399 
400  const uint8_t* maskUpper = maskMiddle - maskStrideElements;
401  const uint8_t* frameUpper = frameMiddle - frameStrideElements;
402 
403  const uint8_t* const maskUpperEnd = mask + maskStrideElements * (height - 2u);
404 
405  while (maskUpper != maskUpperEnd)
406  {
407  // left pixel
408  if (*maskMiddle == 0x00u && (*maskUpper != 0x00u || *(maskMiddle + 1) != 0x00u || *maskLower != 0x00u))
409  {
410  const unsigned int weight = *maskUpper + *(maskMiddle + 1) + *maskLower;
411  ocean_assert(weight > 0u && weight <= 0xFFu * 3u);
412 
413  for (unsigned int n = 0u; n < tChannels; ++n)
414  {
415  if constexpr (tUseRandomNoise)
416  {
417  ocean_assert(randomNoise != 0u);
418  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*maskUpper * *(frameUpper + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) + *maskLower * *(frameLower + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
419  }
420  else
421  {
422  *(frameMiddle + n) = uint8_t((*maskUpper * *(frameUpper + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) + *maskLower * *(frameLower + n)) / weight);
423  }
424  }
425 
426  *intermediateMaskMiddle = 0xFFu;
427  atLeastOnePixel = true;
428  }
429 
430  frameUpper += tChannels;
431  frameMiddle += tChannels;
432  frameLower += tChannels;
433  ++maskUpper;
434  ++maskMiddle;
435  ++maskLower;
436  ++intermediateMaskMiddle;
437 
438 
439  // center pixels
440  for (unsigned int i = 1u; i < width - 1u; ++i)
441  {
442  if (*maskMiddle == 0x00u && (*maskUpper != 0x00u || *(maskMiddle - 1) != 0x00u || *(maskMiddle + 1) != 0x00u || *maskLower != 0x00u))
443  {
444  const unsigned int weight = *maskUpper + *(maskMiddle - 1) + *(maskMiddle + 1) + *maskLower;
445  ocean_assert(weight > 0u && weight <= 0xFFu * 4u);
446 
447  for (unsigned int n = 0u; n < tChannels; ++n)
448  {
449  if constexpr (tUseRandomNoise)
450  {
451  ocean_assert(randomNoise != 0u);
452  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*maskUpper * *(frameUpper + n) + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) + *maskLower * *(frameLower + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
453  }
454  else
455  {
456  *(frameMiddle + n) = uint8_t((*maskUpper * *(frameUpper + n) + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) + *maskLower * *(frameLower + n)) / weight);
457  }
458  }
459 
460  *intermediateMaskMiddle = 0xFFu;
461  atLeastOnePixel = true;
462  }
463 
464  frameUpper += tChannels;
465  frameMiddle += tChannels;
466  frameLower += tChannels;
467  ++maskUpper;
468  ++maskMiddle;
469  ++maskLower;
470  ++intermediateMaskMiddle;
471  }
472 
473 
474  // right pixel
475  if (*maskMiddle == 0x00u && (*maskUpper != 0x00u || *(maskMiddle - 1) != 0x00u || *maskLower != 0x00u))
476  {
477  const unsigned int weight = *maskUpper + *(maskMiddle - 1) + *maskLower;
478  ocean_assert(weight > 0u && weight <= 0xFFu * 3u);
479 
480  for (unsigned int n = 0u; n < tChannels; ++n)
481  {
482  if constexpr (tUseRandomNoise)
483  {
484  ocean_assert(randomNoise != 0u);
485  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*maskUpper * *(frameUpper + n) + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *maskLower * *(frameLower + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
486  }
487  else
488  {
489  *(frameMiddle + n) = uint8_t((*maskUpper * *(frameUpper + n) + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *maskLower * *(frameLower + n)) / weight);
490  }
491  }
492 
493  *intermediateMaskMiddle = 0xFFu;
494  atLeastOnePixel = true;
495  }
496 
497  frameUpper += tChannels + framePaddingElements;
498  frameMiddle += tChannels + framePaddingElements;
499  frameLower += tChannels + framePaddingElements;
500  maskUpper += 1u + maskPaddingElements;
501  maskMiddle += 1u + maskPaddingElements;
502  maskLower += 1u + maskPaddingElements;
503  ++intermediateMaskMiddle; // intermediate mask has no padding elements
504  }
505 
506  ocean_assert(frameUpper == frame + frameStrideElements * (height - 2u));
507  ocean_assert(frameMiddle == frame + frameStrideElements * (height - 1u));
508  ocean_assert(maskUpper == mask + maskStrideElements * (height - 2u));
509  ocean_assert(maskMiddle == mask + maskStrideElements * (height - 1u));
510  ocean_assert(intermediateMaskMiddle == intermediateMask.data<uint8_t>() + width * (height - 1u));
511 
512  // bottom left pixel
513  if (*maskMiddle == 0x00u && (*maskUpper != 0x00u || *(maskMiddle + 1) != 0x00u))
514  {
515  const unsigned int weight = *maskUpper + *(maskMiddle + 1);
516  ocean_assert(weight > 0u && weight <= 0xFFu * 2u);
517 
518  for (unsigned int n = 0u; n < tChannels; ++n)
519  {
520  if constexpr (tUseRandomNoise)
521  {
522  ocean_assert(randomNoise != 0u);
523  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*maskUpper * *(frameUpper + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
524  }
525  else
526  {
527  *(frameMiddle + n) = uint8_t((*maskUpper * *(frameUpper + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n)) / weight);
528  }
529  }
530 
531  *intermediateMaskMiddle = 0xFFu;
532  atLeastOnePixel = true;
533  }
534 
535  frameUpper += tChannels;
536  frameMiddle += tChannels;
537  ++maskUpper;
538  ++maskMiddle;
539  ++intermediateMaskMiddle;
540 
541 
542  // center pixels
543  for (unsigned int i = 1u; i < width - 1u; ++i)
544  {
545  if (*maskMiddle == 0x00u && (*maskUpper != 0x00u || *(maskMiddle - 1) != 0x00u || *(maskMiddle + 1) != 0x00u))
546  {
547  const unsigned int weight = *maskUpper + *(maskMiddle - 1) + *(maskMiddle + 1);
548  ocean_assert(weight > 0u && weight <= 0xFFu * 4u);
549 
550  for (unsigned int n = 0u; n < tChannels; ++n)
551  {
552  if constexpr (tUseRandomNoise)
553  {
554  ocean_assert(randomNoise != 0u);
555  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*maskUpper * *(frameUpper + n) + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
556  }
557  else
558  {
559  *(frameMiddle + n) = uint8_t((*maskUpper * *(frameUpper + n) + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) + *(maskMiddle + 1) * *(frameMiddle + tChannels + n)) / weight);
560  }
561  }
562 
563  *intermediateMaskMiddle = 0xFFu;
564  atLeastOnePixel = true;
565  }
566 
567  frameUpper += tChannels;
568  frameMiddle += tChannels;
569  ++maskUpper;
570  ++maskMiddle;
571  ++intermediateMaskMiddle;
572  }
573 
574 
575  // right pixel
576  if (*maskMiddle == 0x00u && (*maskUpper != 0x00u || *(maskMiddle - 1) != 0x00u))
577  {
578  const unsigned int weight = *maskUpper + *(maskMiddle - 1);
579  ocean_assert(weight > 0u && weight <= 0xFFu * 3u);
580 
581  for (unsigned int n = 0u; n < tChannels; ++n)
582  {
583  if constexpr (tUseRandomNoise)
584  {
585  ocean_assert(randomNoise != 0u);
586  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*maskUpper * *(frameUpper + n) + *(maskMiddle - 1) * *(frameMiddle - tChannels + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
587  }
588  else
589  {
590  *(frameMiddle + n) = uint8_t((*maskUpper * *(frameUpper + n) + *(maskMiddle - 1) * *(frameMiddle - tChannels + n)) / weight);
591  }
592  }
593 
594  *intermediateMaskMiddle = 0xFFu;
595  atLeastOnePixel = true;
596  }
597 
598  if (atLeastOnePixel)
599  {
600  if (maskPaddingElements == 0u)
601  {
602  memcpy(mask, intermediateMask.constdata<uint8_t>(), sizeof(uint8_t) * width * height);
603  }
604  else
605  {
606  for (unsigned int y = 0u; y < height; ++y)
607  {
608  memcpy(mask + y * maskStrideElements, intermediateMask.constrow<uint8_t>(y), sizeof(uint8_t) * width);
609  }
610  }
611  }
612  }
613 
614  return true;
615 }
616 
617 template <unsigned int tChannels, bool tUseRandomNoise>
618 bool FrameFilterErosion::shrinkMask8BitPerChannel8Neighbor(uint8_t* frame, uint8_t* mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise, const unsigned int randomSeed)
619 {
620  static_assert(tChannels >= 1u, "Invalid channel number!");
621 
622  ocean_assert(frame != nullptr && mask != nullptr);
623  ocean_assert(width >= 2 && height >= 2u);
624  ocean_assert(randomNoise <= 255u);
625 
626  const unsigned int frameStrideElements = width * tChannels + framePaddingElements;
627  const unsigned int maskStrideElements = width + maskPaddingElements;
628 
629 #ifdef OCEAN_DEBUG
630  for (unsigned int y = 0u; y < height; ++y)
631  {
632  const uint8_t* maskRow = mask + y * maskStrideElements;
633 
634  for (unsigned int x = 0u; x < width; ++x)
635  {
636  ocean_assert(maskRow[x] == 0x00u || maskRow[x] == 0xFFu);
637  }
638  }
639 #endif // OCEAN_DEBUG
640 
641  RandomGenerator randomGenerator(randomSeed);
642 
643  /**
644  * O O O
645  * O X O
646  * O O O
647  */
648 
649  Frame intermediateMask(FrameType(width, height, FrameType::FORMAT_Y8, FrameType::ORIGIN_UPPER_LEFT), mask, Frame::CM_COPY_REMOVE_PADDING_LAYOUT, maskPaddingElements);
650  ocean_assert(intermediateMask.isContinuous());
651 
652  bool atLeastOnePixel = true;
653 
654  while (atLeastOnePixel)
655  {
656  atLeastOnePixel = false;
657 
658  const uint8_t* maskMiddle = mask;
659  const uint8_t* maskLower = mask + maskStrideElements;
660 
661  uint8_t* frameMiddle = frame;
662  const uint8_t* frameLower = frame + frameStrideElements;
663 
664  uint8_t* intermediateMaskMiddle = intermediateMask.data<uint8_t>();
665 
666  // upper left pixel, if the anchor pixel is inside the hole and at least one of the filter pixels is outside the hole
667  if (*maskMiddle == 0x00u && (*(maskMiddle + 1) != 0x00u || *(maskLower + 0) != 0x00u || *(maskLower + 1) != 0x00u))
668  {
669  const unsigned int weight = *(maskMiddle + 1) * 2u + *(maskLower + 0) * 2u + *(maskLower + 1);
670  ocean_assert(weight > 0u && weight <= 0xFF * 12u);
671 
672  for (unsigned int n = 0u; n < tChannels; ++n)
673  {
674  if constexpr (tUseRandomNoise)
675  {
676  ocean_assert(randomNoise != 0u);
677 
678  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u
679  + *(maskLower + 0) * *(frameLower + n) * 2u + *(maskLower + 1) * *(frameLower + tChannels + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
680  }
681  else
682  {
683  *(frameMiddle + n) = uint8_t((*(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u
684  + *(maskLower + 0) * *(frameLower + n) * 2u + *(maskLower + 1) * *(frameLower + tChannels + n)) / weight);
685  }
686  }
687 
688  *intermediateMaskMiddle = 0xFFu;
689  atLeastOnePixel = true;
690  }
691 
692  frameMiddle += tChannels;
693  frameLower += tChannels;
694  ++maskMiddle;
695  ++maskLower;
696  ++intermediateMaskMiddle;
697 
698 
699  // upper row
700  for (unsigned int i = 1u; i < width - 1u; ++i)
701  {
702  // if the anchor pixel is inside the hole and at least one of the filter pixels is outside the hole
703  if (*maskMiddle == 0x00u && (*(maskMiddle - 1) != 0x00u || *(maskMiddle + 1) != 0x00u || *(maskLower - 1) != 0x00u || *(maskLower + 0) != 0x00u || *(maskLower + 1) != 0x00u))
704  {
705  const unsigned int weight = *(maskMiddle - 1) * 2u + *(maskMiddle + 1) * 2u + *(maskLower - 1) + *(maskLower + 0) * 2u + *(maskLower + 1);
706  ocean_assert(weight > 0u && weight <= 0xFFu * 12u);
707 
708  for (unsigned int n = 0u; n < tChannels; ++n)
709  {
710  if constexpr (tUseRandomNoise)
711  {
712  ocean_assert(randomNoise != 0u);
713 
714  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u
715  + *(maskLower - 1) * *(frameLower - tChannels + n) + *(maskLower + 0) * *(frameLower + n) * 2u + *(maskLower + 1) * *(frameLower + tChannels + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
716  }
717  else
718  {
719  *(frameMiddle + n) = uint8_t((*(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u
720  + *(maskLower - 1) * *(frameLower - tChannels + n) + *(maskLower + 0) * *(frameLower + n) * 2u + *(maskLower + 1) * *(frameLower + tChannels + n)) / weight);
721  }
722  }
723 
724  *intermediateMaskMiddle = 0xFFu;
725  atLeastOnePixel = true;
726  }
727 
728  frameMiddle += tChannels;
729  frameLower += tChannels;
730  ++maskMiddle;
731  ++maskLower;
732  ++intermediateMaskMiddle;
733  }
734 
735 
736  // upper right pixel
737  if (*maskMiddle == 0x00u && (*(maskMiddle - 1) != 0x00u || *(maskLower - 1) != 0x00u || *(maskLower + 0) != 0x00u))
738  {
739  const unsigned int weight = *(maskMiddle - 1) * 2u + *(maskLower - 1) + *(maskLower + 0) * 2u;
740  ocean_assert(weight > 0u && weight <= 0xFFu * 12u);
741 
742  for (unsigned int n = 0u; n < tChannels; ++n)
743  {
744  if constexpr (tUseRandomNoise)
745  {
746  ocean_assert(randomNoise != 0u);
747 
748  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u
749  + *(maskLower - 1) * *(frameLower - tChannels + n) + *(maskLower + 0) * *(frameLower + n) * 2u) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
750  }
751  else
752  {
753  *(frameMiddle + n) = uint8_t((*(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u
754  + *(maskLower - 1) * *(frameLower - tChannels + n) + *(maskLower + 0) * *(frameLower + n) * 2u) / weight);
755  }
756  }
757 
758  *intermediateMaskMiddle = 0xFF;
759  atLeastOnePixel = true;
760  }
761 
762  frameMiddle += tChannels + framePaddingElements;
763  frameLower += tChannels + framePaddingElements;
764  maskMiddle += 1u + maskPaddingElements;
765  maskLower += 1u + maskPaddingElements;
766  ++intermediateMaskMiddle; // intermediate mask has no padding
767 
768  ocean_assert(frameMiddle == frame + frameStrideElements);
769  ocean_assert(frameLower == frame + frameStrideElements * 2u);
770  ocean_assert(maskMiddle == mask + maskStrideElements);
771  ocean_assert(maskLower == mask + maskStrideElements * 2u);
772  ocean_assert(intermediateMaskMiddle == intermediateMask.data<uint8_t>() + width);
773 
774 
775  // center rows
776 
777  const uint8_t* maskUpper = maskMiddle - maskStrideElements;
778  const uint8_t* frameUpper = frameMiddle - frameStrideElements;
779 
780  const uint8_t* const maskUpperEnd = mask + maskStrideElements * (height - 2u);
781 
782  while (maskUpper != maskUpperEnd)
783  {
784  // left pixel
785  if (*maskMiddle == 0x00u && (*(maskUpper + 0) != 0x00u || *(maskUpper + 1) != 0x00u || *(maskMiddle + 1) != 0x00u || *(maskLower + 0) != 0x00u || *(maskLower + 1) != 0x00u))
786  {
787  const unsigned int weight = *(maskUpper + 0) * 2u + *(maskUpper + 1) + *(maskMiddle + 1) * 2u + *(maskLower + 0) * 2u + *(maskLower + 1);
788  ocean_assert(weight > 0u && weight <= 0xFFu * 12u);
789 
790  for (unsigned int n = 0u; n < tChannels; ++n)
791  {
792  if constexpr (tUseRandomNoise)
793  {
794  ocean_assert(randomNoise != 0u);
795 
796  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskUpper + 0) * *(frameUpper + n) * 2u + *(maskUpper + 1) * *(frameUpper + tChannels + n)
797  + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u
798  + *(maskLower + 0) * *(frameLower + n) * 2u + *(maskLower + 1) * *(frameLower + tChannels + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
799  }
800  else
801  {
802  *(frameMiddle + n) = uint8_t((*(maskUpper + 0) * *(frameUpper + n) * 2u + *(maskUpper + 1) * *(frameUpper + tChannels + n)
803  + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u
804  + *(maskLower + 0) * *(frameLower + n) * 2u + *(maskLower + 1) * *(frameLower + tChannels + n)) / weight);
805  }
806  }
807 
808  *intermediateMaskMiddle = 0xFFu;
809  atLeastOnePixel = true;
810  }
811 
812  frameUpper += tChannels;
813  frameMiddle += tChannels;
814  frameLower += tChannels;
815  ++maskUpper;
816  ++maskMiddle;
817  ++maskLower;
818  ++intermediateMaskMiddle;
819 
820 
821  // center pixels
822  for (unsigned int i = 1u; i < width - 1u; ++i)
823  {
824  if (*maskMiddle == 0x00u && (*(maskUpper - 1) != 0x00u || *(maskUpper + 0) != 0x00u || *(maskUpper + 1) != 0x00u || *(maskMiddle - 1) != 0x00u || *(maskMiddle + 1) != 0x00u || *(maskLower - 1) != 0x00u || *(maskLower + 0) != 0x00u || *(maskLower + 1) != 0x00u))
825  {
826  const unsigned int weight = *(maskUpper - 1) + *(maskUpper + 0) * 2u + *(maskUpper + 1) + *(maskMiddle - 1) * 2u + *(maskMiddle + 1) * 2u + *(maskLower - 1) + *(maskLower + 0) * 2u + *(maskLower + 1);
827  ocean_assert(weight > 0u && weight <= 0xFFu * 12u);
828 
829  for (unsigned int n = 0u; n < tChannels; ++n)
830  {
831  if constexpr (tUseRandomNoise)
832  {
833  ocean_assert(randomNoise != 0u);
834 
835  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskUpper - 1) * *(frameUpper - tChannels + n) + *(maskUpper + 0) * *(frameUpper + n) * 2u + *(maskUpper + 1) * *(frameUpper + tChannels + n)
836  + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u
837  + *(maskLower - 1) * *(frameLower - tChannels + n) + *(maskLower + 0) * *(frameLower + n) * 2u + *(maskLower + 1) * *(frameLower + tChannels + n)) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
838  }
839  else
840  {
841  *(frameMiddle + n) = uint8_t((*(maskUpper - 1) * *(frameUpper - tChannels + n) + *(maskUpper + 0) * *(frameUpper + n) * 2u + *(maskUpper + 1) * *(frameUpper + tChannels + n)
842  + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u
843  + *(maskLower - 1) * *(frameLower - tChannels + n) + *(maskLower + 0) * *(frameLower + n) * 2u + *(maskLower + 1) * *(frameLower + tChannels + n)) / weight);
844  }
845  }
846 
847  *intermediateMaskMiddle = 0xFFu;
848  atLeastOnePixel = true;
849  }
850 
851  frameUpper += tChannels;
852  frameMiddle += tChannels;
853  frameLower += tChannels;
854  ++maskUpper;
855  ++maskMiddle;
856  ++maskLower;
857  ++intermediateMaskMiddle;
858  }
859 
860 
861  // right pixels
862  if (*maskMiddle == 0x00u && (*(maskUpper - 1) != 0x00u || *(maskUpper + 0) != 0x00u || *(maskMiddle - 1) != 0x00u || *(maskLower - 1) != 0x00u || *(maskLower + 0) != 0x00u))
863  {
864  const unsigned int weight = *(maskUpper - 1) + *(maskUpper + 0) * 2u + *(maskMiddle - 1) * 2u + *(maskLower - 1) + *(maskLower + 0) * 2u;
865  ocean_assert(weight > 0u && weight <= 0xFFu * 12u);
866 
867  for (unsigned int n = 0u; n < tChannels; ++n)
868  {
869  if constexpr (tUseRandomNoise)
870  {
871  ocean_assert(randomNoise != 0u);
872 
873  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskUpper - 1) * *(frameUpper - tChannels + n) + *(maskUpper + 0) * *(frameUpper + n) * 2u
874  + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u
875  + *(maskLower - 1) * *(frameLower - tChannels + n) + *(maskLower + 0) * *(frameLower + n) * 2u) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
876  }
877  else
878  {
879  *(frameMiddle + n) = uint8_t((*(maskUpper - 1) * *(frameUpper - tChannels + n) + *(maskUpper + 0) * *(frameUpper + n) * 2u
880  + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u
881  + *(maskLower - 1) * *(frameLower - tChannels + n) + *(maskLower + 0) * *(frameLower + n) * 2u) / weight);
882  }
883  }
884 
885  *intermediateMaskMiddle = 0xFFu;
886  atLeastOnePixel = true;
887  }
888 
889  frameUpper += tChannels + framePaddingElements;
890  frameMiddle += tChannels + framePaddingElements;
891  frameLower += tChannels + framePaddingElements;
892  maskUpper += 1u + maskPaddingElements;
893  maskMiddle += 1u + maskPaddingElements;
894  maskLower += 1u + maskPaddingElements;
895  ++intermediateMaskMiddle; // intermediate mask has no padding elements
896  }
897 
898  ocean_assert(frameUpper == frame + frameStrideElements * (height - 2u));
899  ocean_assert(frameMiddle == frame + frameStrideElements * (height - 1u));
900  ocean_assert(maskUpper == mask + maskStrideElements * (height - 2u));
901  ocean_assert(maskMiddle == mask + maskStrideElements * (height - 1u));
902  ocean_assert(intermediateMaskMiddle == intermediateMask.data<uint8_t>() + width * (height - 1u));
903 
904 
905  // bottom left pixel
906  if (*maskMiddle == 0x00u && (*(maskUpper + 0) != 0x00u || *(maskUpper + 1) != 0x00u || *(maskMiddle + 1) != 0x00u))
907  {
908  const unsigned int weight = *(maskUpper + 0) * 2u + *(maskUpper + 1) + *(maskMiddle + 1) * 2u;
909  ocean_assert(weight > 0u && weight <= 0xFFu * 12u);
910 
911  for (unsigned int n = 0u; n < tChannels; ++n)
912  {
913  if constexpr (tUseRandomNoise)
914  {
915  ocean_assert(randomNoise != 0u);
916 
917  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskUpper + 0) * *(frameUpper + n) * 2u + *(maskUpper + 1) * *(frameUpper + tChannels + n)
918  + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
919  }
920  else
921  {
922  *(frameMiddle + n) = uint8_t((*(maskUpper + 0) * *(frameUpper + n) * 2u + *(maskUpper + 1) * *(frameUpper + tChannels + n)
923  + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u) / weight);
924  }
925  }
926 
927  *intermediateMaskMiddle = 0xFFu;
928  atLeastOnePixel = true;
929  }
930 
931  frameUpper += tChannels;
932  frameMiddle += tChannels;
933  ++maskUpper;
934  ++maskMiddle;
935  ++intermediateMaskMiddle;
936 
937 
938  // center pixels
939  for (unsigned int i = 1u; i < width - 1u; ++i)
940  {
941  if (*maskMiddle == 0x00u && (*(maskUpper - 1) != 0x00u || *(maskUpper + 0) != 0x00u || *(maskUpper + 1) != 0x00u || *(maskMiddle - 1) != 0x00u || *(maskMiddle + 1) != 0x00u))
942  {
943  const unsigned int weight = *(maskUpper - 1) + *(maskUpper + 0) * 2u + *(maskUpper + 1) + *(maskMiddle - 1) * 2u + *(maskMiddle + 1) * 2u;
944  ocean_assert(weight > 0u && weight <= 0xFFu * 12u);
945 
946  for (unsigned int n = 0u; n < tChannels; ++n)
947  {
948  if constexpr (tUseRandomNoise)
949  {
950  ocean_assert(randomNoise != 0u);
951 
952  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskUpper - 1) * *(frameUpper - tChannels + n) + *(maskUpper + 0) * *(frameUpper + n) * 2u + *(maskUpper + 1) * *(frameUpper + tChannels + n)
953  + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
954  }
955  else
956  {
957  *(frameMiddle + n) = uint8_t((*(maskUpper - 1) * *(frameUpper - tChannels + n) + *(maskUpper + 0) * *(frameUpper + n) * 2u + *(maskUpper + 1) * *(frameUpper + tChannels + n)
958  + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u + *(maskMiddle + 1) * *(frameMiddle + tChannels + n) * 2u) / weight);
959  }
960  }
961 
962  *intermediateMaskMiddle = 0xFFu;
963  atLeastOnePixel = true;
964  }
965 
966  frameUpper += tChannels;
967  frameMiddle += tChannels;
968  ++maskUpper;
969  ++maskMiddle;
970  ++intermediateMaskMiddle;
971  }
972 
973 
974  // right pixel
975  if (*maskMiddle == 0x00u && (*(maskUpper - 1) != 0x00u || *(maskUpper + 0) != 0x00u || *(maskMiddle - 1) != 0x00u))
976  {
977  const unsigned int weight = *(maskUpper - 1) + *(maskUpper + 0) * 2u + *(maskMiddle - 1) * 2u;
978  ocean_assert(weight > 0u && weight <= 0xFFu * 12u);
979 
980  for (unsigned int n = 0u; n < tChannels; ++n)
981  {
982  if constexpr (tUseRandomNoise)
983  {
984  ocean_assert(randomNoise != 0u);
985 
986  *(frameMiddle + n) = uint8_t(minmax<int>(0, int((*(maskUpper - 1) * *(frameUpper - tChannels + n) + *(maskUpper + 0) * *(frameUpper + n) * 2u
987  + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u) / weight) + RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise)), 255));
988  }
989  else
990  {
991  *(frameMiddle + n) = uint8_t((*(maskUpper - 1) * *(frameUpper - tChannels + n) + *(maskUpper + 0) * *(frameUpper + n) * 2u
992  + *(maskMiddle - 1) * *(frameMiddle - tChannels + n) * 2u) / weight);
993  }
994  }
995 
996  *intermediateMaskMiddle = 0xFFu;
997  atLeastOnePixel = true;
998  }
999 
1000  if (atLeastOnePixel)
1001  {
1002  if (maskPaddingElements == 0u)
1003  {
1004  memcpy(mask, intermediateMask.constdata<uint8_t>(), sizeof(uint8_t) * width * height);
1005  }
1006  else
1007  {
1008  for (unsigned int y = 0u; y < height; ++y)
1009  {
1010  memcpy(mask + y * maskStrideElements, intermediateMask.constrow<uint8_t>(y), sizeof(uint8_t) * width);
1011  }
1012  }
1013  }
1014  }
1015 
1016  return true;
1017 }
1018 
1019 template <unsigned int tChannels>
1020 void FrameFilterErosion::shrinkMaskRandom8BitPerChannel8Neighbor(uint8_t* frame, uint8_t* mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise, const unsigned int randomSeed)
1021 {
1022  static_assert(tChannels >= 1u, "Invalid channel number!");
1023 
1024  ocean_assert(frame != nullptr && mask != nullptr);
1025  ocean_assert(width >= 2u && height >= 2u);
1026 
1027  const unsigned int frameStrideElements = width * tChannels + framePaddingElements;
1028  const unsigned int maskStrideElements = width + maskPaddingElements;
1029 
1030  const unsigned int width_1 = width - 1u;
1031  const unsigned int height_1 = height - 1u;
1032 
1033  RandomGenerator randomGenerator(randomSeed);
1034 
1035  /**
1036  * O O O
1037  * O X O
1038  * O O O
1039  */
1040 
1041  PixelPositionSet borderPixelSet;
1042  CV::PixelPositions borderPixels;
1043 
1044  borderPixelSet.reserve(1024);
1045  borderPixels.reserve(1024);
1046 
1047  constexpr uint8_t nonMaskValue = 0xFFu;
1048 
1049  {
1050  // find all border pixels
1051 
1052  const uint8_t* maskUpper = mask - maskStrideElements;
1053  const uint8_t* maskMiddle = mask;
1054  const uint8_t* maskLower = mask + maskStrideElements;
1055 
1056  for (unsigned int y = 0u; y < height; ++y)
1057  {
1058  unsigned int x = 0u;
1059 
1060  if (y == 0u)
1061  {
1062  // top left pixel
1063  if (maskMiddle[x] != nonMaskValue && (maskMiddle[x + 1u] == nonMaskValue || maskLower[x] == nonMaskValue || maskLower[x + 1u] == nonMaskValue))
1064  {
1065  borderPixelSet.emplace(x, 0u);
1066  borderPixels.emplace_back(x, 0u);
1067  }
1068 
1069  while (++x < width_1)
1070  {
1071  if (*(maskMiddle + x) != nonMaskValue && (*(maskMiddle + x - 1u) == nonMaskValue || *(maskMiddle + x + 1u) == nonMaskValue || *(maskLower + x - 1u) == nonMaskValue || *(maskLower + x) == nonMaskValue || *(maskLower + x + 1u) == nonMaskValue))
1072  {
1073  borderPixelSet.emplace(x, 0u);
1074  borderPixels.emplace_back(x, 0u);
1075  }
1076  }
1077 
1078  ocean_assert(x == width_1);
1079 
1080  // top right pixel
1081  if (*(maskMiddle + x) != nonMaskValue && (*(maskMiddle + x - 1u) == nonMaskValue || *(maskLower + x - 1u) == nonMaskValue || *(maskLower + x) == nonMaskValue))
1082  {
1083  borderPixelSet.emplace(x, 0u);
1084  borderPixels.emplace_back(x, 0u);
1085  }
1086  }
1087  else if (y == height_1)
1088  {
1089  // bottom left pixel
1090  if (maskMiddle[x] != nonMaskValue && (maskMiddle[x + 1u] == nonMaskValue || maskUpper[x] == nonMaskValue || maskUpper[x + 1u] == nonMaskValue))
1091  {
1092  borderPixelSet.emplace(x, height_1);
1093  borderPixels.emplace_back(x, height_1);
1094  }
1095 
1096  while (++x < width_1)
1097  {
1098  if (*(maskMiddle + x) != nonMaskValue && (*(maskMiddle + x - 1u) == nonMaskValue || *(maskMiddle + x + 1u) == nonMaskValue || *(maskUpper + x - 1u) == nonMaskValue || *(maskUpper + x) == nonMaskValue || *(maskUpper + x + 1u) == nonMaskValue))
1099  {
1100  borderPixelSet.emplace(x, height_1);
1101  borderPixels.emplace_back(x, height_1);
1102  }
1103  }
1104 
1105  ocean_assert(x == width_1);
1106 
1107  // bottom right pixel
1108  if (*(maskMiddle + x) != nonMaskValue && (*(maskMiddle + x - 1u) == nonMaskValue || *(maskUpper + x - 1u) == nonMaskValue || *(maskUpper + x) == nonMaskValue))
1109  {
1110  borderPixelSet.emplace(x, height_1);
1111  borderPixels.emplace_back(x, height_1);
1112  }
1113  }
1114  else
1115  {
1116  ocean_assert(y >= 1u && y < height_1);
1117 
1118  // left pixel
1119  if (maskMiddle[x] != nonMaskValue && (maskMiddle[x + 1u] == nonMaskValue || maskUpper[x] == nonMaskValue || maskUpper[x + 1u] == nonMaskValue || maskLower[x] == nonMaskValue || maskLower[x + 1u] == nonMaskValue))
1120  {
1121  borderPixelSet.emplace(x, y);
1122  borderPixels.emplace_back(x, y);
1123  }
1124 
1125  while (++x < width_1)
1126  {
1127  if (*(maskMiddle + x) != nonMaskValue && (*(maskMiddle + x - 1u) == nonMaskValue || *(maskMiddle + x + 1u) == nonMaskValue || *(maskUpper + x - 1u) == nonMaskValue || *(maskUpper + x) == nonMaskValue || *(maskUpper + x + 1u) == nonMaskValue || *(maskLower + x - 1u) == nonMaskValue || *(maskLower + x) == nonMaskValue || *(maskLower + x + 1u) == nonMaskValue))
1128  {
1129  borderPixelSet.emplace(x, y);
1130  borderPixels.emplace_back(x, y);
1131  }
1132  }
1133 
1134  ocean_assert(x == width_1);
1135 
1136  // right pixel
1137  if (*(maskMiddle + x) != nonMaskValue && (*(maskMiddle + x - 1u) == nonMaskValue || *(maskUpper + x - 1u) == nonMaskValue || *(maskUpper + x) == nonMaskValue || *(maskLower + x - 1u) == nonMaskValue || *(maskLower + x) == nonMaskValue))
1138  {
1139  borderPixelSet.emplace(x, y);
1140  borderPixels.emplace_back(x, y);
1141  }
1142  }
1143 
1144  maskUpper += maskStrideElements;
1145  maskMiddle += maskStrideElements;
1146  maskLower += maskStrideElements;
1147  }
1148  }
1149 
1150  while (!borderPixels.empty())
1151  {
1152  const unsigned int index = Random::random(randomGenerator, (unsigned int)(borderPixels.size()) - 1u);
1153 
1154  const CV::PixelPosition pixelPosition(borderPixels[index]);
1155 
1156  borderPixels[index] = borderPixels.back();
1157  borderPixels.pop_back();
1158 
1159  borderPixelSet.erase(pixelPosition);
1160 
1161  ocean_assert(pixelPosition.x() < width && pixelPosition.y() < height);
1162 
1163  uint8_t* maskMiddle = mask + pixelPosition.y() * maskStrideElements + pixelPosition.x();
1164  --maskMiddle; // left pixel of 3x3 square
1165  const uint8_t* maskUpper = maskMiddle - maskStrideElements;
1166  const uint8_t* maskLower = maskMiddle + maskStrideElements;
1167 
1168  uint8_t* frameMiddle = frame + pixelPosition.y() * frameStrideElements + pixelPosition.x() * tChannels;
1169  frameMiddle -= tChannels; // left pixel of 3x3 square
1170  const uint8_t* const frameUpper = frameMiddle - frameStrideElements;
1171  const uint8_t* const frameLower = frameMiddle + frameStrideElements;
1172 
1173  if (pixelPosition.x() >= 1u && pixelPosition.y() >= 1u && pixelPosition.x() < width_1 && pixelPosition.y() < height_1)
1174  {
1175  // we have a center pixel
1176 
1177  const unsigned int weight = maskUpper[0] + maskUpper[1] * 2u + maskUpper[2] + maskMiddle[0] * 2u + maskMiddle[2] * 2u + maskLower[0] + maskLower[1] * 2u + maskLower[2];
1178 
1179  ocean_assert(weight > 0u && weight <= 0xFF * 12u);
1180  const unsigned int weight_2 = weight / 2u;
1181 
1182  for (unsigned int n = 0u; n < tChannels; ++n)
1183  {
1184  const unsigned int value = frameUpper[n] * maskUpper[0] + frameUpper[tChannels + n] * maskUpper[1] * 2u + frameUpper[tChannels * 2u + n] * maskUpper[2]
1185  + frameMiddle[n] * maskMiddle[0] * 2u + frameMiddle[tChannels * 2u + n] * maskMiddle[2] * 2u
1186  + frameLower[n] * maskLower[0] + frameLower[tChannels + n] * maskLower[1] * 2u + frameLower[tChannels * 2u + n] * maskLower[2];
1187 
1188  const int noise = randomNoise == 0u ? 0 : RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise));
1189 
1190  frameMiddle[tChannels + n] = uint8_t(minmax<int>(0, int((value + weight_2) / weight) + noise, 255));
1191  }
1192 
1193  maskMiddle[1] = nonMaskValue;
1194  }
1195  else
1196  {
1197  // we have a frame border pixel
1198 
1199  unsigned int weight = 0u;
1200  unsigned int values[tChannels] = {0u};
1201 
1202  if (pixelPosition.y() > 0u)
1203  {
1204  if (pixelPosition.x() > 0u)
1205  {
1206  weight += maskUpper[0];
1207 
1208  for (unsigned int n = 0u; n < tChannels; ++n)
1209  {
1210  values[n] += frameUpper[n] * maskUpper[0];
1211  }
1212  }
1213 
1214  weight += maskUpper[1] * 2u;
1215 
1216  for (unsigned int n = 0u; n < tChannels; ++n)
1217  {
1218  values[n] += frameUpper[tChannels + n] * maskUpper[1] * 2u;
1219  }
1220 
1221  if (pixelPosition.x() < width_1)
1222  {
1223  weight += maskUpper[2];
1224 
1225  for (unsigned int n = 0u; n < tChannels; ++n)
1226  {
1227  values[n] += frameUpper[tChannels * 2u + n] * maskUpper[2];
1228  }
1229  }
1230  }
1231 
1232  if (pixelPosition.x() > 0u)
1233  {
1234  weight += maskMiddle[0] * 2u;
1235 
1236  for (unsigned int n = 0u; n < tChannels; ++n)
1237  {
1238  values[n] += frameMiddle[n] * maskMiddle[0] * 2u;
1239  }
1240  }
1241 
1242  if (pixelPosition.x() < width_1)
1243  {
1244  weight += maskMiddle[2] * 2u;
1245 
1246  for (unsigned int n = 0u; n < tChannels; ++n)
1247  {
1248  values[n] += frameMiddle[tChannels * 2u + n] * maskMiddle[2] * 2u;
1249  }
1250  }
1251 
1252  if (pixelPosition.y() < height_1)
1253  {
1254  if (pixelPosition.x() > 0u)
1255  {
1256  weight += maskLower[0];
1257 
1258  for (unsigned int n = 0u; n < tChannels; ++n)
1259  {
1260  values[n] += frameLower[n] * maskLower[0];
1261  }
1262  }
1263 
1264  weight += maskLower[1] * 2u;
1265 
1266  for (unsigned int n = 0u; n < tChannels; ++n)
1267  {
1268  values[n] += frameLower[tChannels + n] * maskLower[1] * 2u;
1269  }
1270 
1271  if (pixelPosition.x() < width_1)
1272  {
1273  weight += maskLower[2];
1274 
1275  for (unsigned int n = 0u; n < tChannels; ++n)
1276  {
1277  values[n] += frameLower[tChannels * 2u + n] * maskLower[2];
1278  }
1279  }
1280  }
1281 
1282  ocean_assert(weight > 0u && weight <= 0xFF * 12u);
1283  const unsigned int weight_2 = weight / 2u;
1284 
1285  for (unsigned int n = 0u; n < tChannels; ++n)
1286  {
1287  const int noise = randomNoise == 0u ? 0 : RandomI::random(randomGenerator, -int(randomNoise), int(randomNoise));
1288 
1289  frameMiddle[tChannels + n] = uint8_t(minmax<int>(0, int((values[n] + weight_2) / weight) + noise, 255));
1290  }
1291 
1292  maskMiddle[1] = nonMaskValue;
1293  }
1294 
1295  for (int yy = -1; yy <= 1; ++yy)
1296  {
1297  const unsigned int yCenter = (unsigned int)(int(pixelPosition.y()) + yy);
1298 
1299  if (yCenter < height)
1300  {
1301  for (int xx = -1; xx <= 1; ++xx)
1302  {
1303  if (yy != 0 || xx != 0)
1304  {
1305  const unsigned int xCenter = (unsigned int)(int(pixelPosition.x()) + xx);
1306 
1307  if (xCenter < width)
1308  {
1309  maskMiddle = mask + yCenter * maskStrideElements + xCenter;
1310  --maskMiddle; // left pixel of 3x3 square
1311 
1312  if (maskMiddle[1] != nonMaskValue)
1313  {
1314  if (borderPixelSet.find(CV::PixelPosition(xCenter, yCenter)) == borderPixelSet.cend())
1315  {
1316  maskUpper = maskMiddle - maskStrideElements;
1317  maskLower = maskMiddle + maskStrideElements;
1318 
1319  const bool newBorderPixel = (xCenter > 0u && yCenter > 0u && maskUpper[0] == nonMaskValue) // upper pixels
1320  || (yCenter > 0u && maskUpper[1] == nonMaskValue)
1321  || (xCenter < width_1 && yCenter > 0u && maskUpper[2] == nonMaskValue)
1322  || (xCenter > 0u && maskMiddle[0] == nonMaskValue) // middle pixels
1323  || (xCenter < width_1 && maskMiddle[2] == nonMaskValue)
1324  || (xCenter > 0u && yCenter < height_1 && maskLower[0] == nonMaskValue) // lower pixels
1325  || (yCenter < height_1 && maskLower[1] == nonMaskValue)
1326  || (xCenter < width_1 && yCenter < height_1 && maskLower[2] == nonMaskValue);
1327 
1328  if (newBorderPixel)
1329  {
1330  borderPixelSet.emplace(xCenter, yCenter);
1331  borderPixels.emplace_back(xCenter, yCenter);
1332  }
1333  }
1334  }
1335  }
1336  }
1337  }
1338  }
1339  }
1340  }
1341 }
1342 
1343 template <FrameFilterErosion::MorphologyFilter tErosionFilter>
1344 void FrameFilterErosion::filter1Channel8Bit(uint8_t* mask, const unsigned int width, const unsigned int height, const unsigned int iterations, const uint8_t maskValue, const unsigned int maskPaddingElements, Worker* worker)
1345 {
1346  ocean_assert(mask != nullptr);
1347  ocean_assert(width >= 4u && height >= 4u);
1348  ocean_assert(iterations >= 1u);
1349 
1350  Frame intermediateTarget(FrameType(width, height, FrameType::FORMAT_Y8, FrameType::ORIGIN_UPPER_LEFT));
1351 
1352  switch (tErosionFilter)
1353  {
1354  case MF_CROSS_3:
1355  {
1356  for (unsigned int n = 0u; n < iterations / 2u; ++n)
1357  {
1358  filter1Channel8Bit4Neighbor(mask, intermediateTarget.data<uint8_t>(), width, height, maskValue, maskPaddingElements, intermediateTarget.paddingElements(), worker);
1359  filter1Channel8Bit4Neighbor(intermediateTarget.constdata<uint8_t>(), mask, width, height, maskValue, intermediateTarget.paddingElements(), maskPaddingElements, worker);
1360  }
1361 
1362  if (iterations % 2u == 1u)
1363  {
1364  filter1Channel8Bit4Neighbor(mask, intermediateTarget.data<uint8_t>(), width, height, maskValue, maskPaddingElements, intermediateTarget.paddingElements(), worker);
1365  }
1366 
1367  break;
1368  }
1369 
1370  case MF_SQUARE_3:
1371  {
1372  for (unsigned int n = 0u; n < iterations / 2u; ++n)
1373  {
1374  filter1Channel8Bit8Neighbor(mask, intermediateTarget.data<uint8_t>(), width, height, maskValue, maskPaddingElements, intermediateTarget.paddingElements(), worker);
1375  filter1Channel8Bit8Neighbor(intermediateTarget.constdata<uint8_t>(), mask, width, height, maskValue, intermediateTarget.paddingElements(), maskPaddingElements, worker);
1376  }
1377 
1378  if (iterations % 2u == 1u)
1379  {
1380  filter1Channel8Bit8Neighbor(mask, intermediateTarget.data<uint8_t>(), width, height, maskValue, maskPaddingElements, intermediateTarget.paddingElements(), worker);
1381  }
1382 
1383  break;
1384  }
1385 
1386  case MF_SQUARE_5:
1387  {
1388  for (unsigned int n = 0u; n < iterations / 2u; ++n)
1389  {
1390  filter1Channel8Bit24Neighbor(mask, intermediateTarget.data<uint8_t>(), width, height, maskValue, maskPaddingElements, intermediateTarget.paddingElements(), worker);
1391  filter1Channel8Bit24Neighbor(intermediateTarget.constdata<uint8_t>(), mask, width, height, maskValue, intermediateTarget.paddingElements(), maskPaddingElements, worker);
1392  }
1393 
1394  if (iterations % 2u == 1u)
1395  {
1396  filter1Channel8Bit24Neighbor(mask, intermediateTarget.data<uint8_t>(), width, height, maskValue, maskPaddingElements, intermediateTarget.paddingElements(), worker);
1397  }
1398 
1399  break;
1400  }
1401 
1402  default:
1403  ocean_assert(false && "Invalid erosion filter!");
1404  }
1405 
1406  if (iterations % 2u == 1u)
1407  {
1408  CV::FrameConverter::subFrame<uint8_t>(intermediateTarget.constdata<uint8_t>(), mask, width, height, width, height, 1u, 0u, 0u, 0u, 0u, width, height, intermediateTarget.paddingElements(), maskPaddingElements);
1409  }
1410 }
1411 
1412 inline void FrameFilterErosion::filter1Channel8Bit4Neighbor(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, Worker* worker)
1413 {
1414  ocean_assert(mask && target);
1415  ocean_assert(width >= 2u && height >= 2u);
1416 
1417  if (worker)
1418  {
1419  worker->executeFunction(Worker::Function::createStatic(&FrameFilterErosion::filter1Channel8Bit4NeighborSubset, mask, target, width, height, maskValue, maskPaddingElements, targetPaddingElements, 0u, 0u), 0u, height, 7u, 8u, 20u);
1420  }
1421  else
1422  {
1423  filter1Channel8Bit4NeighborSubset(mask, target, width, height, maskValue, maskPaddingElements, targetPaddingElements, 0u, height);
1424  }
1425 }
1426 
1427 inline void FrameFilterErosion::filter1Channel8Bit8Neighbor(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, Worker* worker)
1428 {
1429  ocean_assert(mask && target);
1430  ocean_assert(width >= 2u && height >= 2u);
1431 
1432  if (worker)
1433  {
1434  worker->executeFunction(Worker::Function::createStatic(&FrameFilterErosion::filter1Channel8Bit8NeighborSubset, mask, target, width, height, maskValue, maskPaddingElements, targetPaddingElements, 0u, 0u), 0u, height, 7u, 8u, 20u);
1435  }
1436  else
1437  {
1438  filter1Channel8Bit8NeighborSubset(mask, target, width, height, maskValue, maskPaddingElements, targetPaddingElements, 0u, height);
1439  }
1440 }
1441 
1442 inline void FrameFilterErosion::filter1Channel8Bit24Neighbor(const uint8_t* mask, uint8_t* target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, Worker* worker)
1443 {
1444  ocean_assert(mask && target);
1445  ocean_assert(width >= 2u && height >= 2u);
1446 
1447  if (worker)
1448  {
1449  worker->executeFunction(Worker::Function::createStatic(&FrameFilterErosion::filter1Channel8Bit24NeighborSubset, mask, target, width, height, maskValue, maskPaddingElements, targetPaddingElements, 0u, 0u), 0u, height, 7u, 8u, 20u);
1450  }
1451  else
1452  {
1453  filter1Channel8Bit24NeighborSubset(mask, target, width, height, maskValue, maskPaddingElements, targetPaddingElements, 0u, height);
1454  }
1455 }
1456 
1457 template <>
1458 OCEAN_FORCE_INLINE bool FrameFilterErosion::onePixelNotEqual<3u>(const uint8_t* const maskPixels, const uint8_t maskValue)
1459 {
1460  ocean_assert(maskPixels != nullptr);
1461 
1462  return maskPixels[0] != maskValue || maskPixels[1] != maskValue || maskPixels[2] != maskValue;
1463 }
1464 
1465 template <>
1466 OCEAN_FORCE_INLINE bool FrameFilterErosion::onePixelNotEqual<4u>(const uint8_t* const maskPixels, const uint8_t maskValue)
1467 {
1468  ocean_assert(maskPixels != nullptr);
1469 
1470  return maskPixels[0] != maskValue || maskPixels[1] != maskValue || maskPixels[2] != maskValue || maskPixels[3] != maskValue;
1471 }
1472 
1473 template <>
1474 OCEAN_FORCE_INLINE bool FrameFilterErosion::onePixelNotEqual<5u>(const uint8_t* const maskPixels, const uint8_t maskValue)
1475 {
1476  ocean_assert(maskPixels != nullptr);
1477 
1478  return maskPixels[0] != maskValue || maskPixels[1] != maskValue || maskPixels[2] != maskValue || maskPixels[3] != maskValue || maskPixels[4] != maskValue;
1479 }
1480 
1481 }
1482 
1483 }
1484 
1485 #endif // META_OCEAN_CV_FRAME_FILTER_EROSION_H
The following comfort class provides comfortable functions simplifying prototyping applications but a...
Definition: FrameFilterErosion.h:48
static bool shrinkMaskRandom(Frame &frame, Frame &mask, const MorphologyFilter erosionFilter, const unsigned int randomNoise=3u, const unsigned int randomSeed=RandomI::random32())
Closes a hole inside an 8 bit grayscale frame using a randomized erosion filter.
static bool shrinkMask(Frame &frame, Frame &mask, const MorphologyFilter erosionFilter, const unsigned int randomNoise=3u, const unsigned int randomSeed=RandomI::random32())
Closes holes inside a frame using a shrinking/in-bleeding approach based on either a 4-neighborhood o...
This class implements an erosion filter.
Definition: FrameFilterErosion.h:32
static bool shrinkMask8BitPerChannel8Neighbor(uint8_t *frame, uint8_t *mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise=0u, const unsigned int randomSeed=RandomI::random32())
Closes a hole inside a frame by using a shrinking/in-bleeding approach based on an 8-neighborhood.
Definition: FrameFilterErosion.h:618
static void filter1Channel8Bit24Neighbor(const uint8_t *mask, uint8_t *target, const unsigned int width, const unsigned int height, const uint8_t maskValue=0x00, const unsigned int maskPaddingElements=0u, const unsigned int targetPaddingElements=0u, Worker *worker=nullptr)
Applies one erosion filter iteration for an 8 bit mask image using a 24-neighborhood,...
Definition: FrameFilterErosion.h:1442
static void filter1Channel8Bit24NeighborSubset(const uint8_t *mask, uint8_t *target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, const unsigned int firstRow, const unsigned int numberRows)
Applies one erosion filter iteration for an 8 bit mask image using a 24-neighborhood.
static void filter1Channel8Bit8NeighborSubset(const uint8_t *mask, uint8_t *target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, const unsigned int firstRow, const unsigned int numberRows)
Applies one erosion filter iteration for an 8 bit mask image using a 8-neighborhood.
static void filter1Channel8Bit8Neighbor(const uint8_t *mask, uint8_t *target, const unsigned int width, const unsigned int height, const uint8_t maskValue=0x00, const unsigned int maskPaddingElements=0u, const unsigned int targetPaddingElements=0u, Worker *worker=nullptr)
Applies one erosion filter iteration for an 8 bit mask image using a 8-neighborhood,...
Definition: FrameFilterErosion.h:1427
std::unordered_set< CV::PixelPosition, CV::PixelPosition > PixelPositionSet
Definition of an unordered set holding pixel positions.
Definition: FrameFilterErosion.h:38
static OCEAN_FORCE_INLINE bool onePixelNotEqual(const uint8_t *const maskPixels, const uint8_t maskValue)
Returns whether at least one of several pixels in a row is not equal to a specified value.
static void shrinkMaskRandom8BitPerChannel8Neighbor(uint8_t *frame, uint8_t *mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise=0u, const unsigned int randomSeed=RandomI::random32())
Closes a hole inside a frame by using a randomized shrinking/in-bleeding approach based on an 8-neigh...
Definition: FrameFilterErosion.h:1020
static void filter1Channel8Bit(uint8_t *mask, const unsigned int width, const unsigned int height, const unsigned int iterations, const uint8_t maskValue=0x00, const unsigned int maskPaddingElements=0u, Worker *worker=nullptr)
Applies several erosion filter iterations for an 8 bit mask image.
Definition: FrameFilterErosion.h:1344
static bool shrinkMask8BitPerChannel4Neighbor(uint8_t *frame, uint8_t *mask, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const unsigned int maskPaddingElements, const unsigned int randomNoise=0u, const unsigned int randomSeed=RandomI::random32())
Closes a hole inside a frame by using a shrinking/in-bleeding approach based on a 4-neighborhood.
Definition: FrameFilterErosion.h:250
static void filter1Channel8Bit4NeighborSubset(const uint8_t *mask, uint8_t *target, const unsigned int width, const unsigned int height, const uint8_t maskValue, const unsigned int maskPaddingElements, const unsigned int targetPaddingElements, const unsigned int firstRow, const unsigned int numberRows)
Applies one erosion filter iteration in a subset of an 8 bit mask image using a 4-neighborhood.
static void filter1Channel8Bit4Neighbor(const uint8_t *mask, uint8_t *target, const unsigned int width, const unsigned int height, const uint8_t maskValue=0x00, const unsigned int maskPaddingElements=0u, const unsigned int targetPaddingElements=0u, Worker *worker=nullptr)
Applies one erosion filter iteration in an 8 bit mask image using a 4-neighborhood.
Definition: FrameFilterErosion.h:1412
This class implements the base class for all morphology frame filter.
Definition: FrameFilterMorphology.h:26
MorphologyFilter
Definition of individual morphology masks.
Definition: FrameFilterMorphology.h:33
@ MF_SQUARE_3
Square mask filter defined by one center point and 8 surrounded filter pixels with an area of 3x3.
Definition: FrameFilterMorphology.h:37
@ MF_CROSS_3
Cross mask filter defined by one center point and 4 (north, south, west, east) filter pixels with an ...
Definition: FrameFilterMorphology.h:35
@ MF_SQUARE_5
Square mask filter defined by one center point and 24 surrounded filter pixels with an area of 5x5.
Definition: FrameFilterMorphology.h:39
T y() const
Returns the vertical coordinate position of this object.
Definition: PixelPosition.h:470
T x() const
Returns the horizontal coordinate position of this object.
Definition: PixelPosition.h:458
static Caller< void > createStatic(typename StaticFunctionPointerMaker< void, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass, NullClass >::Type function)
Creates a new caller container for a static function with no function parameter.
Definition: Caller.h:2876
This class implements Ocean's image class.
Definition: Frame.h:1760
bool isContinuous() const
Returns whether all planes of this frame have continuous memory and thus do not contain any padding a...
Definition: Frame.h:4244
const T * constdata(const unsigned int planeIndex=0u) const
Returns a pointer to the read-only pixel data of a specific plane.
Definition: Frame.h:4136
T * data(const unsigned int planeIndex=0u)
Returns a pointer to the pixel data of a specific plane.
Definition: Frame.h:4127
@ CM_COPY_REMOVE_PADDING_LAYOUT
Makes a copy of the source memory, but the new plane will not contain padding elements.
Definition: Frame.h:1771
const T * constrow(const unsigned int y, const unsigned int planeIndex=0u) const
Returns the pointer to the constant data of a specific row.
Definition: Frame.h:4161
unsigned int paddingElements(const unsigned int planeIndex=0u) const
Returns the optional number of padding elements at the end of each row for a specific plane.
Definition: Frame.h:4010
Definition of a frame type composed by the frame dimension, pixel format and pixel origin.
Definition: Frame.h:30
@ FORMAT_Y8
Pixel format for grayscale images with byte order Y and 8 bits per pixel.
Definition: Frame.h:594
@ ORIGIN_UPPER_LEFT
The first pixel lies in the upper left corner, the last pixel in the lower right corner.
Definition: Frame.h:1018
This class implements a generator for random numbers.
Definition: RandomGenerator.h:42
static uint32_t random32()
Returns one random integer number with range [0x00000000, 0xFFFFFFFF].
static unsigned int random(const unsigned int maxValue)
Returns one random integer value with specified maximum value.
This class implements a worker able to distribute function calls over different threads.
Definition: Worker.h:33
bool executeFunction(const Function &function, const unsigned int first, const unsigned int size, const unsigned int firstIndex=(unsigned int)(-1), const unsigned int sizeIndex=(unsigned int)(-1), const unsigned int minimalIterations=1u, const unsigned int threadIndex=(unsigned int)(-1))
Executes a callback function separable by two function parameters.
std::vector< PixelPosition > PixelPositions
Definition of a vector holding pixel positions (with positive coordinate values).
Definition: PixelPosition.h:48
The namespace covering the entire Ocean framework.
Definition: Accessor.h:15