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
15#include "ocean/base/Worker.h"
16
17#include "ocean/math/Vector2.h"
18
19#include <functional>
20
21namespace Ocean
22{
23
24namespace CV
25{
26
27/**
28 * This class provides base functionality and type definitions for non-maximum-suppression operations.
29 * It defines common types like StrengthPosition and StrengthPositions used by the template class NonMaximumSuppressionT.
30 * @see NonMaximumSuppressionT
31 * @ingroup cv
32 */
34{
35 public:
36
37 /**
38 * Definition of individual suppression modes for extremum search.
39 */
40 enum SuppressionMode : uint32_t
41 {
42 /// Finds the maximum values, any value is allowed.
44 /// Finds the minimum values, any value is allowed.
46 /// Finds the maximum values, only positive values are allowed (> 0).
48 /// Finds the minimum values, only negative values are allowed (< 0).
50 };
51
52 /**
53 * Definition of individual refinement status values for iterative sub-pixel peak refinement.
54 */
55 enum RefinementStatus : uint32_t
56 {
57 /// Invalid status, no refinement was applied.
59 /// The fitted offset exceeded the step size, indicating divergence.
61 /// The sample position fell outside the frame bounds.
63 /// The refinement did not converge within the maximum number of iterations.
65 /// The refinement converged, the offset is below the convergence threshold.
67 };
68
69 /**
70 * This class extends a 2D position by a third parameter storing a strength value.
71 * @tparam TCoordinate The data type of a scalar coordinate
72 * @tparam TStrength The data type of the strength parameter
73 */
74 template <typename TCoordinate, typename TStrength>
75 class StrengthPosition : public VectorT2<TCoordinate>
76 {
77 public:
78
79 /**
80 * Creates a new object with default strength parameter.
81 */
82 StrengthPosition() = default;
83
84 /**
85 * Creates a new object with explicit position and strength parameter.
86 * @param x Horizontal position
87 * @param y Vertical position
88 * @param strength The strength parameter
89 */
90 inline StrengthPosition(const TCoordinate x, const TCoordinate y, const TStrength& strength);
91
92 /**
93 * Returns the strength parameter of this object.
94 * @return Strength parameter
95 */
96 inline const TStrength& strength() const;
97
98 /**
99 * Compares the strength value of two objects.
100 * @param left The left object to compare
101 * @param right The right object to compare
102 * @return True, if so
103 * @tparam tLeftLargerThanRight True, to return whether left's strength is larger than right's strength; False, to return whether right is larger than left
104 */
105 template <bool tLeftLargerThanRight = true>
107
108 private:
109
110 /// Strength parameter of this object.
111 TStrength strength_ = TStrength();
112 };
113
114 /**
115 * Definition of a vector holding strength pixel positions.
116 */
117 template <typename TCoordinate, typename TStrength>
118 using StrengthPositions = std::vector<StrengthPosition<TCoordinate, TStrength>>;
119};
120
121/**
122 * This class implements the possibility to find local maximum in a 2D array by applying a non-maximum-suppression search.
123 * The search is done within a 3x3 neighborhood (centered around the point of interest).<br>
124 * Use this class to determine e.g. reliable feature points.<br>
125 * The class supports bin accuracy (pixel accuracy) and sub-bin accuracy (sub-pixel accuracy).
126 *
127 * The non-maximum-suppression search is implemented by a vertical list holding maps of horizontal array elements.<br>
128 * The performance depends on the number of elements inserted into the individual maps.<br>
129 * Thus, do not add data elements with negligible value.
130 *
131 * 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>
132 * The performance boost comes with a simultaneous determination of filter responses and the insertion of possible candidates for maximum locations.
133 * @tparam T The data type of the individual elements that are applied for the non-maximum-suppression search.
134 * @ingroup cv
135 */
136template <typename T>
138{
139 public:
140
141 /**
142 * Definition of a callback function used to determine the precise sub-pixel position of a specific point.
143 * @param x The horizontal position of the point, with range [0, width - 1]
144 * @param y The vertical position of the point, with range [0, height - 1]
145 * @param strength The strength parameter already known for the position of the point
146 * @param preciseX The resulting precise horizontal position of the point, with range (x - 1, x + 1)
147 * @param preciseY The resulting precise vertical position of the point, with range (y - 1, y + 1)
148 * @param preciseStrength The resulting precise strength parameter of the precise point
149 * @return True, if the precise position could be determined
150 * @tparam TCoordinate The data type of a scalar coordinate
151 * @tparam TStrength The data type of the strength parameter
152 */
153 template <typename TCoordinate, typename TStrength>
154 using PositionCallback = std::function<bool(const unsigned int x, const unsigned int y, const TStrength strength, TCoordinate& preciseX, TCoordinate& preciseY, TStrength& preciseStrength)>;
155
156 protected:
157
158 /**
159 * This class holds the horizontal position and strength parameter of an interest pixel.
160 */
162 {
163 public:
164
165 /**
166 * Creates a new candidate object.
167 */
168 inline StrengthCandidate();
169
170 /**
171 * Creates a new candidate object with horizontal position and strength parameter.
172 * @param x Horizontal position in pixel
173 * @param strength The strength parameter
174 */
175 inline StrengthCandidate(const unsigned int x, const T& strength);
176
177 /**
178 * Returns the horizontal position of this candidate object.
179 * @return Horizontal position in pixel
180 */
181 inline unsigned int x() const;
182
183 /**
184 * Returns the strength parameter of this object.
185 * @return Strength parameter
186 */
187 inline const T& strength() const;
188
189 private:
190
191 /// Horizontal position of this object.
192 unsigned int positionX_ = (unsigned int)(-1);
193
194 /// Strength parameter of this object.
195 T strength_ = T();
196 };
197
198 /**
199 * Definition of a vector holding strength candidate objects.
200 */
201 using StrengthCandidateRow = std::vector<StrengthCandidate>;
202
203 /**
204 * Definition of a vector holding a vector of strength candidates.
205 */
207
208 public:
209
210 /**
211 * Move constructor.
212 * @param nonMaximumSuppression The object to be moved
213 */
214 NonMaximumSuppressionT(NonMaximumSuppressionT<T>&& nonMaximumSuppression) noexcept;
215
216 /**
217 * Copy constructor.
218 * @param nonMaximumSuppression The object to be moved
219 */
220 NonMaximumSuppressionT(const NonMaximumSuppressionT<T>& nonMaximumSuppression) noexcept;
221
222 /**
223 * Creates a new maximum suppression object with a predefined size.
224 * @param width The width of this object in pixel, with range [3, infinity)
225 * @param height The height of this object in pixel, with range [3, infinity)
226 * @param yOffset Optional offset in the vertical direction moving the suppression region by the specified number of rows, with range [0, infinity)
227 */
228 NonMaximumSuppressionT(const unsigned int width, const unsigned int height, const unsigned int yOffset = 0u) noexcept;
229
230 /**
231 * Returns the width of this object.
232 * @return Width in pixel
233 */
234 inline unsigned int width() const;
235
236 /**
237 * Returns the height of this object.
238 * @return Height in pixel
239 */
240 inline unsigned int height() const;
241
242 /**
243 * Returns the optional offset in the vertical direction.
244 * @return Optional vertical direction offset, 0 by default
245 */
246 inline unsigned int yOffset() const;
247
248 /**
249 * Adds a new candidate to this object.
250 * Beware: Due to performance issues do no add candidates with negligible strength parameter.
251 * @param x Horizontal position in pixel, with range [0, width() - 1]
252 * @param y Vertical position in pixel, with range [yOffset(), yOffset() + height() - 1]
253 * @param strength The strength parameter
254 * @see addCandidates(), removeCandidatesFromRight().
255 */
256 inline void addCandidate(const unsigned int x, const unsigned int y, const T& strength);
257
258 /**
259 * Adds new candidates to this object from a given buffer providing one value for each bin/pixel of this object.
260 * 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>
261 * @param values The from which candidates will be added, must be width() * height() elements
262 * @param valuesPaddingElements The number of padding elements at the end of each values row, in elements, with range [0, infinity)
263 * @param firstColumn First column to be handled, with range [0, width() - 1]
264 * @param numberColumns Number of columns to be handled, with range [1, width() - firstColumn]
265 * @param firstRow First row to be handled, with range [yOffset(), height() - 1]
266 * @param numberRows Number of rows to be handled, with range [1, height() - firstRow]
267 * @param minimalThreshold The minimal threshold so that a value counts as candidate
268 * @param worker Optional worker object to distribute the computation
269 * @see addCandidate(), removeCandidatesFromRight().
270 */
271 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);
272
273 /**
274 * Removes all candidates from a specified row having a horizontal location equal or larger than a specified coordinate.
275 * @param x The horizontal coordinate specifying which candidates will be removed, all candidates with horizontal location >= x will be removed, with range [0, infinity)
276 * @param y The index of the row in which the candidates will be removed, with range [yOffset(), yOffset() + height() - 1]
277 */
278 inline void removeCandidatesRightFrom(const unsigned int x, const unsigned int y);
279
280 /**
281 * Returns the strength value of a candidate at a specified position.
282 * The function uses binary search on the x-coordinate within the specified row, as candidates are sorted by horizontal position.
283 * @param x The horizontal position to look up, with range [0, width() - 1]
284 * @param y The vertical position (row) to look up, with range [yOffset(), yOffset() + height() - 1]
285 * @param strength The resulting strength value if a candidate exists at the specified position
286 * @return True, if a candidate exists at the specified position; False, if no candidate was found
287 */
288 bool candidate(const unsigned int x, const unsigned int y, T& strength) const;
289
290 /**
291 * Returns all gathered candidates of this object.
292 * The resulting candidates are raw candidates without any suppression.
293 * @param firstColumn The first column from which candidates will be returned, with range [0, width() - 1]
294 * @param numberColumns The number of columns for which candidates will be returned, with range [1, width() - firstColumn]
295 * @param firstRow The first row from which candidates will be returned, with range [yOffset(), height() - 1]
296 * @param numberRows The number of rows for which candidates will be returned, with range [1, height() - firstRow]
297 * @param strengthPositions The resulting strength positions
298 */
299 void candidates(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, StrengthPositions<unsigned int, T>& strengthPositions) const;
300
301 /**
302 * Applies a non-maximum-suppression search on a given 2D frame in a 3x3 neighborhood (eight neighbors).
303 * 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>
304 * @param firstColumn First column to be handled, with range [1, width() - 1)
305 * @param numberColumns Number of columns to be handled
306 * @param firstRow First row to be handled, with range [yOffset() + 1, height() - 1)
307 * @param numberRows Number of rows to be handled
308 * @param strengthPositions The resulting non maximum suppressed positions including the strength parameters, will be appended
309 * @param worker Optional worker object to distribute the computation
310 * @param positionCallback Optional callback function allowing to determine the precise position of the individual maximum value positions
311 * @return True, if succeeded
312 * @tparam TCoordinate The data type of a scalar coordinate
313 * @tparam TStrength The data type of the strength parameter
314 * @tparam tStrictMaximum True, to search for a strict maximum (larger than all eight neighbors); False, to allow equal values in the upper left neighborhood
315 * @tparam tSuppressionMode The suppression mode defining whether to find maximum/minimum and whether to enforce sign constraints
316 */
317 template <typename TCoordinate, typename TStrength, bool tStrictMaximum = true, NonMaximumSuppression::SuppressionMode tSuppressionMode = NonMaximumSuppression::SM_MAXIMUM>
318 bool suppressNonMaximum(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, StrengthPositions<TCoordinate, TStrength>& strengthPositions, Worker* worker = nullptr, const PositionCallback<TCoordinate, TStrength>* positionCallback = nullptr) const;
319
320 /**
321 * Removes the gathered non-maximum suppression information so that this object can be reused again (for the same task with same resolution etc.).
322 * The allocated memory will remain so that reusing this object may improve performance.
323 */
324 void reset();
325
326 /**
327 * Move operator.
328 * @param nonMaximumSuppression Object to be moved
329 * @return Reference to this object
330 */
332
333 /**
334 * Copy operator.
335 * @param nonMaximumSuppression The object to be copied
336 * @return Reference to this object
337 */
339
340 /**
341 * Applies a non-maximum-suppression based on already existing strength positions (just with a custom suppression radius) e.g., as a post-processing step.
342 * @param width The width of the image/domain in which the strength positions are located, e.g., in pixel, with range [1, infinity)
343 * @param height The height of the image/domain in which the strength positions are located, e.g., in pixel, with range [1, infinity)
344 * @param strengthPositions The strength positions for which a custom suppression-radius will be applied
345 * @param radius The suppression radius to be applied, with range [1, infinity)
346 * @param validIndices Optional resulting indices of all strength positions which remain after suppression
347 * @return The resulting strength positions
348 * @tparam TCoordinate The data type of a scalar coordinate
349 * @tparam TStrength The data type of the strength parameter
350 * @tparam tStrictMaximum True, to search for a strict maximum (larger than all eight neighbors); False, to allow equal values in the upper left neighborhood
351 */
352 template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
353 static StrengthPositions<TCoordinate, TStrength> suppressNonMaximum(const unsigned int width, const unsigned int height, const StrengthPositions<TCoordinate, TStrength>& strengthPositions, const TCoordinate radius, Indices32* validIndices = nullptr);
354
355 /**
356 * Determines the precise peak location in 1D space for three discrete neighboring measurements at location x == 0.
357 * The precise peak is determined based on the first and second derivatives of the measurement values.
358 * @param leftValue The left discrete value, e.g., at location x - 1, with range (-infinity, middleValue]
359 * @param middleValue The middle discrete value, e.g., at location x, with range (-infinity, infinity)
360 * @param rightValue The discrete value, e.g., at location x + 1, with range (-infinity, middleValue]
361 * @param location The location of the precise peak of all values, with range (-1, 1)
362 * @return True, if succeeded
363 * @tparam TFloat The floating point data type to be used for calculation, either 'float' or 'double'
364 */
365 template <typename TFloat>
366 static bool determinePrecisePeakLocation1(const T& leftValue, const T& middleValue, const T& rightValue, TFloat& location);
367
368 /**
369 * Determines the precise peak location in 2D space for nine discrete neighboring measurements at location x == 0, y == 0.
370 * The precise peak is determined based on the first and second derivatives of the measurement values.
371 * @param topValues The three discrete values in the top row, must be valid
372 * @param centerValues The three discrete values in the center row, must be valid
373 * @param bottomValues The three discrete values in the bottom row, must be valid
374 * @param location The location of the precise peak of all values, with range (-1, 1)
375 * @return True, if succeeded
376 * @tparam TFloat The floating point data type to be used for calculation, either 'float' or 'double'
377 */
378 template <typename TFloat>
379 static bool determinePrecisePeakLocation2(const T* const topValues, const T* const centerValues, const T* const bottomValues, VectorT2<TFloat>& location);
380
381 /**
382 * Determines the precise peak location in 2D space by fitting a quadratic surface via least-squares to a tSize x tSize grid of values.
383 * Uses all tSize * tSize samples to fit F(x,y) = a + bx + cy + dx^2 + exy + fy^2 and solves for the peak of the fitted surface.<br>
384 * For a 3x3 grid this reduces to central differences; for larger grids the least-squares fit uses all samples for a more robust estimate.<br>
385 * The function validates that the fitted surface has the correct curvature (negative-definite Hessian for maximum, positive-definite for minimum).
386 * @param values The grid values in row-major order, must have at least (tSize - 1) * valuesStrideElements + tSize elements
387 * @param valuesStrideElements The number of elements between the start of two consecutive rows, with range [tSize, infinity)
388 * @param offsetX The resulting horizontal offset of the peak from the grid center, in range (-1, 1)
389 * @param offsetY The resulting vertical offset of the peak from the grid center, in range (-1, 1)
390 * @return True, if the fit succeeded, the curvature is correct, and the offset is within range; False, if the surface is degenerate, has wrong curvature, or the offset is out of range
391 * @tparam TFloat The floating point data type to be used for calculation, either 'float' or 'double'
392 * @tparam tSize The size of the sampling grid, must be odd and >= 3
393 * @tparam tFindMaximum True, to find the maximum of the fitted surface; False, to find the minimum
394 */
395 template <typename TFloat, unsigned int tSize, bool tFindMaximum = true>
396 static bool determinePrecisePeakLocationNxN(const TFloat* values, const size_t valuesStrideElements, TFloat& offsetX, TFloat& offsetY);
397
398 /**
399 * Determines the precise sub-pixel peak location by iteratively sampling image intensity values at sub-pixel positions.
400 * The function uses bilinear interpolation to sample a tSize x tSize grid of intensity values around the current estimate, calls determinePrecisePeakLocationNxN() to find the sub-pixel offset, and iterates with decreasing step size until convergence.<br>
401 * The valid sampling range is [0, width - 1]x[0, height - 1] for PC_TOP_LEFT or [0, width]x[0, height] for PC_CENTER.
402 * @param frame Pointer to the first pixel of the grayscale frame (single channel, uint8_t), must be valid
403 * @param width The width of the frame in pixels, with range [tSize, infinity)
404 * @param height The height of the frame in pixels, with range [tSize, infinity)
405 * @param framePaddingElements The number of padding elements at the end of each frame row, in elements, with range [0, infinity)
406 * @param x The horizontal position of the initial peak estimate, in the coordinate system defined by tPixelCenter
407 * @param y The vertical position of the initial peak estimate, in the coordinate system defined by tPixelCenter
408 * @param preciseX The resulting precise horizontal position, in the coordinate system defined by tPixelCenter
409 * @param preciseY The resulting precise vertical position, in the coordinate system defined by tPixelCenter
410 * @param maxIterations The maximum number of refinement iterations, with range [1, infinity)
411 * @param stepSize The initial step size for the sampling grid, with range (0, 1]
412 * @param convergenceThreshold The threshold for the pixel offset magnitude below which convergence is declared, with range (0, infinity)
413 * @return The refinement status
414 * @tparam TFloat The floating point data type to be used for calculation, either 'float' or 'double'
415 * @tparam tSize The size of the sampling grid, must be odd and >= 3
416 * @tparam tFindMaximum True, to find the intensity maximum; False, to find the intensity minimum
417 * @tparam tPixelCenter The pixel center to be used during interpolation, either 'PC_TOP_LEFT' or 'PC_CENTER'
418 */
419 template <typename TFloat, unsigned int tSize, bool tFindMaximum = true, PixelCenter tPixelCenter = PC_TOP_LEFT>
420 static NonMaximumSuppression::RefinementStatus determinePrecisePeakLocationIterativeNxN(const uint8_t* frame, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const TFloat x, const TFloat y, TFloat& preciseX, TFloat& preciseY, const unsigned int maxIterations = 5u, const TFloat stepSize = TFloat(0.75), const TFloat convergenceThreshold = TFloat(0.01));
421
422 private:
423
424 /**
425 * Adds new candidates to this object from a subset of a given buffer providing one value for each bin/pixel of this object.
426 * @param values The from which candidates will be added, must be width() * height() elements
427 * @param valuesStrideElements The number of elements between two values rows, in elements, with range [0, infinity)
428 * @param minimalThreshold The minimal threshold so that a value counts as candidate
429 * @param firstColumn First column to be handled, with range [0, width() - 1]
430 * @param numberColumns Number of columns to be handled, with range [1, width() - firstColumn]
431 * @param firstRow The first row in the buffer from which the candidates will be added, with range [yOffset(), height())
432 * @param numberRows The number of rows to be handled, with range [1, height() - firstRow]
433 * @see addCandidates().
434 */
435 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);
436
437 /**
438 * Applies a non-maximum-suppression search on a subset of a given 2D frame in a 3x3 neighborhood (eight neighbors).
439 * 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>
440 * @param strengthPositions Resulting non maximum suppressed positions including the strength parameters
441 * @param firstColumn First column to be handled
442 * @param numberColumns Number of columns to be handled
443 * @param lock Optional lock when executed in parallel
444 * @param positionCallback Optional callback function allowing to determine the precise position of the individual maximum value positions
445 * @param firstRow First row to be handled
446 * @param numberRows Number of rows to be handled
447 * @tparam TCoordinate The data type of a scalar coordinate
448 * @tparam TStrength The data type of the strength parameter
449 * @tparam tStrictMaximum True, to search for a strict maximum (larger than all eight neighbors); False, to allow equal values in the upper left neighborhood
450 * @tparam tOnlyPositive True, to only consider positive values (> 0); False, to consider all values
451 */
452 template <typename TCoordinate, typename TStrength, bool tStrictMaximum, bool tOnlyPositive = false>
453 void 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;
454
455 /**
456 * Applies a non-minimum-suppression search on a subset of a given 2D frame in a 3x3 neighborhood (eight neighbors).
457 * This function allows to determine the precise position of the individual minimum value positions by application of a callback function determining the individual positions.<br>
458 * @param strengthPositions Resulting non minimum suppressed positions including the strength parameters
459 * @param firstColumn First column to be handled
460 * @param numberColumns Number of columns to be handled
461 * @param lock Optional lock when executed in parallel
462 * @param positionCallback Optional callback function allowing to determine the precise position of the individual minimum value positions
463 * @param firstRow First row to be handled
464 * @param numberRows Number of rows to be handled
465 * @tparam TCoordinate The data type of a scalar coordinate
466 * @tparam TStrength The data type of the strength parameter
467 * @tparam tStrictMaximum True, to search for a strict minimum (smaller than all eight neighbors); False, to allow equal values in the upper left neighborhood
468 * @tparam tOnlyNegative True, to only consider negative values (< 0); False, to consider all values
469 */
470 template <typename TCoordinate, typename TStrength, bool tStrictMaximum, bool tOnlyNegative = false>
471 void suppressNonMinimumSubset(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;
472
473 private:
474
475 /// Width of this object.
476 unsigned int width_ = 0u;
477
478 /// All candidate rows.
480};
481
482template <typename TCoordinate, typename TStrength>
484 VectorT2<TCoordinate>(x, y),
485 strength_(strength)
486{
487 // nothing to do here
488}
489
490template <typename TCoordinate, typename TStrength>
492{
493 return strength_;
494}
495
496template <typename TCoordinate, typename TStrength>
497template <bool tLeftLargerThanRight>
499{
500 if constexpr (tLeftLargerThanRight)
501 {
502 return left.strength() > right.strength();
503 }
504 else
505 {
506 return left.strength() < right.strength();
507 }
508}
509
510template <typename T>
512 positionX_(-1),
513 strength_(T())
514{
515 // nothing to do here
516}
517
518template <typename T>
519inline NonMaximumSuppressionT<T>::StrengthCandidate::StrengthCandidate(const unsigned int x, const T& strength) :
520 positionX_(x),
521 strength_(strength)
522{
523 // nothing to do here
524}
525
526template <typename T>
528{
529 return positionX_;
530}
531
532template <typename T>
534{
535 return strength_;
536}
537
538template <typename T>
540{
541 *this = std::move(nonMaximumSuppression);
542}
543
544template <typename T>
546 width_(nonMaximumSuppression.width_),
547 rows_(nonMaximumSuppression.rows_)
548{
549 // nothing to do here
550}
551
552template <typename T>
553NonMaximumSuppressionT<T>::NonMaximumSuppressionT(const unsigned int width, const unsigned int height, const unsigned int yOffset) noexcept :
554 width_(width),
556{
557 // nothing to do here
558}
559
560template <typename T>
561inline unsigned int NonMaximumSuppressionT<T>::width() const
562{
563 return width_;
564}
565
566template <typename T>
567inline unsigned int NonMaximumSuppressionT<T>::height() const
568{
569 return (unsigned int)rows_.size();
570}
571
572template <typename T>
573inline unsigned int NonMaximumSuppressionT<T>::yOffset() const
574{
575 ocean_assert(rows_.firstIndex() >= 0);
576
577 return (unsigned int)(rows_.firstIndex());
578}
579
580template <typename T>
581inline void NonMaximumSuppressionT<T>::addCandidate(const unsigned int x, const unsigned int y, const T& strength)
582{
583 ocean_assert(x < width_);
584
585 ocean_assert(rows_.isValidIndex(y) && y >= (unsigned int)(rows_.firstIndex()) && y <= (unsigned int)(rows_.endIndex()));
586
587 if (rows_[y].empty())
588 {
589 rows_[y].reserve(128);
590 }
591
592 rows_[y].emplace_back(x, strength);
593}
594
595template <typename T>
596void NonMaximumSuppressionT<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)
597{
598 ocean_assert(values != nullptr);
599
600 ocean_assert(firstColumn + numberColumns <= width_);
601 ocean_assert(typename StrengthCandidateRows::Index(firstRow) >= rows_.firstIndex());
602 ocean_assert(typename StrengthCandidateRows::Index(firstRow + numberRows) <= rows_.endIndex());
603
604 const unsigned int valuesStrideElements = width_ + valuesPaddingElements;
605
606 if (worker != nullptr)
607 {
608 worker->executeFunction(Worker::Function::create(*this, &NonMaximumSuppressionT<T>::addCandidatesSubset, values, valuesStrideElements, firstColumn, numberColumns, &minimalThreshold, 0u, 0u), firstRow, numberRows, 5u, 6u, 20u);
609 }
610 else
611 {
612 addCandidatesSubset(values, valuesStrideElements, firstColumn, numberColumns, &minimalThreshold, firstRow, numberRows);
613 }
614}
615
616template <typename T>
617inline void NonMaximumSuppressionT<T>::removeCandidatesRightFrom(const unsigned int x, const unsigned int y)
618{
619 ocean_assert(rows_.isValidIndex(y) && y >= (unsigned int)(rows_.firstIndex()) && y <= (unsigned int)(rows_.endIndex()));
620
621 StrengthCandidateRow& suppressionRow = rows_[y];
622
623 while (!suppressionRow.empty())
624 {
625 if (suppressionRow.back().x() >= x)
626 {
627 suppressionRow.pop_back();
628 }
629 else
630 {
631 break;
632 }
633 }
634}
635
636template <typename T>
637bool NonMaximumSuppressionT<T>::candidate(const unsigned int x, const unsigned int y, T& strength) const
638{
639 ocean_assert(x < width_);
640 ocean_assert(rows_.isValidIndex(y) && y >= (unsigned int)(rows_.firstIndex()) && y <= (unsigned int)(rows_.endIndex()));
641
642 const StrengthCandidateRow& suppressionRow = rows_[y];
643
644 if (suppressionRow.empty())
645 {
646 return false;
647 }
648
649 size_t left = 0;
650 size_t right = suppressionRow.size() - 1;
651
652 while (left <= right)
653 {
654 const size_t mid = left + (right - left) / 2;
655
656 const unsigned int midX = suppressionRow[mid].x();
657
658 if (midX == x)
659 {
660 strength = suppressionRow[mid].strength();
661 return true;
662 }
663 else if (midX < x)
664 {
665 left = mid + 1;
666 }
667 else
668 {
669 if (mid == 0)
670 {
671 break;
672 }
673
674 right = mid - 1;
675 }
676 }
677
678 return false;
679}
680
681template <typename T>
682void NonMaximumSuppressionT<T>::candidates(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, NonMaximumSuppressionT<T>::StrengthPositions<unsigned int, T>& strengthPositions) const
683{
684 ocean_assert(firstColumn + numberColumns <= width_);
685 ocean_assert(firstRow + numberRows <= (unsigned int)(rows_.endIndex()));
686
687 const unsigned int endColumn = firstColumn + numberColumns;
688
689 for (unsigned int y = firstRow; y < firstRow + numberRows; ++y)
690 {
691 const StrengthCandidateRow& row = rows_[y];
692
693 for (const StrengthCandidate& candidate : row)
694 {
695 if (candidate.x() < firstColumn)
696 {
697 continue;
698 }
699
700 if (candidate.x() >= endColumn)
701 {
702 break;
703 }
704
705 strengthPositions.emplace_back(candidate.x(), y, candidate.strength());
706 }
707 }
708}
709
710template <typename T>
711template <typename TCoordinate, typename TStrength, bool tStrictMaximum, NonMaximumSuppression::SuppressionMode tSuppressionMode>
712bool NonMaximumSuppressionT<T>::suppressNonMaximum(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, StrengthPositions<TCoordinate, TStrength>& strengthPositions, Worker* worker, const PositionCallback<TCoordinate, TStrength>* positionCallback) const
713{
714 ocean_assert(firstColumn + numberColumns <= width_);
715 ocean_assert(firstRow >= (unsigned int)(rows_.firstIndex()) && firstRow + numberRows <= (unsigned int)(rows_.endIndex()));
716
717 if (firstColumn + numberColumns > width_)
718 {
719 return false;
720 }
721
722 if (firstRow >= (unsigned int)(rows_.endIndex()) || firstRow + numberRows <= (unsigned int)(rows_.firstIndex()))
723 {
724 return false;
725 }
726
727 strengthPositions.reserve(strengthPositions.size() + 128);
728
729 if constexpr (tSuppressionMode == NonMaximumSuppression::SM_MAXIMUM || tSuppressionMode == NonMaximumSuppression::SM_MAXIMUM_POSITIVE_ONLY)
730 {
731 constexpr bool tOnlyPositive = tSuppressionMode == NonMaximumSuppression::SM_MAXIMUM_POSITIVE_ONLY;
732
733 if (worker != nullptr)
734 {
735 Lock lock;
736 worker->executeFunction(Worker::Function::create(*this, &NonMaximumSuppressionT<T>::suppressNonMaximumSubset<TCoordinate, TStrength, tStrictMaximum, tOnlyPositive>, &strengthPositions, firstColumn, numberColumns, &lock, positionCallback, 0u, 0u), firstRow, numberRows, 5u, 6u, 3u);
737 }
738 else
739 {
740 suppressNonMaximumSubset<TCoordinate, TStrength, tStrictMaximum, tOnlyPositive>(&strengthPositions, firstColumn, numberColumns, nullptr, positionCallback, firstRow, numberRows);
741 }
742 }
743 else
744 {
745 static_assert(tSuppressionMode == NonMaximumSuppression::SM_MINIMUM || tSuppressionMode == NonMaximumSuppression::SM_MINIMUM_NEGATIVE_ONLY);
746
747 constexpr bool tOnlyNegative = tSuppressionMode == NonMaximumSuppression::SM_MINIMUM_NEGATIVE_ONLY;
748
749 if (worker != nullptr)
750 {
751 Lock lock;
752 worker->executeFunction(Worker::Function::create(*this, &NonMaximumSuppressionT<T>::suppressNonMinimumSubset<TCoordinate, TStrength, tStrictMaximum, tOnlyNegative>, &strengthPositions, firstColumn, numberColumns, &lock, positionCallback, 0u, 0u), firstRow, numberRows, 5u, 6u, 3u);
753 }
754 else
755 {
756 suppressNonMinimumSubset<TCoordinate, TStrength, tStrictMaximum, tOnlyNegative>(&strengthPositions, firstColumn, numberColumns, nullptr, positionCallback, firstRow, numberRows);
757 }
758 }
759
760 return true;
761}
762
763template <typename T>
765{
766 for (ptrdiff_t n = rows_.firstIndex(); n < rows_.endIndex(); ++n)
767 {
768 rows_[n].clear();
769 }
770}
771
772template <typename T>
774{
775 if (this != &nonMaximumSuppression)
776 {
777 width_ = nonMaximumSuppression.width_;
778 nonMaximumSuppression.width_ = 0u;
779
780 rows_ = std::move(nonMaximumSuppression.rows_);
781 }
782
783 return *this;
784}
785
786template <typename T>
788{
789 if (this != &nonMaximumSuppression)
790 {
791 width_ = nonMaximumSuppression.width_;
792 rows_ = nonMaximumSuppression.rows_;
793 }
794
795 return *this;
796}
797
798template <typename T>
799template <typename TCoordinate, typename TStrength, bool tStrictMaximum>
801{
802 ocean_assert(width >= 1u && height >= 1u);
803 ocean_assert(radius >= TCoordinate(1));
804
805 const unsigned int binSize = std::max(10u, (unsigned int)(NumericT<TCoordinate>::ceil(radius)));
806
807 const unsigned int horizontalBins = std::max(1u, (width + binSize - 1u) / binSize);
808 const unsigned int verticalBins = std::max(1u, (height + binSize - 1u) / binSize);
809
810 ocean_assert(binSize * horizontalBins >= width);
811 ocean_assert(binSize * verticalBins >= height);
812
813 IndexGroups32 indexGroups(horizontalBins * verticalBins);
814
815 // distributing all strength positions into a regular grid to reduce search space later
816
817 for (size_t n = 0; n < strengthPositions.size(); ++n)
818 {
819 const VectorT2<TCoordinate>& position = strengthPositions[n];
820
821 ocean_assert((unsigned int)(position.x()) < width);
822 ocean_assert((unsigned int)(position.y()) < height);
823
824 const unsigned int xBin = (unsigned int)(position.x()) / binSize;
825 const unsigned int yBin = (unsigned int)(position.y()) / binSize;
826
827 ocean_assert(xBin <= horizontalBins);
828 ocean_assert(yBin <= verticalBins);
829
830 indexGroups[yBin * horizontalBins + xBin].emplace_back(Index32(n));
831 }
832
833 std::vector<uint8_t> validPositions(strengthPositions.size(), 1u);
834
835 const TCoordinate sqrRadius = radius * radius;
836
837 for (size_t nCandidate = 0; nCandidate < strengthPositions.size(); ++nCandidate)
838 {
839 if (validPositions[nCandidate] == 0u)
840 {
841 // the positions is already suppressed
842 continue;
843 }
844
845 const StrengthPosition<TCoordinate, TStrength>& candidatePosition = strengthPositions[nCandidate];
846
847 const unsigned int xCandidateBin = (unsigned int)(candidatePosition.x()) / binSize;
848 const unsigned int yCandidateBin = (unsigned int)(candidatePosition.y()) / binSize;
849
850 ocean_assert(xCandidateBin <= horizontalBins);
851 ocean_assert(yCandidateBin <= verticalBins);
852
853 bool checkNextCandidate = false;
854
855 for (unsigned int yBin = (unsigned int)(max(0, int(yCandidateBin) - 1)); !checkNextCandidate && yBin < min(yCandidateBin + 2u, verticalBins); ++yBin)
856 {
857 for (unsigned int xBin = (unsigned int)(max(0, int(xCandidateBin) - 1)); !checkNextCandidate && xBin < min(xCandidateBin + 2u, horizontalBins); ++xBin)
858 {
859 const Indices32& indices = indexGroups[yBin * horizontalBins + xBin];
860
861 for (const Index32& nTest : indices)
862 {
863 if (nTest == Index32(nCandidate))
864 {
865 continue;
866 }
867
868 const StrengthPosition<TCoordinate, TStrength>& testPosition = strengthPositions[nTest];
869
870 // we do not check whether test position is suppressed already (as the test position may still be the reason to suppress the candidate position)
871
872 if (candidatePosition.sqrDistance(testPosition) <= sqrRadius)
873 {
874 if (candidatePosition.strength() > testPosition.strength())
875 {
876 validPositions[nTest] = 0u;
877 }
878 else if (candidatePosition.strength() < testPosition.strength())
879 {
880 validPositions[nCandidate] = 0u;
881
882 checkNextCandidate = true;
883 break;
884 }
885 else
886 {
887 ocean_assert(candidatePosition.strength() == testPosition.strength());
888
889 if constexpr (tStrictMaximum)
890 {
891 // we suppress both elements, as we seek a strict maximum element
892
893 validPositions[nCandidate] = 0u;
894 validPositions[nTest] = 0u;
895
896 checkNextCandidate = true;
897 break;
898 }
899 else
900 {
901 // we will suppress one of both elements, as we accept a non-strict maximum element
902
903 if (candidatePosition.y() < testPosition.y() || (candidatePosition.y() == testPosition.y() && candidatePosition.x() < testPosition.x()))
904 {
905 // the candidate position will be suppressed as the test position is located to the bottom/right of the candidate position
906
907 validPositions[nCandidate] = 0u;
908
909 checkNextCandidate = true;
910 break;
911 }
912 else
913 {
914 ocean_assert(testPosition.y() < candidatePosition.y() || (testPosition.y() == candidatePosition.y() && testPosition.x() < candidatePosition.x()));
915
916 // the test position will be suppressed as the candidate position is located to the bottom/right of the test position
917
918 validPositions[nTest] = 0u;
919 }
920 }
921 }
922 }
923 }
924 }
925 }
926 }
927
929 remainingPositions.reserve(strengthPositions.size());
930
931 if (validIndices)
932 {
933 ocean_assert(validIndices->empty());
934
935 validIndices->clear();
936 validIndices->reserve(strengthPositions.size());
937
938 for (size_t n = 0; n < validPositions.size(); ++n)
939 {
940 if (validPositions[n])
941 {
942 remainingPositions.emplace_back(strengthPositions[n]);
943 validIndices->emplace_back(Index32(n));
944 }
945 }
946 }
947 else
948 {
949 for (size_t n = 0; n < validPositions.size(); ++n)
950 {
951 if (validPositions[n])
952 {
953 remainingPositions.emplace_back(strengthPositions[n]);
954 }
955 }
956 }
957
958 return remainingPositions;
959}
960
961template <typename T>
962template <typename TFloat>
963bool NonMaximumSuppressionT<T>::determinePrecisePeakLocation1(const T& leftValue, const T& middleValue, const T& rightValue, TFloat& location)
964{
965 static_assert(std::is_floating_point<TFloat>::value, "Invalid floating point data type!");
966
967 // f(x) = f(a) + f`(a) * (x - a)
968
969 // we expect our middle value to be located at a = 0:
970 // f(x) = f(0) + f`(0) * x
971
972 // 0 = f'(x)
973 // = f'(0) + f''(0) * x
974
975 // x = - f'(0) / f''(0)
976
977 // f`(x) = [-1 0 1] * 1/2
978 const TFloat df = (TFloat(rightValue) - TFloat(leftValue)) * TFloat(0.5);
979
980 // f``(x) = [1 -2 1] * 1/1
981 const TFloat dff = TFloat(leftValue) + TFloat(rightValue) - TFloat(middleValue) * TFloat(2);
982
984 {
985 location = TFloat(0);
986 return true;
987 }
988
989 const TFloat x = -df / dff;
990
991 if (x < TFloat(-1) || x > TFloat(1))
992 {
993 return false;
994 }
995
996 location = x;
997 return true;
998}
999
1000template <typename T>
1001template <typename TFloat>
1002bool NonMaximumSuppressionT<T>::determinePrecisePeakLocation2(const T* const topValues, const T* const centerValues, const T* const bottomValues, VectorT2<TFloat>& location)
1003{
1004 static_assert(std::is_floating_point<TFloat>::value, "Invalid floating point data type!");
1005
1006 const T& value00 = topValues[0];
1007 const T& value01 = topValues[1];
1008 const T& value02 = topValues[2];
1009
1010 const T& value10 = centerValues[0];
1011 const T& value11 = centerValues[1];
1012 const T& value12 = centerValues[2];
1013
1014 const T& value20 = bottomValues[0];
1015 const T& value21 = bottomValues[1];
1016 const T& value22 = bottomValues[2];
1017
1018#if 0
1019 // some response values may not perfectly follow the peak criteria so that we do not use the asserts by default
1020 ocean_assert(value11 >= value00 && value11 >= value01 && value11 >= value02);
1021 ocean_assert(value11 >= value10 && value11 >= value12);
1022 ocean_assert(value11 >= value20 && value11 >= value21 && value11 >= value22);
1023#endif
1024
1025 // [-1 0 1] * 1/2
1026 const TFloat dx = TFloat(value12 - value10) * TFloat(0.5);
1027 const TFloat dy = TFloat(value21 - value01) * TFloat(0.5);
1028
1029 // [1 -2 1] * 1/1
1030 const TFloat dxx = TFloat(value12 + value10) - TFloat(value11) * TFloat(2);
1031 const TFloat dyy = TFloat(value21 + value01) - TFloat(value11) * TFloat(2);
1032
1033 // [ 1 0 -1 ]
1034 // [ 0 0 0 ] * 1/4
1035 // [-1 0 1 ]
1036
1037 const TFloat dxy = TFloat(value22 + value00 - value20 - value02) * TFloat(0.25);
1038
1039 const TFloat denominator = dxx * dyy - dxy * dxy;
1040
1041 if (NumericT<TFloat>::isEqualEps(denominator))
1042 {
1043 location = VectorT2<TFloat>(0, 0);
1044 return true;
1045 }
1046
1047 const TFloat factor = TFloat(1) / denominator;
1048
1049 const TFloat offsetX = -(dyy * dx - dxy * dy) * factor;
1050 const TFloat offsetY = -(dxx * dy - dxy * dx) * factor;
1051
1052 if (offsetX < TFloat(-1) || offsetX > TFloat(1) || offsetY < TFloat(-1) || offsetY > TFloat(1))
1053 {
1054 return false;
1055 }
1056
1057 location = VectorT2<TFloat>(offsetX, offsetY);
1058
1059 return true;
1060}
1061
1062template <typename T>
1063template <typename TFloat, unsigned int tSize, bool tFindMaximum>
1064bool NonMaximumSuppressionT<T>::determinePrecisePeakLocationNxN(const TFloat* values, const size_t valuesStrideElements, TFloat& offsetX, TFloat& offsetY)
1065{
1066 static_assert(std::is_floating_point<TFloat>::value, "Invalid floating point data type!");
1067 static_assert(tSize >= 3u && tSize % 2u == 1u, "Grid size must be odd and >= 3");
1068
1069 ocean_assert(valuesStrideElements >= tSize);
1070
1071 // Least-squares fit of F(x,y) = a + bx + cy + dx^2 + exy + fy^2 to all NxN grid samples.
1072 //
1073 // Grid coordinates: x_i = gx - tSize_2, y_j = gy - tSize_2, for gx,gy in [0, tSize).
1074 // On a symmetric grid, cross-terms in the normal equations vanish, yielding closed-form coefficients:
1075 //
1076 // gradient: dx = Sxz / (n * S2), dy = Syz / (n * S2)
1077 // Hessian: dxx = 2 * (Sxxz - S2 * Sz / n) / (n * S4 - S2^2)
1078 // dyy = 2 * (Syyz - S2 * Sz / n) / (n * S4 - S2^2)
1079 // dxy = Sxyz / S2^2
1080 //
1081 // where S2 = sum(i^2) and S4 = sum(i^4) over i in [-tSize_2, tSize_2].
1082
1083 constexpr int tSize_2 = int(tSize / 2u);
1084 constexpr unsigned int n = tSize;
1085
1086 // Precomputed grid sums for coordinates in [-tSize_2, tSize_2]:
1087 // sumOfSquaredCoordinates = sum(i^2) = tSize_2 * (tSize_2 + 1) * (2 * tSize_2 + 1) / 3
1088 // sumOfFourthPowerCoordinates = sum(i^4) = tSize_2 * (tSize_2 + 1) * (2 * tSize_2 + 1) * (3 * tSize_2^2 + 3 * tSize_2 - 1) / 15
1089
1090 const TFloat sumOfSquaredCoordinates = TFloat(tSize_2 * (tSize_2 + 1) * (2 * tSize_2 + 1)) / TFloat(3);
1091 const TFloat sumOfFourthPowerCoordinates = TFloat(tSize_2 * (tSize_2 + 1) * (2 * tSize_2 + 1) * (3 * tSize_2 * tSize_2 + 3 * tSize_2 - 1)) / TFloat(15);
1092
1093 const TFloat normalEquationGradientDenominator = TFloat(n) * sumOfSquaredCoordinates;
1094 const TFloat squaredSumOfSquaredCoordinates = sumOfSquaredCoordinates * sumOfSquaredCoordinates;
1095 const TFloat normalEquationHessianDenominator = TFloat(n) * sumOfFourthPowerCoordinates - squaredSumOfSquaredCoordinates;
1096
1097 if (NumericT<TFloat>::isEqualEps(normalEquationGradientDenominator) || NumericT<TFloat>::isEqualEps(normalEquationHessianDenominator))
1098 {
1099 offsetX = TFloat(0);
1100 offsetY = TFloat(0);
1101
1102 return true;
1103 }
1104
1105 // Accumulate weighted sums in a single pass
1106
1107 TFloat sumValues = TFloat(0);
1108 TFloat sumXValues = TFloat(0);
1109 TFloat sumYValues = TFloat(0);
1110 TFloat sumXXValues = TFloat(0);
1111 TFloat sumYYValues = TFloat(0);
1112 TFloat sumXYValues = TFloat(0);
1113
1114 for (unsigned int yy = 0u; yy < tSize; ++yy)
1115 {
1116 const TFloat yj = TFloat(int(yy) - tSize_2);
1117
1118 for (unsigned int xx = 0u; xx < tSize; ++xx)
1119 {
1120 const TFloat xi = TFloat(int(xx) - tSize_2);
1121 const TFloat z = values[yy * valuesStrideElements + xx];
1122
1123 sumValues += z;
1124 sumXValues += xi * z;
1125 sumYValues += yj * z;
1126 sumXXValues += xi * xi * z;
1127 sumYYValues += yj * yj * z;
1128 sumXYValues += xi * yj * z;
1129 }
1130 }
1131
1132 // Derivatives at the grid center from the least-squares quadratic fit
1133
1134 const TFloat dx = sumXValues / normalEquationGradientDenominator;
1135 const TFloat dy = sumYValues / normalEquationGradientDenominator;
1136 const TFloat dxy = sumXYValues / squaredSumOfSquaredCoordinates;
1137
1138 const TFloat meanValue = sumValues / TFloat(n);
1139 const TFloat dxx = TFloat(2) * (sumXXValues - sumOfSquaredCoordinates * meanValue) / normalEquationHessianDenominator;
1140 const TFloat dyy = TFloat(2) * (sumYYValues - sumOfSquaredCoordinates * meanValue) / normalEquationHessianDenominator;
1141
1142 const TFloat hessianDeterminant = dxx * dyy - dxy * dxy;
1143
1144 if (NumericT<TFloat>::isEqualEps(hessianDeterminant))
1145 {
1146 offsetX = TFloat(0);
1147 offsetY = TFloat(0);
1148
1149 return true;
1150 }
1151
1152 // For a maximum, the Hessian must be negative-definite (dxx < 0 and det > 0)
1153 // For a minimum, the Hessian must be positive-definite (dxx > 0 and det > 0)
1154 if constexpr (tFindMaximum)
1155 {
1156 if (dxx >= TFloat(0) || hessianDeterminant <= TFloat(0))
1157 {
1158 offsetX = TFloat(0);
1159 offsetY = TFloat(0);
1160
1161 return false;
1162 }
1163 }
1164 else
1165 {
1166 if (dxx <= TFloat(0) || hessianDeterminant <= TFloat(0))
1167 {
1168 offsetX = TFloat(0);
1169 offsetY = TFloat(0);
1170
1171 return false;
1172 }
1173 }
1174
1175 const TFloat inverseHessianDeterminant = TFloat(1) / hessianDeterminant;
1176
1177 offsetX = -(dyy * dx - dxy * dy) * inverseHessianDeterminant;
1178 offsetY = -(dxx * dy - dxy * dx) * inverseHessianDeterminant;
1179
1180 if (offsetX < TFloat(-1) || offsetX > TFloat(1) || offsetY < TFloat(-1) || offsetY > TFloat(1))
1181 {
1182 return false;
1183 }
1184
1185 return true;
1186}
1187
1188template <typename T>
1189template <typename TFloat, unsigned int tSize, bool tFindMaximum, PixelCenter tPixelCenter>
1190NonMaximumSuppression::RefinementStatus NonMaximumSuppressionT<T>::determinePrecisePeakLocationIterativeNxN(const uint8_t* frame, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const TFloat x, const TFloat y, TFloat& preciseX, TFloat& preciseY, const unsigned int maxIterations, const TFloat stepSize, const TFloat convergenceThreshold)
1191{
1192 static_assert(std::is_floating_point<TFloat>::value, "Invalid floating point data type!");
1193 static_assert(tSize >= 3u && tSize % 2u == 1u, "Grid size must be odd and >= 3");
1194
1195 ocean_assert(frame != nullptr);
1196 ocean_assert(width >= tSize && height >= tSize);
1197 ocean_assert(maxIterations >= 1u);
1198 ocean_assert(stepSize > TFloat(0) && stepSize <= TFloat(1));
1199 ocean_assert(convergenceThreshold > TFloat(0));
1200
1201 constexpr unsigned int tSize_2 = tSize / 2u;
1202
1203 const TFloat maxSampleX = (tPixelCenter == PC_TOP_LEFT) ? TFloat(width - 1u) : TFloat(width);
1204 const TFloat maxSampleY = (tPixelCenter == PC_TOP_LEFT) ? TFloat(height - 1u) : TFloat(height);
1205
1206 ocean_assert(x >= TFloat(0) && x <= maxSampleX);
1207 ocean_assert(y >= TFloat(0) && y <= maxSampleY);
1208
1209 if (x < TFloat(0) || x > maxSampleX || y < TFloat(0) || y > maxSampleY)
1210 {
1212 }
1213
1214 TFloat iterationX = x;
1215 TFloat iterationY = y;
1216 TFloat stepFactor = stepSize;
1217
1218 const TFloat sqrConvergenceThreshold = convergenceThreshold * convergenceThreshold;
1219
1220 for (unsigned int iteration = 0u; iteration < maxIterations; ++iteration)
1221 {
1222 if (iterationX - TFloat(tSize_2) < TFloat(0) || iterationX + TFloat(tSize_2) > maxSampleX || iterationY - TFloat(tSize_2) < TFloat(0) || iterationY + TFloat(tSize_2) > maxSampleY)
1223 {
1224 preciseX = iterationX;
1225 preciseY = iterationY;
1226
1228 }
1229
1230 TFloat interpolatedValues[tSize * tSize];
1231
1232 for (unsigned int yy = 0u; yy < tSize; ++yy)
1233 {
1234 for (unsigned int xx = 0u; xx < tSize; ++xx)
1235 {
1236 const TFloat sampleX = iterationX + TFloat(int(xx) - int(tSize_2));
1237 const TFloat sampleY = iterationY + TFloat(int(yy) - int(tSize_2));
1238
1239 ocean_assert(sampleX >= TFloat(0) && sampleX <= maxSampleX);
1240 ocean_assert(sampleY >= TFloat(0) && sampleY <= maxSampleY);
1241
1242 const VectorT2<TFloat> samplePos(sampleX, sampleY);
1243
1244 FrameInterpolatorBilinear::interpolatePixel<uint8_t, TFloat, 1u, tPixelCenter, TFloat>(frame, width, height, framePaddingElements, samplePos, &interpolatedValues[yy * tSize + xx]);
1245 }
1246 }
1247
1248 TFloat fitOffsetX;
1249 TFloat fitOffsetY;
1250
1251 if (!determinePrecisePeakLocationNxN<TFloat, tSize, tFindMaximum>(interpolatedValues, tSize, fitOffsetX, fitOffsetY))
1252 {
1253 preciseX = iterationX;
1254 preciseY = iterationY;
1255
1257 }
1258
1259 if (fitOffsetX == TFloat(0) && fitOffsetY == TFloat(0))
1260 {
1261 preciseX = iterationX;
1262 preciseY = iterationY;
1263
1265 }
1266
1267 const TFloat pixelOffsetX = fitOffsetX * stepFactor;
1268 const TFloat pixelOffsetY = fitOffsetY * stepFactor;
1269
1270 if (NumericT<TFloat>::abs(pixelOffsetX) > stepFactor || NumericT<TFloat>::abs(pixelOffsetY) > stepFactor)
1271 {
1272 preciseX = iterationX;
1273 preciseY = iterationY;
1274
1276 }
1277
1278 iterationX += pixelOffsetX;
1279 iterationY += pixelOffsetY;
1280
1281 stepFactor *= stepSize;
1282
1283 if (pixelOffsetX * pixelOffsetX + pixelOffsetY * pixelOffsetY < sqrConvergenceThreshold)
1284 {
1285 preciseX = iterationX;
1286 preciseY = iterationY;
1287
1289 }
1290 }
1291
1292 preciseX = iterationX;
1293 preciseY = iterationY;
1294
1296}
1297
1298template <typename T>
1299void NonMaximumSuppressionT<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)
1300{
1301 ocean_assert(values != nullptr);
1302 ocean_assert(valuesStrideElements >= width_);
1303 ocean_assert(firstColumn + numberColumns <= width_);
1304
1305 ocean_assert(typename StrengthCandidateRows::Index(firstRow) >= rows_.firstIndex());
1306 ocean_assert(typename StrengthCandidateRows::Index(firstRow + numberRows) <= rows_.endIndex());
1307
1308 const T localThreshold = *minimalThreshold;
1309
1310 values += firstRow * valuesStrideElements;
1311
1312 for (unsigned int y = firstRow; y < firstRow + numberRows; ++y)
1313 {
1314 for (unsigned int x = firstColumn; x < firstColumn + numberColumns; ++x)
1315 {
1316 if (values[x] >= localThreshold)
1317 {
1318 addCandidate(x, y, values[x]);
1319 }
1320 }
1321
1322 values += valuesStrideElements;
1323 }
1324}
1325
1326template <typename T>
1327template <typename TCoordinate, typename TStrength, bool tStrictMaximum, bool tOnlyPositive>
1328void NonMaximumSuppressionT<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
1329{
1330 ocean_assert(strengthPositions);
1331
1332 ocean_assert(firstColumn + numberColumns <= width_);
1333 ocean_assert(firstRow >= (unsigned int)(rows_.firstIndex()));
1334 ocean_assert(firstRow + numberRows <= (unsigned int)rows_.endIndex());
1335
1336 if (numberColumns < 3u || numberRows < 3u)
1337 {
1338 return;
1339 }
1340
1341 const unsigned int firstCenterColumn = max(1u, firstColumn);
1342 const unsigned int endCenterColumn = min(firstColumn + numberColumns, width_ - 1u);
1343
1344 const unsigned int firstCenterRow = max((unsigned int)rows_.firstIndex() + 1u, firstRow);
1345 const unsigned int endCenterRow = min(firstRow + numberRows, (unsigned int)rows_.lastIndex());
1346
1347 ocean_assert(firstCenterRow >= 1u);
1348
1349 StrengthPositions<TCoordinate, TStrength> localStrengthPositions;
1350 localStrengthPositions.reserve(100);
1351
1352 for (unsigned int y = firstCenterRow; y < endCenterRow; ++y)
1353 {
1354 const StrengthCandidateRow& row0 = rows_[y - 1u];
1355 const StrengthCandidateRow& row1 = rows_[y + 0u];
1356 const StrengthCandidateRow& row2 = rows_[y + 1u];
1357
1358 typename StrengthCandidateRow::const_iterator iRow0 = row0.begin();
1359 typename StrengthCandidateRow::const_iterator iRow2 = row2.begin();
1360
1361 typename StrengthCandidateRow::const_iterator iRow1Minus = row1.end();
1362 typename StrengthCandidateRow::const_iterator iRow1Plus = row1.size() > 1 ? row1.begin() + 1 : row1.end();
1363
1364 for (typename StrengthCandidateRow::const_iterator iRow1 = row1.begin(); iRow1 != row1.end(); ++iRow1)
1365 {
1366 ocean_assert(iRow1->x() >= 0u && iRow1->x() + 1u <= width_);
1367
1368 // check value constraint if enabled
1369 if constexpr (tOnlyPositive)
1370 {
1371 if (iRow1->strength() <= T(0))
1372 {
1373 goto next;
1374 }
1375 }
1376
1377 // check left candidate (west)
1378 if (iRow1->x() >= firstCenterColumn && iRow1->x() < endCenterColumn && (iRow1Minus == row1.end() || iRow1Minus->x() + 1u != iRow1->x() || (tStrictMaximum && iRow1Minus->strength() < iRow1->strength()) || (!tStrictMaximum && iRow1Minus->strength() <= iRow1->strength())))
1379 {
1380 // check right candidate (east)
1381 if (iRow1Plus == row1.end() || iRow1Plus->x() != iRow1->x() + 1u || iRow1Plus->strength() < iRow1->strength())
1382 {
1383 // set the top row iterator to the right position
1384 while (iRow0 != row0.end())
1385 {
1386 if (iRow0->x() + 1u < iRow1->x())
1387 {
1388 ++iRow0;
1389 }
1390 else
1391 {
1392 break;
1393 }
1394 }
1395
1396 // now iRow0 should point at least to the north west pixel position (or more far east)
1397 ocean_assert(iRow0 == row0.end() || iRow0->x() + 1u >= iRow1->x());
1398
1399 if (iRow0 != row0.end() && iRow0->x() <= iRow1->x() + 1u)
1400 {
1401 ocean_assert(iRow0->x() + 1u == iRow1->x() || iRow0->x() == iRow1->x() || iRow0->x() - 1u == iRow1->x());
1402
1403 if ((tStrictMaximum && iRow0->strength() >= iRow1->strength()) || (!tStrictMaximum && iRow0->strength() > iRow1->strength()))
1404 {
1405 goto next;
1406 }
1407
1408 // check if there is a further candidate in the north row
1409
1410 const typename StrengthCandidateRow::const_iterator iRow0Plus = iRow0 + 1;
1411
1412 if (iRow0Plus != row0.end() && iRow0Plus->x() <= iRow1->x() + 1u)
1413 {
1414 if ((tStrictMaximum && iRow0Plus->strength() >= iRow1->strength()) || (!tStrictMaximum && iRow0Plus->strength() > iRow1->strength()))
1415 {
1416 goto next;
1417 }
1418
1419 // check if there is a further candidate in the north row
1420
1421 const typename StrengthCandidateRow::const_iterator iRow0PlusPlus = iRow0Plus + 1;
1422
1423 if (iRow0PlusPlus != row0.end() && iRow0PlusPlus->x() <= iRow1->x() + 1u)
1424 {
1425 ocean_assert(iRow0PlusPlus->x() == iRow1->x() + 1u);
1426
1427 if ((tStrictMaximum && iRow0PlusPlus->strength() >= iRow1->strength()) || (!tStrictMaximum && iRow0PlusPlus->strength() > iRow1->strength()))
1428 {
1429 goto next;
1430 }
1431 }
1432 }
1433 }
1434
1435
1436 // set the bottom row iterator to the right position
1437 while (iRow2 != row2.end())
1438 {
1439 if (iRow2->x() + 1u < iRow1->x())
1440 {
1441 ++iRow2;
1442 }
1443 else
1444 {
1445 break;
1446 }
1447 }
1448
1449 // now iRow2 should point at least to the south west pixel position (or more far east)
1450 ocean_assert(iRow2 == row2.end() || iRow2->x() + 1u >= iRow1->x());
1451
1452 if (iRow2 != row2.end() && iRow2->x() <= iRow1->x() + 1u)
1453 {
1454 ocean_assert(iRow2->x() + 1u == iRow1->x() || iRow2->x() == iRow1->x() || iRow2->x() - 1u == iRow1->x());
1455
1456 if (iRow2->x() + 1u == iRow1->x())
1457 {
1458 // iRow2 points to the south west pixel
1459
1460 if ((tStrictMaximum && iRow2->strength() >= iRow1->strength()) || (!tStrictMaximum && iRow2->strength() > iRow1->strength()))
1461 {
1462 goto next;
1463 }
1464 }
1465 else
1466 {
1467 if (iRow2->strength() >= iRow1->strength())
1468 {
1469 goto next;
1470 }
1471 }
1472
1473 // check if there is a further candidate in the south row
1474
1475 const typename StrengthCandidateRow::const_iterator iRow2Plus = iRow2 + 1;
1476
1477 if (iRow2Plus != row2.end() && iRow2Plus->x() <= iRow1->x() + 1u)
1478 {
1479 if (iRow2Plus->strength() >= iRow1->strength())
1480 {
1481 goto next;
1482 }
1483
1484 // check if there is a further candidate in the south row
1485
1486 const typename StrengthCandidateRow::const_iterator iRow2PlusPlus = iRow2Plus + 1;
1487
1488 if (iRow2PlusPlus != row2.end() && iRow2PlusPlus->x() <= iRow1->x() + 1u)
1489 {
1490 ocean_assert(iRow2PlusPlus->x() == iRow1->x() + 1u);
1491
1492 if (iRow2PlusPlus->strength() >= iRow1->strength())
1493 {
1494 goto next;
1495 }
1496 }
1497 }
1498 }
1499
1500 if (positionCallback != nullptr)
1501 {
1502 TCoordinate preciseX;
1503 TCoordinate preciseY;
1504 TStrength preciseStrength;
1505
1506 if ((*positionCallback)(iRow1->x(), y, iRow1->strength(), preciseX, preciseY, preciseStrength))
1507 {
1508 localStrengthPositions.emplace_back(preciseX, preciseY, preciseStrength);
1509 }
1510 }
1511 else
1512 {
1513 localStrengthPositions.emplace_back(TCoordinate(iRow1->x()), TCoordinate(y), iRow1->strength());
1514 }
1515 }
1516 }
1517
1518next:
1519
1520 iRow1Minus = iRow1;
1521
1522 if (iRow1Plus != row1.end())
1523 {
1524 ++iRow1Plus;
1525 }
1526 }
1527 }
1528
1529 const OptionalScopedLock scopedLock(lock);
1530
1531 strengthPositions->insert(strengthPositions->end(), localStrengthPositions.begin(), localStrengthPositions.end());
1532}
1533
1534template <typename T>
1535template <typename TCoordinate, typename TStrength, bool tStrictMaximum, bool tOnlyNegative>
1536void NonMaximumSuppressionT<T>::suppressNonMinimumSubset(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
1537{
1538 ocean_assert(strengthPositions);
1539
1540 ocean_assert(firstColumn + numberColumns <= width_);
1541 ocean_assert(firstRow >= (unsigned int)(rows_.firstIndex()));
1542 ocean_assert(firstRow + numberRows <= (unsigned int)rows_.endIndex());
1543
1544 if (numberColumns < 3u || numberRows < 3u)
1545 {
1546 return;
1547 }
1548
1549 const unsigned int firstCenterColumn = max(1u, firstColumn);
1550 const unsigned int endCenterColumn = min(firstColumn + numberColumns, width_ - 1u);
1551
1552 const unsigned int firstCenterRow = max((unsigned int)rows_.firstIndex() + 1u, firstRow);
1553 const unsigned int endCenterRow = min(firstRow + numberRows, (unsigned int)rows_.lastIndex());
1554
1555 ocean_assert(firstCenterRow >= 1u);
1556
1557 StrengthPositions<TCoordinate, TStrength> localStrengthPositions;
1558 localStrengthPositions.reserve(100);
1559
1560 for (unsigned int y = firstCenterRow; y < endCenterRow; ++y)
1561 {
1562 const StrengthCandidateRow& row0 = rows_[y - 1u];
1563 const StrengthCandidateRow& row1 = rows_[y + 0u];
1564 const StrengthCandidateRow& row2 = rows_[y + 1u];
1565
1566 typename StrengthCandidateRow::const_iterator iRow0 = row0.begin();
1567 typename StrengthCandidateRow::const_iterator iRow2 = row2.begin();
1568
1569 typename StrengthCandidateRow::const_iterator iRow1Minus = row1.end();
1570 typename StrengthCandidateRow::const_iterator iRow1Plus = row1.size() > 1 ? row1.begin() + 1 : row1.end();
1571
1572 for (typename StrengthCandidateRow::const_iterator iRow1 = row1.begin(); iRow1 != row1.end(); ++iRow1)
1573 {
1574 ocean_assert(iRow1->x() >= 0u && iRow1->x() + 1u <= width_);
1575
1576 // check value constraint if enabled
1577 if constexpr (tOnlyNegative)
1578 {
1579 if (iRow1->strength() >= T(0))
1580 {
1581 goto nextMinimum;
1582 }
1583 }
1584
1585 // check left candidate (west)
1586 if (iRow1->x() >= firstCenterColumn && iRow1->x() < endCenterColumn && (iRow1Minus == row1.end() || iRow1Minus->x() + 1u != iRow1->x() || (tStrictMaximum && iRow1Minus->strength() > iRow1->strength()) || (!tStrictMaximum && iRow1Minus->strength() >= iRow1->strength())))
1587 {
1588 // check right candidate (east)
1589 if (iRow1Plus == row1.end() || iRow1Plus->x() != iRow1->x() + 1u || iRow1Plus->strength() > iRow1->strength())
1590 {
1591 // set the top row iterator to the right position
1592 while (iRow0 != row0.end())
1593 {
1594 if (iRow0->x() + 1u < iRow1->x())
1595 {
1596 ++iRow0;
1597 }
1598 else
1599 {
1600 break;
1601 }
1602 }
1603
1604 // now iRow0 should point at least to the north west pixel position (or more far east)
1605 ocean_assert(iRow0 == row0.end() || iRow0->x() + 1u >= iRow1->x());
1606
1607 if (iRow0 != row0.end() && iRow0->x() <= iRow1->x() + 1u)
1608 {
1609 ocean_assert(iRow0->x() + 1u == iRow1->x() || iRow0->x() == iRow1->x() || iRow0->x() - 1u == iRow1->x());
1610
1611 if ((tStrictMaximum && iRow0->strength() <= iRow1->strength()) || (!tStrictMaximum && iRow0->strength() < iRow1->strength()))
1612 {
1613 goto nextMinimum;
1614 }
1615
1616 // check if there is a further candidate in the north row
1617
1618 const typename StrengthCandidateRow::const_iterator iRow0Plus = iRow0 + 1;
1619
1620 if (iRow0Plus != row0.end() && iRow0Plus->x() <= iRow1->x() + 1u)
1621 {
1622 if ((tStrictMaximum && iRow0Plus->strength() <= iRow1->strength()) || (!tStrictMaximum && iRow0Plus->strength() < iRow1->strength()))
1623 {
1624 goto nextMinimum;
1625 }
1626
1627 // check if there is a further candidate in the north row
1628
1629 const typename StrengthCandidateRow::const_iterator iRow0PlusPlus = iRow0Plus + 1;
1630
1631 if (iRow0PlusPlus != row0.end() && iRow0PlusPlus->x() <= iRow1->x() + 1u)
1632 {
1633 ocean_assert(iRow0PlusPlus->x() == iRow1->x() + 1u);
1634
1635 if ((tStrictMaximum && iRow0PlusPlus->strength() <= iRow1->strength()) || (!tStrictMaximum && iRow0PlusPlus->strength() < iRow1->strength()))
1636 {
1637 goto nextMinimum;
1638 }
1639 }
1640 }
1641 }
1642
1643
1644 // set the bottom row iterator to the right position
1645 while (iRow2 != row2.end())
1646 {
1647 if (iRow2->x() + 1u < iRow1->x())
1648 {
1649 ++iRow2;
1650 }
1651 else
1652 {
1653 break;
1654 }
1655 }
1656
1657 // now iRow2 should point at least to the south west pixel position (or more far east)
1658 ocean_assert(iRow2 == row2.end() || iRow2->x() + 1u >= iRow1->x());
1659
1660 if (iRow2 != row2.end() && iRow2->x() <= iRow1->x() + 1u)
1661 {
1662 ocean_assert(iRow2->x() + 1u == iRow1->x() || iRow2->x() == iRow1->x() || iRow2->x() - 1u == iRow1->x());
1663
1664 if (iRow2->x() + 1u == iRow1->x())
1665 {
1666 // iRow2 points to the south west pixel
1667
1668 if ((tStrictMaximum && iRow2->strength() <= iRow1->strength()) || (!tStrictMaximum && iRow2->strength() < iRow1->strength()))
1669 {
1670 goto nextMinimum;
1671 }
1672 }
1673 else
1674 {
1675 if (iRow2->strength() <= iRow1->strength())
1676 {
1677 goto nextMinimum;
1678 }
1679 }
1680
1681 // check if there is a further candidate in the south row
1682
1683 const typename StrengthCandidateRow::const_iterator iRow2Plus = iRow2 + 1;
1684
1685 if (iRow2Plus != row2.end() && iRow2Plus->x() <= iRow1->x() + 1u)
1686 {
1687 if (iRow2Plus->strength() <= iRow1->strength())
1688 {
1689 goto nextMinimum;
1690 }
1691
1692 // check if there is a further candidate in the south row
1693
1694 const typename StrengthCandidateRow::const_iterator iRow2PlusPlus = iRow2Plus + 1;
1695
1696 if (iRow2PlusPlus != row2.end() && iRow2PlusPlus->x() <= iRow1->x() + 1u)
1697 {
1698 ocean_assert(iRow2PlusPlus->x() == iRow1->x() + 1u);
1699
1700 if (iRow2PlusPlus->strength() <= iRow1->strength())
1701 {
1702 goto nextMinimum;
1703 }
1704 }
1705 }
1706 }
1707
1708 if (positionCallback != nullptr)
1709 {
1710 TCoordinate preciseX;
1711 TCoordinate preciseY;
1712 TStrength preciseStrength;
1713
1714 if ((*positionCallback)(iRow1->x(), y, iRow1->strength(), preciseX, preciseY, preciseStrength))
1715 {
1716 localStrengthPositions.emplace_back(preciseX, preciseY, preciseStrength);
1717 }
1718 }
1719 else
1720 {
1721 localStrengthPositions.emplace_back(TCoordinate(iRow1->x()), TCoordinate(y), iRow1->strength());
1722 }
1723 }
1724 }
1725
1726nextMinimum:
1727
1728 iRow1Minus = iRow1;
1729
1730 if (iRow1Plus != row1.end())
1731 {
1732 ++iRow1Plus;
1733 }
1734 }
1735 }
1736
1737 const OptionalScopedLock scopedLock(lock);
1738
1739 strengthPositions->insert(strengthPositions->end(), localStrengthPositions.begin(), localStrengthPositions.end());
1740}
1741
1742}
1743
1744}
1745
1746#endif // META_OCEAN_CV_NON_MAXIMUM_SUPPRESSION_H
This class extends a 2D position by a third parameter storing a strength value.
Definition NonMaximumSuppression.h:76
const TStrength & strength() const
Returns the strength parameter of this object.
Definition NonMaximumSuppression.h:491
StrengthPosition()=default
Creates a new object with default strength parameter.
TStrength strength_
Strength parameter of this object.
Definition NonMaximumSuppression.h:111
static bool compareStrength(const StrengthPosition< TCoordinate, TStrength > &left, const StrengthPosition< TCoordinate, TStrength > &right)
Compares the strength value of two objects.
Definition NonMaximumSuppression.h:498
This class provides base functionality and type definitions for non-maximum-suppression operations.
Definition NonMaximumSuppression.h:34
SuppressionMode
Definition of individual suppression modes for extremum search.
Definition NonMaximumSuppression.h:41
@ SM_MAXIMUM
Finds the maximum values, any value is allowed.
Definition NonMaximumSuppression.h:43
@ SM_MINIMUM
Finds the minimum values, any value is allowed.
Definition NonMaximumSuppression.h:45
@ SM_MAXIMUM_POSITIVE_ONLY
Finds the maximum values, only positive values are allowed (> 0).
Definition NonMaximumSuppression.h:47
@ SM_MINIMUM_NEGATIVE_ONLY
Finds the minimum values, only negative values are allowed (< 0).
Definition NonMaximumSuppression.h:49
RefinementStatus
Definition of individual refinement status values for iterative sub-pixel peak refinement.
Definition NonMaximumSuppression.h:56
@ RS_CONVERGED
The refinement converged, the offset is below the convergence threshold.
Definition NonMaximumSuppression.h:66
@ RS_BORDER
The sample position fell outside the frame bounds.
Definition NonMaximumSuppression.h:62
@ RS_MAX_ITERATIONS
The refinement did not converge within the maximum number of iterations.
Definition NonMaximumSuppression.h:64
@ RS_INVALID
Invalid status, no refinement was applied.
Definition NonMaximumSuppression.h:58
@ RS_DIVERGED
The fitted offset exceeded the step size, indicating divergence.
Definition NonMaximumSuppression.h:60
std::vector< StrengthPosition< TCoordinate, TStrength > > StrengthPositions
Definition of a vector holding strength pixel positions.
Definition NonMaximumSuppression.h:118
This class holds the horizontal position and strength parameter of an interest pixel.
Definition NonMaximumSuppression.h:162
StrengthCandidate()
Creates a new candidate object.
Definition NonMaximumSuppression.h:511
unsigned int positionX_
Horizontal position of this object.
Definition NonMaximumSuppression.h:192
const T & strength() const
Returns the strength parameter of this object.
Definition NonMaximumSuppression.h:533
unsigned int x() const
Returns the horizontal position of this candidate object.
Definition NonMaximumSuppression.h:527
T strength_
Strength parameter of this object.
Definition NonMaximumSuppression.h:195
This class implements the possibility to find local maximum in a 2D array by applying a non-maximum-s...
Definition NonMaximumSuppression.h:138
void 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
Applies a non-maximum-suppression search on a subset of a given 2D frame in a 3x3 neighborhood (eight...
Definition NonMaximumSuppression.h:1328
unsigned int yOffset() const
Returns the optional offset in the vertical direction.
Definition NonMaximumSuppression.h:573
static bool determinePrecisePeakLocationNxN(const TFloat *values, const size_t valuesStrideElements, TFloat &offsetX, TFloat &offsetY)
Determines the precise peak location in 2D space by fitting a quadratic surface via least-squares to ...
Definition NonMaximumSuppression.h:1064
void reset()
Removes the gathered non-maximum suppression information so that this object can be reused again (for...
Definition NonMaximumSuppression.h:764
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:1002
void addCandidate(const unsigned int x, const unsigned int y, const T &strength)
Adds a new candidate to this object.
Definition NonMaximumSuppression.h:581
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 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:617
unsigned int height() const
Returns the height of this object.
Definition NonMaximumSuppression.h:567
bool candidate(const unsigned int x, const unsigned int y, T &strength) const
Returns the strength value of a candidate at a specified position.
Definition NonMaximumSuppression.h:637
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:963
bool suppressNonMaximum(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, StrengthPositions< TCoordinate, TStrength > &strengthPositions, 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).
Definition NonMaximumSuppression.h:712
NonMaximumSuppressionT< T > & operator=(NonMaximumSuppressionT< T > &&nonMaximumSuppression)
Move operator.
Definition NonMaximumSuppression.h:773
std::vector< StrengthCandidate > StrengthCandidateRow
Definition of a vector holding strength candidate objects.
Definition NonMaximumSuppression.h:201
static NonMaximumSuppression::RefinementStatus determinePrecisePeakLocationIterativeNxN(const uint8_t *frame, const unsigned int width, const unsigned int height, const unsigned int framePaddingElements, const TFloat x, const TFloat y, TFloat &preciseX, TFloat &preciseY, const unsigned int maxIterations=5u, const TFloat stepSize=TFloat(0.75), const TFloat convergenceThreshold=TFloat(0.01))
Determines the precise sub-pixel peak location by iteratively sampling image intensity values at sub-...
Definition NonMaximumSuppression.h:1190
std::function< bool(const unsigned int x, const unsigned int y, const TStrength strength, TCoordinate &preciseX, TCoordinate &preciseY, TStrength &preciseStrength)> PositionCallback
Definition of a callback function used to determine the precise sub-pixel position of a specific poin...
Definition NonMaximumSuppression.h:154
void suppressNonMinimumSubset(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
Applies a non-minimum-suppression search on a subset of a given 2D frame in a 3x3 neighborhood (eight...
Definition NonMaximumSuppression.h:1536
NonMaximumSuppressionT(NonMaximumSuppressionT< T > &&nonMaximumSuppression) noexcept
Move constructor.
Definition NonMaximumSuppression.h:539
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:596
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:1299
StrengthCandidateRows rows_
All candidate rows.
Definition NonMaximumSuppression.h:479
unsigned int width_
Width of this object.
Definition NonMaximumSuppression.h:476
unsigned int width() const
Returns the width of this object.
Definition NonMaximumSuppression.h:561
void candidates(const unsigned int firstColumn, const unsigned int numberColumns, const unsigned int firstRow, const unsigned int numberRows, StrengthPositions< unsigned int, T > &strengthPositions) const
Returns all gathered candidates of this object.
Definition NonMaximumSuppression.h:682
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:387
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
std::ptrdiff_t Index
Definition of an element index.
Definition ShiftVector.h:38
void clear()
Clears this object, the specified index shift will be untouched.
Definition ShiftVector.h:658
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< Index32 > Indices32
Definition of a vector holding 32 bit index values.
Definition Base.h:96
std::vector< Indices32 > IndexGroups32
Definition of a vector holding 32 bit indices, so we have groups of indices.
Definition Base.h:102
uint32_t Index32
Definition of a 32 bit index value.
Definition Base.h:84
@ PC_TOP_LEFT
The center of a pixel is in the upper-left corner of each pixel's square.
Definition CV.h:133
The namespace covering the entire Ocean framework.
Definition Accessor.h:15