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