Ocean
NonMaximumSuppression.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_NON_MAXIMUM_SUPPRESSION_H
9 #define META_OCEAN_CV_NON_MAXIMUM_SUPPRESSION_H
10 
11 #include "ocean/cv/CV.h"
12 #include "ocean/cv/PixelPosition.h"
13 
14 #include "ocean/base/Callback.h"
15 #include "ocean/base/ShiftVector.h"
16 #include "ocean/base/Worker.h"
17 
18 namespace Ocean
19 {
20 
21 namespace CV
22 {
23 
24 /**
25  * This class implements the possibility to find local maximum in a 2D array by applying a non-maximum-suppression search.
26  * The search is done within a 3x3 neighborhood (centered around the point of interest).<br>
27  * Use this class to determine e.g. reliable feature points.<br>
28  * The class supports bin accuracy (pixel accuracy) and sub-bin accuracy (sub-pixel accuracy).
29  *
30  * The non-maximum-suppression search is implemented by a vertical list holding maps of horizontal array elements.<br>
31  * The performance depends on the number of elements inserted into the individual maps.<br>
32  * Thus, do not add data elements with negligible value.
33  *
34  * It should be mentioned that the application of this class should be restricted to situations in which the entire filter response values do not exist already.<br>
35  * The performance boost comes with a simultaneous determination of filter responses and the insertion of possible candidates for maximum locations.
36  * @tparam T The data type of the individual elements that are applied for the non-maximum-suppression search.
37  * @ingroup cv
38  */
39 template <typename T>
41 {
42  public:
43 
44  /**
45  * This class extends a 2D position by a third parameter storing a strength value.
46  * @tparam TCoordinate The data type of a scalar coordinate
47  * @tparam TStrength The data type of the strength parameter
48  */
49  template <typename TCoordinate, typename TStrength>
50  class StrengthPosition : public VectorT2<TCoordinate>
51  {
52  public:
53 
54  /**
55  * Creates a new object with default strength parameter.
56  */
57  StrengthPosition() = default;
58 
59  /**
60  * Creates a new object with explicit position and strength parameter.
61  * @param x Horizontal position
62  * @param y Vertical position
63  * @param strength The strength parameter
64  */
65  inline StrengthPosition(const TCoordinate x, const TCoordinate y, const TStrength& strength);
66 
67  /**
68  * Returns the strength parameter of this object.
69  * @return Strength parameter
70  */
71  inline const TStrength& strength() const;
72 
73  /**
74  * Compares the strength value of two objects.
75  * @param left The left object to compare
76  * @param right The right object to compare
77  * @return True, if so
78  * @tparam tLeftLargerThanRight True, to return whether left's strength is larger than right's strength; False, to return whether right is larger than left
79  */
80  template <bool tLeftLargerThanRight = true>
82 
83  private:
84 
85  /// Strength parameter of this object.
86  TStrength strength_ = TStrength();
87  };
88 
89  /**
90  * Definition of a vector holding strength pixel positions.
91  */
92  template <typename TCoordinate, typename TStrength>
93  using StrengthPositions = std::vector<StrengthPosition<TCoordinate, TStrength>>;
94 
95  /**
96  * Definition of a callback function used to determine the precise sub-pixel position of a specific point.
97  * The first parameter provides the horizontal position.<br>
98  * The second parameter provides the vertical position.<br>
99  * The third parameter provides the strength value.<br>
100  * The fourth parameter receives the precise horizontal position.<br>
101  * The fifth parameter receives the precise vertical position.<br>
102  * The sixth parameter receives the precise strength value.<br>
103  * The return parameter should be True if the precise position could be determined
104  */
105  template <typename TCoordinate, typename TStrength>
107 
108  private:
109 
110  /**
111  * This class holds the horizontal position and strength parameter of an interest pixel.
112  */
114  {
115  public:
116 
117  /**
118  * Creates a new candidate object.
119  */
120  inline StrengthCandidate();
121 
122  /**
123  * Creates a new candidate object with horizontal position and strength parameter.
124  * @param x Horizontal position in pixel
125  * @param strength The strength parameter
126  */
127  inline StrengthCandidate(const unsigned int x, const T& strength);
128 
129  /**
130  * Returns the horizontal position of this candidate object.
131  * @return Horizontal position in pixel
132  */
133  inline unsigned int x() const;
134 
135  /**
136  * Returns the strength parameter of this object.
137  * @return Strength parameter
138  */
139  inline const T& strength() const;
140 
141  private:
142 
143  /// Horizontal position of this object.
144  unsigned int positionX_ = (unsigned int)(-1);
145 
146  /// Strength parameter of this object.
147  T strength_ = T();
148  };
149 
150  /**
151  * Definition of a vector holding strength candidate objects.
152  */
153  typedef std::vector<StrengthCandidate> StrengthCandidateRow;
154 
155  /**
156  * Definition of a vector holding a vector of strength candidates.
157  */
159 
160  public:
161 
162  /**
163  * Move constructor.
164  * @param nonMaximumSuppression The object to be moved
165  */
166  NonMaximumSuppression(NonMaximumSuppression<T>&& nonMaximumSuppression) noexcept;
167 
168  /**
169  * Copy constructor.
170  * @param nonMaximumSuppression The object to be moved
171  */
172  NonMaximumSuppression(const NonMaximumSuppression<T>& nonMaximumSuppression) noexcept;
173 
174  /**
175  * Creates a new maximum suppression object with a predefined size.
176  * @param width The width of this object in pixel, with range [3, infinity)
177  * @param height The height of this object in pixel, with range [3, infinity)
178  * @param yOffset Optional offset in the vertical direction moving the suppression region by the specified number of rows, with range [0, infinity)
179  */
180  NonMaximumSuppression(const unsigned int width, const unsigned int height, const unsigned int yOffset = 0u) noexcept;
181 
182  /**
183  * Returns the width of this object.
184  * @return Width in pixel
185  */
186  inline unsigned int width() const;
187 
188  /**
189  * Returns the height of this object.
190  * @return Height in pixel
191  */
192  inline unsigned int height() const;
193 
194  /**
195  * Returns the optional offset in the vertical direction.
196  * @return Optional vertical direction offset, 0 by default
197  */
198  inline unsigned int yOffset() const;
199 
200  /**
201  * Adds a new candidate to this object.
202  * Beware: Due to performance issues do no add candidates with negligible strength parameter.
203  * @param x Horizontal position in pixel, with range [0, width() - 1]
204  * @param y Vertical position in pixel, with range [yOffset(), yOffset() + height() - 1]
205  * @param strength The strength parameter
206  * @see addCandidates(), removeCandidatesFromRight().
207  */
208  inline void addCandidate(const unsigned int x, const unsigned int y, const T& strength);
209 
210  /**
211  * Adds new candidates to this object from a given buffer providing one value for each bin/pixel of this object.
212  * Beware: Due to performance reasons, you should use the addCandidate() function to add one single new candidate in the moment the filter response is larger than a specific threshold.<br>
213  * @param values The from which candidates will be added, must be width() * height() elements
214  * @param valuesPaddingElements The number of padding elements at the end of each values row, in elements, with range [0, infinity)
215  * @param firstColumn First column to be handled, with range [0, width() - 1]
216  * @param numberColumns Number of columns to be handled, with range [1, width() - firstColumn]
217  * @param firstRow First row to be handled, with range [yOffset(), height() - 1]
218  * @param numberRows Number of rows to be handled, with range [1, height() - firstRow]
219  * @param minimalThreshold The minimal threshold so that a value counts as candidate
220  * @param worker Optional worker object to distribute the computation
221  * @see addCandidate(), removeCandidatesFromRight().
222  */
223  void addCandidates(const T* values, const unsigned int valuesPaddingElements, const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, const T& minimalThreshold, Worker* worker);
224 
225  /**
226  * Removes all candidates from a specified row having a horizontal location equal or larger than a specified coordinate.
227  * @param x The horizontal coordinate specifying which candidates will be removed, all candidates with horizontal location >= x will be removed, with range [0, infinity)
228  * @param y The index of the row in which the candidates will be removed, with range [yOffset(), yOffset() + height() - 1]
229  */
230  inline void removeCandidatesRightFrom(const unsigned int x, const unsigned int y);
231 
232  /**
233  * Applies a non-maximum-suppression search on a given 2D frame in a 3x3 neighborhood (eight neighbors).
234  * This function allows to determine the precise position of the individual maximum value positions by application of a callback function determining the individual positions.<br>
235  * @param firstColumn First column to be handled, with range [1, width() - 1)
236  * @param numberColumns Number of columns to be handled
237  * @param firstRow First row to be handled, with range [yOffset() + 1, height() - 1)
238  * @param numberRows Number of rows to be handled
239  * @param worker Optional worker object to distribute the computation
240  * @param positionCallback Optional callback function allowing to determine the precise position of the individual maximum value positions
241  * @return Resulting non maximum suppressed positions including the strength parameters
242  * @tparam TCoordinate The data type of a scalar coordinate
243  * @tparam TStrength The data type of the strength parameter
244  * @tparam tStrictMaximum True, to search for a strict maximum (larger than all eight neighbors); False, to allow equal values in the upper left neighborhood
245  */
246  template <typename TCoordinate, typename TStrength, bool tStrictMaximum = true>
247  StrengthPositions<TCoordinate, TStrength> suppressNonMaximum(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, Worker* worker = nullptr, const PositionCallback<TCoordinate, TStrength>* positionCallback = nullptr) const;
248 
249  /**
250  * Removes the gathered non-maximum suppression information so that this object can be reused again (for the same task with same resolution etc.).
251  * The allocated memory will remain so that reusing this object may improve performance.
252  */
253  void reset();
254 
255  /**
256  * Move operator.
257  * @param nonMaximumSuppression Object to be moved
258  * @return Reference to this object
259  */
261 
262  /**
263  * Copy operator.
264  * @param nonMaximumSuppression The object to be copied
265  * @return Reference to this object
266  */
267  NonMaximumSuppression<T>& operator=(const NonMaximumSuppression<T>& nonMaximumSuppression);
268 
269  /**
270  * Applies a non-maximum-suppression based on already existing strength positions (just with a custom suppression radius) e.g., as a post-processing step.
271  * @param width The width of the image/domain in which the strength positions are located, e.g., in pixel, with range [1, infinity)
272  * @param height The height of the image/domain in which the strength positions are located, e.g., in pixel, with range [1, infinity)
273  * @param strengthPositions The strength positions for which a custom suppression-radius will be applied
274  * @param radius The suppression radius to be applied, with range [1, infinity)
275  * @param validIndices Optional resulting indices of all strength positions which remain after suppression
276  * @return The resulting strength positions
277  * @tparam TCoordinate The data type of a scalar coordinate
278  * @tparam TStrength The data type of the strength parameter
279  * @tparam tStrictMaximum True, to search for a strict maximum (larger than all eight neighbors); False, to allow equal values in the upper left neighborhood
280  */
281  template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
282  static StrengthPositions<TCoordinate, TStrength> suppressNonMaximum(const unsigned int width, const unsigned int height, const StrengthPositions<TCoordinate, TStrength>& strengthPositions, const TCoordinate radius, Indices32* validIndices = nullptr);
283 
284  /**
285  * Determines the precise peak location in 1D space for three discrete neighboring measurements at location x == 0.
286  * The precise peak is determined based on the first and second derivatives of the measurement values.
287  * @param leftValue The left discrete value, e.g., at location x - 1, with range (-infinity, middleValue]
288  * @param middleValue The middle discrete value, e.g., at location x, with range (-infinity, infinity)
289  * @param rightValue The discrete value, e.g., at location x + 1, with range (-infinity, middleValue]
290  * @param location The location of the precise peak of all values, with range (-1, 1)
291  * @return True, if succeeded
292  * @tparam TFloat The floating point data type to be used for calculation, either 'float' or 'double'
293  */
294  template <typename TFloat>
295  static bool determinePrecisePeakLocation1(const T& leftValue, const T& middleValue, const T& rightValue, TFloat& location);
296 
297  /**
298  * Determines the precise peak location in 2D space for nine discrete neighboring measurements at location x == 0, y == 0.
299  * The precise peak is determined based on the first and second derivatives of the measurement values.
300  * @param topValues The three discrete values in the top row, must be valid
301  * @param centerValues The three discrete values in the center row, must be valid
302  * @param bottomValues The three discrete values in the bottom row, must be valid
303  * @param location The location of the precise peak of all values, with range (-1, 1)
304  * @return True, if succeeded
305  * @tparam TFloat The floating point data type to be used for calculation, either 'float' or 'double'
306  */
307  template <typename TFloat>
308  static bool determinePrecisePeakLocation2(const T* const topValues, const T* const centerValues, const T* const bottomValues, VectorT2<TFloat>& location);
309 
310  private:
311 
312  /**
313  * Adds new candidates to this object from a subset of a given buffer providing one value for each bin/pixel of this object.
314  * @param values The from which candidates will be added, must be width() * height() elements
315  * @param valuesStrideElements The number of elements between two values rows, in elements, with range [0, infinity)
316  * @param minimalThreshold The minimal threshold so that a value counts as candidate
317  * @param firstColumn First column to be handled, with range [0, width() - 1]
318  * @param numberColumns Number of columns to be handled, with range [1, width() - firstColumn]
319  * @param firstRow The first row in the buffer from which the candidates will be added, with range [yOffset(), height())
320  * @param numberRows The number of rows to be handled, with range [1, height() - firstRow]
321  * @see addCandidates().
322  */
323  void addCandidatesSubset(const T* values, const unsigned int valuesStrideElements, const unsigned int firstColumn, const unsigned int numberColumns, const T* minimalThreshold, const unsigned int firstRow, const unsigned int numberRows);
324 
325  /**
326  * Applies a non-maximum-suppression search on a subset of a given 2D frame in a 3x3 neighborhood (eight neighbors).
327  * This function allows to determine the precise position of the individual maximum value positions by application of a callback function determining the individual positions.<br>
328  * @param strengthPositions Resulting non maximum suppressed positions including the strength parameters
329  * @param firstColumn First column to be handled
330  * @param numberColumns Number of columns to be handled
331  * @param lock The lock object that must be defined if this function is executed in parallel on several threads
332  * @param positionCallback Optional callback function allowing to determine the precise position of the individual maximum value positions
333  * @param firstRow First row to be handled
334  * @param numberRows Number of rows to be handled
335  * @tparam tStrictMaximum True, to search for a strict maximum (larger than all eight neighbors); False, to allow equal values in the upper left neighborhood
336  */
337  template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
338  void suppressNonMaximumSubset(StrengthPositions<TCoordinate, TStrength>* strengthPositions, const unsigned int firstColumn, const unsigned int firstRow, Lock* lock, const PositionCallback<TCoordinate, TStrength>* positionCallback, const unsigned int numberColumns, const unsigned int numberRows) const;
339 
340  private:
341 
342  /// Width of this object.
343  unsigned int width_ = 0u;
344 
345  /// All candidate rows.
347 };
348 
349 template <typename T>
350 template <typename TCoordinate, typename TStrength>
351 inline NonMaximumSuppression<T>::StrengthPosition<TCoordinate, TStrength>::StrengthPosition(const TCoordinate x, const TCoordinate y, const TStrength& strength) :
352  VectorT2<TCoordinate>(x, y),
353  strength_(strength)
354 {
355  // nothing to do here
356 }
357 
358 template <typename T>
359 template <typename TCoordinate, typename TStrength>
361 {
362  return strength_;
363 }
364 
365 template <typename T>
366 template <typename TCoordinate, typename TStrength>
367 template <bool tLeftLargerThanRight>
369 {
370  if constexpr (tLeftLargerThanRight)
371  {
372  return left.strength() > right.strength();
373  }
374  else
375  {
376  return left.strength() < right.strength();
377  }
378 }
379 
380 template <typename T>
382  positionX_(-1),
383  strength_(T())
384 {
385  // nothing to do here
386 }
387 
388 template <typename T>
389 inline NonMaximumSuppression<T>::StrengthCandidate::StrengthCandidate(const unsigned int x, const T& strength) :
390  positionX_(x),
391  strength_(strength)
392 {
393  // nothing to do here
394 }
395 
396 template <typename T>
398 {
399  return positionX_;
400 }
401 
402 template <typename T>
404 {
405  return strength_;
406 }
407 
408 template <typename T>
410 {
411  *this = std::move(nonMaximumSuppression);
412 }
413 
414 template <typename T>
416  width_(nonMaximumSuppression.width_),
417  rows_(nonMaximumSuppression.rows_)
418 {
419  // nothing to do here
420 }
421 
422 template <typename T>
423 NonMaximumSuppression<T>::NonMaximumSuppression(const unsigned int width, const unsigned int height, const unsigned int yOffset) noexcept :
424  width_(width),
426 {
427  // nothing to do here
428 }
429 
430 template <typename T>
431 inline unsigned int NonMaximumSuppression<T>::width() const
432 {
433  return width_;
434 }
435 
436 template <typename T>
437 inline unsigned int NonMaximumSuppression<T>::height() const
438 {
439  return (unsigned int)rows_.size();
440 }
441 
442 template <typename T>
443 inline unsigned int NonMaximumSuppression<T>::yOffset() const
444 {
445  ocean_assert(rows_.firstIndex() >= 0);
446 
447  return (unsigned int)(rows_.firstIndex());
448 }
449 
450 template <typename T>
451 inline void NonMaximumSuppression<T>::addCandidate(const unsigned int x, const unsigned int y, const T& strength)
452 {
453  ocean_assert(x < width_);
454 
455  ocean_assert(rows_.isValidIndex(y) && y >= (unsigned int)(rows_.firstIndex()) && y <= (unsigned int)(rows_.endIndex()));
456 
457  if (rows_[y].empty())
458  {
459  rows_[y].reserve(128);
460  }
461 
462  rows_[y].emplace_back(x, strength);
463 }
464 
465 template <typename T>
466 void NonMaximumSuppression<T>::addCandidates(const T* values, const unsigned int valuesPaddingElements, const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, const T& minimalThreshold, Worker* worker)
467 {
468  ocean_assert(values != nullptr);
469 
470  ocean_assert(firstColumn + numberColumns <= width_);
471  ocean_assert(typename StrengthCandidateRows::Index(firstRow) >= rows_.firstIndex());
472  ocean_assert(typename StrengthCandidateRows::Index(firstRow + numberRows) <= rows_.endIndex());
473 
474  const unsigned int valuesStrideElements = width_ + valuesPaddingElements;
475 
476  if (worker)
477  {
478  worker->executeFunction(Worker::Function::create(*this, &NonMaximumSuppression<T>::addCandidatesSubset, values, valuesStrideElements, firstColumn, numberColumns, &minimalThreshold, 0u, 0u), firstRow, numberRows, 5u, 6u, 20u);
479  }
480  else
481  {
482  addCandidatesSubset(values, valuesStrideElements, firstColumn, numberColumns, &minimalThreshold, firstRow, numberRows);
483  }
484 }
485 
486 template <typename T>
487 inline void NonMaximumSuppression<T>::removeCandidatesRightFrom(const unsigned int x, const unsigned int y)
488 {
489  ocean_assert(rows_.isValidIndex(y) && y >= (unsigned int)(rows_.firstIndex()) && y <= (unsigned int)(rows_.endIndex()));
490 
491  StrengthCandidateRow& suppressionRow = rows_[y];
492 
493  while (!suppressionRow.empty())
494  {
495  if (suppressionRow.back().x() >= x)
496  {
497  suppressionRow.pop_back();
498  }
499  else
500  {
501  break;
502  }
503  }
504 }
505 
506 template <typename T>
507 template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
508 typename NonMaximumSuppression<T>::template StrengthPositions<TCoordinate, TStrength> NonMaximumSuppression<T>::suppressNonMaximum(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, Worker* worker, const PositionCallback<TCoordinate, TStrength>* positionCallback) const
509 {
510  ocean_assert(firstColumn + numberColumns <= width_);
511  ocean_assert(firstRow >= (unsigned int)rows_.firstIndex() && firstRow + numberRows <= (unsigned int)rows_.endIndex());
512 
514  result.reserve(100);
515 
516  if (worker)
517  {
518  Lock lock;
519  worker->executeFunction(Worker::Function::create(*this, &NonMaximumSuppression<T>::suppressNonMaximumSubset<TCoordinate, TStrength, tStrictMaximum>, &result, firstColumn, numberColumns, &lock, positionCallback, 0u, 0u), firstRow, numberRows, 5u, 6u, 3u);
520  }
521  else
522  {
523  suppressNonMaximumSubset<TCoordinate, TStrength, tStrictMaximum>(&result, firstColumn, numberColumns, nullptr, positionCallback, firstRow, numberRows);
524  }
525 
526  return result;
527 }
528 
529 template <typename T>
531 {
532  for (ptrdiff_t n = rows_.firstIndex(); n < rows_.endIndex(); ++n)
533  {
534  rows_[n].clear();
535  }
536 }
537 
538 template <typename T>
540 {
541  if (this != &nonMaximumSuppression)
542  {
543  width_ = nonMaximumSuppression.width_;
544  nonMaximumSuppression.width_ = 0u;
545 
546  rows_ = std::move(nonMaximumSuppression.rows_);
547  }
548 
549  return *this;
550 }
551 
552 template <typename T>
554 {
555  if (this != &nonMaximumSuppression)
556  {
557  width_ = nonMaximumSuppression.width_;
558  rows_ = nonMaximumSuppression.rows_;
559  }
560 
561  return *this;
562 }
563 
564 template <typename T>
565 template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
566 typename NonMaximumSuppression<T>::template StrengthPositions<TCoordinate, TStrength> NonMaximumSuppression<T>::suppressNonMaximum(const unsigned int width, const unsigned int height, const StrengthPositions<TCoordinate, TStrength>& strengthPositions, const TCoordinate radius, Indices32* validIndices)
567 {
568  ocean_assert(width >= 1u && height >= 1u);
569  ocean_assert(radius >= TCoordinate(1));
570 
571  const unsigned int binSize = std::max(10u, (unsigned int)(NumericT<TCoordinate>::ceil(radius)));
572 
573  const unsigned int horizontalBins = std::max(1u, (width + binSize - 1u) / binSize);
574  const unsigned int verticalBins = std::max(1u, (height + binSize - 1u) / binSize);
575 
576  ocean_assert(binSize * horizontalBins >= width);
577  ocean_assert(binSize * verticalBins >= height);
578 
579  IndexGroups32 indexGroups(horizontalBins * verticalBins);
580 
581  // distributing all strength positions into a regular grid to reduce search space later
582 
583  for (size_t n = 0; n < strengthPositions.size(); ++n)
584  {
585  const VectorT2<TCoordinate>& position = strengthPositions[n];
586 
587  ocean_assert((unsigned int)(position.x()) < width);
588  ocean_assert((unsigned int)(position.y()) < height);
589 
590  const unsigned int xBin = (unsigned int)(position.x()) / binSize;
591  const unsigned int yBin = (unsigned int)(position.y()) / binSize;
592 
593  ocean_assert(xBin <= horizontalBins);
594  ocean_assert(yBin <= verticalBins);
595 
596  indexGroups[yBin * horizontalBins + xBin].emplace_back(Index32(n));
597  }
598 
599  std::vector<uint8_t> validPositions(strengthPositions.size(), 1u);
600 
601  const TCoordinate sqrRadius = radius * radius;
602 
603  for (size_t nCandidate = 0; nCandidate < strengthPositions.size(); ++nCandidate)
604  {
605  if (validPositions[nCandidate] == 0u)
606  {
607  // the positions is already suppressed
608  continue;
609  }
610 
611  const StrengthPosition<TCoordinate, TStrength>& candidatePosition = strengthPositions[nCandidate];
612 
613  const unsigned int xCandidateBin = (unsigned int)(candidatePosition.x()) / binSize;
614  const unsigned int yCandidateBin = (unsigned int)(candidatePosition.y()) / binSize;
615 
616  ocean_assert(xCandidateBin <= horizontalBins);
617  ocean_assert(yCandidateBin <= verticalBins);
618 
619  bool checkNextCandidate = false;
620 
621  for (unsigned int yBin = (unsigned int)(max(0, int(yCandidateBin) - 1)); !checkNextCandidate && yBin < min(yCandidateBin + 2u, verticalBins); ++yBin)
622  {
623  for (unsigned int xBin = (unsigned int)(max(0, int(xCandidateBin) - 1)); !checkNextCandidate && xBin < min(xCandidateBin + 2u, horizontalBins); ++xBin)
624  {
625  const Indices32& indices = indexGroups[yBin * horizontalBins + xBin];
626 
627  for (const Index32& nTest : indices)
628  {
629  if (nTest == Index32(nCandidate))
630  {
631  continue;
632  }
633 
634  const StrengthPosition<TCoordinate, TStrength>& testPosition = strengthPositions[nTest];
635 
636  // we do not check whether test position is suppressed already (as the test position may still be the reason to suppress the candidate position)
637 
638  if (candidatePosition.sqrDistance(testPosition) <= sqrRadius)
639  {
640  if (candidatePosition.strength() > testPosition.strength())
641  {
642  validPositions[nTest] = 0u;
643  }
644  else if (candidatePosition.strength() < testPosition.strength())
645  {
646  validPositions[nCandidate] = 0u;
647 
648  checkNextCandidate = true;
649  break;
650  }
651  else
652  {
653  ocean_assert(candidatePosition.strength() == testPosition.strength());
654 
655  if constexpr (tStrictMaximum)
656  {
657  // we suppress both elements, as we seek a strict maximum element
658 
659  validPositions[nCandidate] = 0u;
660  validPositions[nTest] = 0u;
661 
662  checkNextCandidate = true;
663  break;
664  }
665  else
666  {
667  // we will suppress one of both elements, as we accept a non-strict maximum element
668 
669  if (candidatePosition.y() < testPosition.y() || (candidatePosition.y() == testPosition.y() && candidatePosition.x() < testPosition.x()))
670  {
671  // the candidate position will be suppressed as the test position is located to the bottom/right of the candidate position
672 
673  validPositions[nCandidate] = 0u;
674 
675  checkNextCandidate = true;
676  break;
677  }
678  else
679  {
680  ocean_assert(testPosition.y() < candidatePosition.y() || (testPosition.y() == candidatePosition.y() && testPosition.x() < candidatePosition.x()));
681 
682  // the test position will be suppressed as the candidate position is located to the bottom/right of the test position
683 
684  validPositions[nTest] = 0u;
685  }
686  }
687  }
688  }
689  }
690  }
691  }
692  }
693 
695  remainingPositions.reserve(strengthPositions.size());
696 
697  if (validIndices)
698  {
699  ocean_assert(validIndices->empty());
700 
701  validIndices->clear();
702  validIndices->reserve(strengthPositions.size());
703 
704  for (size_t n = 0; n < validPositions.size(); ++n)
705  {
706  if (validPositions[n])
707  {
708  remainingPositions.emplace_back(strengthPositions[n]);
709  validIndices->emplace_back(Index32(n));
710  }
711  }
712  }
713  else
714  {
715  for (size_t n = 0; n < validPositions.size(); ++n)
716  {
717  if (validPositions[n])
718  {
719  remainingPositions.emplace_back(strengthPositions[n]);
720  }
721  }
722  }
723 
724  return remainingPositions;
725 }
726 
727 template <typename T>
728 template <typename TFloat>
729 bool NonMaximumSuppression<T>::determinePrecisePeakLocation1(const T& leftValue, const T& middleValue, const T& rightValue, TFloat& location)
730 {
731  static_assert(std::is_floating_point<TFloat>::value, "Invalid floating point data type!");
732 
733  // f(x) = f(a) + f`(a) * (x - a)
734 
735  // we expect our middle value to be located at a = 0:
736  // f(x) = f(0) + f`(0) * x
737 
738  // 0 = f'(x)
739  // = f'(0) + f''(0) * x
740 
741  // x = - f'(0) / f''(0)
742 
743  // f`(x) = [-1 0 1] * 1/2
744  const TFloat df = (TFloat(rightValue) - TFloat(leftValue)) * TFloat(0.5);
745 
746  // f``(x) = [1 -2 1] * 1/1
747  const TFloat dff = TFloat(leftValue) + TFloat(rightValue) - TFloat(middleValue) * TFloat(2);
748 
750  {
751  location = TFloat(0);
752  return true;
753  }
754 
755  const TFloat x = -df / dff;
756 
757  if (x < TFloat(-1) || x > TFloat(1))
758  {
759  return false;
760  }
761 
762  location = x;
763  return true;
764 }
765 
766 template <typename T>
767 template <typename TFloat>
768 bool NonMaximumSuppression<T>::determinePrecisePeakLocation2(const T* const topValues, const T* const centerValues, const T* const bottomValues, VectorT2<TFloat>& location)
769 {
770  static_assert(std::is_floating_point<TFloat>::value, "Invalid floating point data type!");
771 
772  const T& value00 = topValues[0];
773  const T& value01 = topValues[1];
774  const T& value02 = topValues[2];
775 
776  const T& value10 = centerValues[0];
777  const T& value11 = centerValues[1];
778  const T& value12 = centerValues[2];
779 
780  const T& value20 = bottomValues[0];
781  const T& value21 = bottomValues[1];
782  const T& value22 = bottomValues[2];
783 
784 #if 0
785  // some response values may not perfectly follow the peak criteria so that we do not use the asserts by default
786  ocean_assert(value11 >= value00 && value11 >= value01 && value11 >= value02);
787  ocean_assert(value11 >= value10 && value11 >= value12);
788  ocean_assert(value11 >= value20 && value11 >= value21 && value11 >= value22);
789 #endif
790 
791  // [-1 0 1] * 1/2
792  const TFloat dx = TFloat(value12 - value10) * TFloat(0.5);
793  const TFloat dy = TFloat(value21 - value01) * TFloat(0.5);
794 
795  // [1 -2 1] * 1/1
796  const TFloat dxx = TFloat(value12 + value10 - value11 * TFloat(2));
797  const TFloat dyy = TFloat(value21 + value01 - value11 * TFloat(2));
798 
799  // [ 1 0 -1 ]
800  // [ 0 0 0 ] * 1/4
801  // [-1 0 1 ]
802 
803  const TFloat dxy = TFloat(value22 + value00 - value20 - value02) * TFloat(0.25);
804 
805  const TFloat denominator = dxx * dyy - dxy * dxy;
806 
807  if (NumericT<TFloat>::isEqualEps(denominator))
808  {
809  location = VectorT2<TFloat>(0, 0);
810  return true;
811  }
812 
813  const TFloat factor = TFloat(1) / denominator;
814 
815  const TFloat offsetX = -(dyy * dx - dxy * dy) * factor;
816  const TFloat offsetY = -(dxx * dy - dxy * dx) * factor;
817 
818  if (offsetX < TFloat(-1) || offsetX > TFloat(1) || offsetY < TFloat(-1) || offsetY > TFloat(1))
819  {
820  return false;
821  }
822 
823  location = VectorT2<TFloat>(offsetX, offsetY);
824  return true;
825 }
826 
827 template <typename T>
828 void NonMaximumSuppression<T>::addCandidatesSubset(const T* values, const unsigned int valuesStrideElements, const unsigned int firstColumn, const unsigned int numberColumns, const T* minimalThreshold, const unsigned int firstRow, const unsigned int numberRows)
829 {
830  ocean_assert(values != nullptr);
831  ocean_assert(valuesStrideElements >= width_);
832  ocean_assert(firstColumn + numberColumns <= width_);
833 
834  ocean_assert(typename StrengthCandidateRows::Index(firstRow) >= rows_.firstIndex());
835  ocean_assert(typename StrengthCandidateRows::Index(firstRow + numberRows) <= rows_.endIndex());
836 
837  const T localThreshold = *minimalThreshold;
838 
839  values += firstRow * valuesStrideElements;
840 
841  for (unsigned int y = firstRow; y < firstRow + numberRows; ++y)
842  {
843  for (unsigned int x = firstColumn; x < firstColumn + numberColumns; ++x)
844  {
845  if (values[x] >= localThreshold)
846  {
847  addCandidate(x, y, values[x]);
848  }
849  }
850 
851  values += valuesStrideElements;
852  }
853 }
854 
855 template <typename T>
856 template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
857 void NonMaximumSuppression<T>::suppressNonMaximumSubset(StrengthPositions<TCoordinate, TStrength>* strengthPositions, const unsigned int firstColumn, const unsigned int numberColumns, Lock* lock, const PositionCallback<TCoordinate, TStrength>* positionCallback, const unsigned int firstRow, const unsigned int numberRows) const
858 {
859  ocean_assert(strengthPositions);
860 
861  ocean_assert(firstColumn + numberColumns <= width_);
862  ocean_assert(firstRow >= (unsigned int)rows_.firstIndex());
863  ocean_assert(firstRow + numberRows <= (unsigned int)rows_.endIndex());
864 
865  if (numberColumns < 3u || numberRows < 3u)
866  {
867  return;
868  }
869 
870  const unsigned int firstCenterColumn = max(1u, firstColumn);
871  const unsigned int endCenterColumn = min(firstColumn + numberColumns, width_ - 1u);
872 
873  const unsigned int firstCenterRow = max((unsigned int)rows_.firstIndex() + 1u, firstRow);
874  const unsigned int endCenterRow = min(firstRow + numberRows, (unsigned int)rows_.lastIndex());
875 
876  ocean_assert(firstCenterRow >= 1u);
877 
878  StrengthPositions<TCoordinate, TStrength> localStrengthPositions;
879  localStrengthPositions.reserve(100);
880 
881  for (unsigned int y = firstCenterRow; y < endCenterRow; ++y)
882  {
883  const StrengthCandidateRow& row0 = rows_[y - 1u];
884  const StrengthCandidateRow& row1 = rows_[y + 0u];
885  const StrengthCandidateRow& row2 = rows_[y + 1u];
886 
887  typename StrengthCandidateRow::const_iterator i0 = row0.begin();
888  typename StrengthCandidateRow::const_iterator i2 = row2.begin();
889 
890  typename StrengthCandidateRow::const_iterator i1Minus = row1.end();
891  typename StrengthCandidateRow::const_iterator i1Plus = row1.size() > 1 ? row1.begin() + 1 : row1.end();
892 
893  for (typename StrengthCandidateRow::const_iterator i1 = row1.begin(); i1 != row1.end(); ++i1)
894  {
895  ocean_assert(i1->x() >= 0u && i1->x() + 1u <= width_);
896 
897  // check left candidate (west)
898  if (i1->x() >= firstCenterColumn && i1->x() < endCenterColumn && (i1Minus == row1.end() || i1Minus->x() + 1u != i1->x() || (tStrictMaximum && i1Minus->strength() < i1->strength()) || (!tStrictMaximum && i1Minus->strength() <= i1->strength())))
899  {
900  // check right candidate (east)
901  if (i1Plus == row1.end() || i1Plus->x() != i1->x() + 1u || i1Plus->strength() < i1->strength())
902  {
903  // set the top row iterator to the right position
904  while (i0 != row0.end())
905  {
906  if (i0->x() + 1u < i1->x())
907  {
908  ++i0;
909  }
910  else
911  {
912  break;
913  }
914  }
915 
916  // now i0 should point at least to the north west pixel position (or more far east)
917  ocean_assert(i0 == row0.end() || i0->x() + 1u >= i1->x());
918 
919  if (i0 != row0.end() && i0->x() <= i1->x() + 1u)
920  {
921  ocean_assert(i0->x() + 1u == i1->x() || i0->x() == i1->x() || i0->x() - 1u == i1->x());
922 
923  if ((tStrictMaximum && i0->strength() >= i1->strength()) || (!tStrictMaximum && i0->strength() > i1->strength()))
924  {
925  goto next;
926  }
927 
928  // check if there is a further candidate in the north row
929 
930  const typename StrengthCandidateRow::const_iterator i0Plus = i0 + 1;
931 
932  if (i0Plus != row0.end() && i0Plus->x() <= i1->x() + 1u)
933  {
934  if ((tStrictMaximum && i0Plus->strength() >= i1->strength()) || (!tStrictMaximum && i0Plus->strength() > i1->strength()))
935  {
936  goto next;
937  }
938 
939  // check if there is a further candidate in the north row
940 
941  const typename StrengthCandidateRow::const_iterator i0PlusPlus = i0Plus + 1;
942 
943  if (i0PlusPlus != row0.end() && i0PlusPlus->x() <= i1->x() + 1u)
944  {
945  ocean_assert(i0PlusPlus->x() == i1->x() + 1u);
946 
947  if ((tStrictMaximum && i0PlusPlus->strength() >= i1->strength()) || (!tStrictMaximum && i0PlusPlus->strength() > i1->strength()))
948  {
949  goto next;
950  }
951  }
952  }
953  }
954 
955 
956  // set the bottom row iterator to the right position
957  while (i2 != row2.end())
958  {
959  if (i2->x() + 1u < i1->x())
960  {
961  ++i2;
962  }
963  else
964  {
965  break;
966  }
967  }
968 
969  // now i2 should point at least to the south west pixel position (or more far east)
970  ocean_assert(i2 == row2.end() || i2->x() + 1u >= i1->x());
971 
972  if (i2 != row2.end() && i2->x() <= i1->x() + 1u)
973  {
974  ocean_assert(i2->x() + 1u == i1->x() || i2->x() == i1->x() || i2->x() - 1u == i1->x());
975 
976  if (i2->x() + 1u == i1->x())
977  {
978  // i2 points to the south west pixel
979 
980  if ((tStrictMaximum && i2->strength() >= i1->strength()) || (!tStrictMaximum && i2->strength() > i1->strength()))
981  {
982  goto next;
983  }
984  }
985  else
986  {
987  if (i2->strength() >= i1->strength())
988  {
989  goto next;
990  }
991  }
992 
993  // check if there is a further candidate in the south row
994 
995  const typename StrengthCandidateRow::const_iterator i2Plus = i2 + 1;
996 
997  if (i2Plus != row2.end() && i2Plus->x() <= i1->x() + 1u)
998  {
999  if (i2Plus->strength() >= i1->strength())
1000  goto next;
1001 
1002  // check if there is a further candidate in the south row
1003 
1004  const typename StrengthCandidateRow::const_iterator i2PlusPlus = i2Plus + 1;
1005 
1006  if (i2PlusPlus != row2.end() && i2PlusPlus->x() <= i1->x() + 1u)
1007  {
1008  ocean_assert(i2PlusPlus->x() == i1->x() + 1u);
1009 
1010  if (i2PlusPlus->strength() >= i1->strength())
1011  goto next;
1012  }
1013  }
1014  }
1015 
1016  if (positionCallback)
1017  {
1018  TCoordinate preciseX, preciseY;
1019  TStrength preciseStrength;
1020  if ((*positionCallback)(i1->x(), y, i1->strength(), preciseX, preciseY, preciseStrength))
1021  {
1022  localStrengthPositions.emplace_back(preciseX, preciseY, preciseStrength);
1023  }
1024  }
1025  else
1026  {
1027  localStrengthPositions.emplace_back(TCoordinate(i1->x()), TCoordinate(y), i1->strength());
1028  }
1029  }
1030  }
1031 
1032 next:
1033 
1034  i1Minus = i1;
1035 
1036  if (i1Plus != row1.end())
1037  ++i1Plus;
1038  }
1039  }
1040 
1041  const OptionalScopedLock scopedLock(lock);
1042 
1043  strengthPositions->insert(strengthPositions->end(), localStrengthPositions.begin(), localStrengthPositions.end());
1044 }
1045 
1046 }
1047 
1048 }
1049 
1050 #endif // META_OCEAN_CV_NON_MAXIMUM_SUPPRESSION_H
This class holds the horizontal position and strength parameter of an interest pixel.
Definition: NonMaximumSuppression.h:114
StrengthCandidate()
Creates a new candidate object.
Definition: NonMaximumSuppression.h:381
const T & strength() const
Returns the strength parameter of this object.
Definition: NonMaximumSuppression.h:403
unsigned int x() const
Returns the horizontal position of this candidate object.
Definition: NonMaximumSuppression.h:397
T strength_
Strength parameter of this object.
Definition: NonMaximumSuppression.h:147
unsigned int positionX_
Horizontal position of this object.
Definition: NonMaximumSuppression.h:144
This class extends a 2D position by a third parameter storing a strength value.
Definition: NonMaximumSuppression.h:51
static bool compareStrength(const StrengthPosition< TCoordinate, TStrength > &left, const StrengthPosition< TCoordinate, TStrength > &right)
Compares the strength value of two objects.
Definition: NonMaximumSuppression.h:368
StrengthPosition()=default
Creates a new object with default strength parameter.
const TStrength & strength() const
Returns the strength parameter of this object.
Definition: NonMaximumSuppression.h:360
TStrength strength_
Strength parameter of this object.
Definition: NonMaximumSuppression.h:86
This class implements the possibility to find local maximum in a 2D array by applying a non-maximum-s...
Definition: NonMaximumSuppression.h:41
NonMaximumSuppression< T > & operator=(NonMaximumSuppression< T > &&nonMaximumSuppression)
Move operator.
Definition: NonMaximumSuppression.h:539
unsigned int width_
Width of this object.
Definition: NonMaximumSuppression.h:343
StrengthCandidateRows rows_
All candidate rows.
Definition: NonMaximumSuppression.h:346
static bool determinePrecisePeakLocation1(const T &leftValue, const T &middleValue, const T &rightValue, TFloat &location)
Determines the precise peak location in 1D space for three discrete neighboring measurements at locat...
Definition: NonMaximumSuppression.h:729
void reset()
Removes the gathered non-maximum suppression information so that this object can be reused again (for...
Definition: NonMaximumSuppression.h:530
unsigned int height() const
Returns the height of this object.
Definition: NonMaximumSuppression.h:437
static StrengthPositions< TCoordinate, TStrength > suppressNonMaximum(const unsigned int width, const unsigned int height, const StrengthPositions< TCoordinate, TStrength > &strengthPositions, const TCoordinate radius, Indices32 *validIndices=nullptr)
Applies a non-maximum-suppression based on already existing strength positions (just with a custom su...
unsigned int yOffset() const
Returns the optional offset in the vertical direction.
Definition: NonMaximumSuppression.h:443
StrengthPositions< TCoordinate, TStrength > suppressNonMaximum(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, Worker *worker=nullptr, const PositionCallback< TCoordinate, TStrength > *positionCallback=nullptr) const
Applies a non-maximum-suppression search on a given 2D frame in a 3x3 neighborhood (eight neighbors).
void suppressNonMaximumSubset(StrengthPositions< TCoordinate, TStrength > *strengthPositions, const unsigned int firstColumn, const unsigned int firstRow, Lock *lock, const PositionCallback< TCoordinate, TStrength > *positionCallback, const unsigned int numberColumns, const unsigned int numberRows) const
Applies a non-maximum-suppression search on a subset of a given 2D frame in a 3x3 neighborhood (eight...
Definition: NonMaximumSuppression.h:857
void addCandidatesSubset(const T *values, const unsigned int valuesStrideElements, const unsigned int firstColumn, const unsigned int numberColumns, const T *minimalThreshold, const unsigned int firstRow, const unsigned int numberRows)
Adds new candidates to this object from a subset of a given buffer providing one value for each bin/p...
Definition: NonMaximumSuppression.h:828
void removeCandidatesRightFrom(const unsigned int x, const unsigned int y)
Removes all candidates from a specified row having a horizontal location equal or larger than a speci...
Definition: NonMaximumSuppression.h:487
std::vector< StrengthPosition< TCoordinate, TStrength > > StrengthPositions
Definition of a vector holding strength pixel positions.
Definition: NonMaximumSuppression.h:93
void addCandidate(const unsigned int x, const unsigned int y, const T &strength)
Adds a new candidate to this object.
Definition: NonMaximumSuppression.h:451
static bool determinePrecisePeakLocation2(const T *const topValues, const T *const centerValues, const T *const bottomValues, VectorT2< TFloat > &location)
Determines the precise peak location in 2D space for nine discrete neighboring measurements at locati...
Definition: NonMaximumSuppression.h:768
unsigned int width() const
Returns the width of this object.
Definition: NonMaximumSuppression.h:431
NonMaximumSuppression(NonMaximumSuppression< T > &&nonMaximumSuppression) noexcept
Move constructor.
Definition: NonMaximumSuppression.h:409
void addCandidates(const T *values, const unsigned int valuesPaddingElements, const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, const T &minimalThreshold, Worker *worker)
Adds new candidates to this object from a given buffer providing one value for each bin/pixel of this...
Definition: NonMaximumSuppression.h:466
ShiftVector< StrengthCandidateRow > StrengthCandidateRows
Definition of a vector holding a vector of strength candidates.
Definition: NonMaximumSuppression.h:158
std::vector< StrengthCandidate > StrengthCandidateRow
Definition of a vector holding strength candidate objects.
Definition: NonMaximumSuppression.h:153
This class implements a container for callback functions.
Definition: Callback.h:3456
static Caller< void > create(CT &object, typename MemberFunctionPointerMaker< CT, 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 member function with no function parameter.
Definition: Caller.h:3023
This class implements a recursive lock object.
Definition: Lock.h:31
This class provides basic numeric functionalities.
Definition: Numeric.h:57
This class implements an optional recursive scoped lock object locking the lock object only if it's d...
Definition: Lock.h:325
bool isValidIndex(const Index index) const
Returns whether a specific index is valid for this vector and matches to the current offset layout.
Definition: ShiftVector.h:646
size_t size() const
Returns the number of elements that are stored by this object.
Definition: ShiftVector.h:490
Index endIndex() const
Returns the index of the element behind the last (excluding) element of this object.
Definition: ShiftVector.h:435
void clear()
Clears this object, the specified index shift will be untouched.
Definition: ShiftVector.h:658
std::ptrdiff_t Index
Definition of an element index.
Definition: ShiftVector.h:38
Iterator begin()
Returns the iterator for the first data element.
Definition: ShiftVector.h:670
Index lastIndex() const
Returns the index of the last (including) element of this object.
Definition: ShiftVector.h:422
Index firstIndex() const
Returns the index of the first element of this object.
Definition: ShiftVector.h:416
This class implements a vector with two elements.
Definition: Vector2.h:96
const TCoordinate & x() const noexcept
Returns the x value.
Definition: Vector2.h:698
const TCoordinate & y() const noexcept
Returns the y value.
Definition: Vector2.h:710
T sqrDistance(const VectorT2< T > &right) const
Returns the square distance between this 2D position and a second 2D position.
Definition: Vector2.h:633
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< Indices32 > IndexGroups32
Definition of a vector holding 32 bit indices, so we have groups of indices.
Definition: Base.h:102
std::vector< Index32 > Indices32
Definition of a vector holding 32 bit index values.
Definition: Base.h:96
uint32_t Index32
Definition of a 32 bit index value.
Definition: Base.h:84
The namespace covering the entire Ocean framework.
Definition: Accessor.h:15