Ocean
Loading...
Searching...
No Matches
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"
13
14#include "ocean/base/Callback.h"
16#include "ocean/base/Worker.h"
17
18namespace Ocean
19{
20
21namespace 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 */
39template <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
349template <typename T>
350template <typename TCoordinate, typename TStrength>
352 VectorT2<TCoordinate>(x, y),
353 strength_(strength)
354{
355 // nothing to do here
356}
357
358template <typename T>
359template <typename TCoordinate, typename TStrength>
361{
362 return strength_;
363}
364
365template <typename T>
366template <typename TCoordinate, typename TStrength>
367template <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
380template <typename T>
382 positionX_(-1),
383 strength_(T())
384{
385 // nothing to do here
386}
387
388template <typename T>
389inline 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
396template <typename T>
398{
399 return positionX_;
400}
401
402template <typename T>
404{
405 return strength_;
406}
407
408template <typename T>
410{
411 *this = std::move(nonMaximumSuppression);
412}
413
414template <typename T>
416 width_(nonMaximumSuppression.width_),
417 rows_(nonMaximumSuppression.rows_)
418{
419 // nothing to do here
420}
421
422template <typename T>
423NonMaximumSuppression<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
430template <typename T>
431inline unsigned int NonMaximumSuppression<T>::width() const
432{
433 return width_;
434}
435
436template <typename T>
437inline unsigned int NonMaximumSuppression<T>::height() const
438{
439 return (unsigned int)rows_.size();
440}
441
442template <typename T>
443inline unsigned int NonMaximumSuppression<T>::yOffset() const
444{
445 ocean_assert(rows_.firstIndex() >= 0);
446
447 return (unsigned int)(rows_.firstIndex());
448}
449
450template <typename T>
451inline 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
465template <typename T>
466void 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
486template <typename T>
487inline 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
506template <typename T>
507template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
508typename 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
529template <typename T>
531{
532 for (ptrdiff_t n = rows_.firstIndex(); n < rows_.endIndex(); ++n)
533 {
534 rows_[n].clear();
535 }
536}
537
538template <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
552template <typename T>
554{
555 if (this != &nonMaximumSuppression)
556 {
557 width_ = nonMaximumSuppression.width_;
558 rows_ = nonMaximumSuppression.rows_;
559 }
560
561 return *this;
562}
563
564template <typename T>
565template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
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
727template <typename T>
728template <typename TFloat>
729bool 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
766template <typename T>
767template <typename TFloat>
768bool 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
827template <typename T>
828void 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
855template <typename T>
856template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
857void 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
1032next:
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
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).
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
unsigned int yOffset() const
Returns the optional offset in the vertical direction.
Definition NonMaximumSuppression.h:443
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
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...
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
This class implements a vector with shifted elements.
Definition ShiftVector.h:27
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:710
const TCoordinate & y() const noexcept
Returns the y value.
Definition Vector2.h:722
T sqrDistance(const VectorT2< T > &right) const
Returns the square distance between this 2D position and a second 2D position.
Definition Vector2.h:645
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