Ocean
FinderPatternDetector.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 #pragma once
9 
14 
15 #include "ocean/base/Frame.h"
16 #include "ocean/base/Memory.h"
17 
18 #include "ocean/cv/Bresenham.h"
19 
21 
22 #include "ocean/math/Box2.h"
24 #include "ocean/math/Vector2.h"
25 
26 #include <array>
27 #include <cstdint>
28 
29 namespace Ocean
30 {
31 
32 namespace CV
33 {
34 
35 namespace Detector
36 {
37 
38 namespace QRCodes
39 {
40 
41 /**
42  * Definition of a triplet of indices
43  * @ingroup cvdetectorqrcodes
44  */
45 typedef std::array<unsigned int, 3> IndexTriplet;
46 
47 /**
48  * Definition of a vector index triplets
49  * @ingroup cvdetectorqrcodes
50  */
51 typedef std::vector<IndexTriplet> IndexTriplets;
52 
53 /**
54  * Definition of a class for finder patterns of QR codes (squares in the top-left, top-right and bottom-left corners)
55  * @ingroup cvdetectorqrcodes
56  */
58 {
59  public:
60 
61  /**
62  * Creates an invalid finder pattern object.
63  */
64  inline FinderPattern();
65 
66  /**
67  * Creates a new finder pattern object by a given position and edge length.
68  * @param position The (center) position of the finder pattern within the camera frame
69  * @param length The edge length of the finder pattern in pixels, with range (0, infinity)
70  * @param centerIntensity The intensity that has been measured in the center of the finder pattern, range: [0, 255]
71  * @param grayThreshold Threshold that was used during the detection, range [0, 255]
72  * @param symmetryScore Symmetry score of this finder pattern, range: [0, infinity) (lower value = higher symmetry)
73  */
74  inline FinderPattern(const Vector2& position, const Scalar length, const unsigned int centerIntensity, const unsigned int grayThreshold, const Scalar symmetryScore);
75 
76  /**
77  * Creates a new finder pattern object by a given position and edge length.
78  * @param position The (center) position of the finder pattern within the camera frame
79  * @param length The edge length of the finder pattern in pixels, with range (0, infinity)
80  * @param centerIntensity The intensity that has been measured in the center of the finder pattern, range: [0, 255]
81  * @param grayThreshold Threshold that was used during the detection, range [0, 255]
82  * @param symmetryScore Symmetry score of this finder pattern, range: [0, infinity) (lower value = higher symmetry)
83  * @param corners The locations of the four corners of this finder pattern, must be valid and have 4 elements
84  * @param orientation Dominant orientation of the finder pattern
85  * @param moduleSize The size of modules (=bits) in pixels
86  */
87  inline FinderPattern(const Vector2& position, const Scalar length, const unsigned int centerIntensity, const unsigned int grayThreshold, const Scalar symmetryScore, const Vector2* corners, const Vector2& orientation, const Scalar moduleSize);
88 
89  /**
90  * Returns the (center) position of the finder pattern.
91  * @return The finder pattern's position within the camera frame
92  */
93  inline const Vector2& position() const;
94 
95  /**
96  * Returns the radius of the finder pattern.
97  * @return The finder pattern's radius, with range (0, infinity), 0 for an invalid object
98  */
99  inline Scalar length() const;
100 
101  /**
102  * Returns the intensity value that was measured in the center of the finder pattern
103  * @return The intensity value, range: [0, 255]
104  */
105  inline unsigned int centerIntensity() const;
106 
107  /**
108  * Returns the threshold that was used for the detection of this finder pattern
109  * @return The threshold value, range: [0, 255]
110  */
111  inline unsigned int grayThreshold() const;
112 
113  /**
114  * Returns the width of a module (= bit) in pixels
115  * @return The module width
116  */
117  inline Scalar moduleSize() const;
118 
119  /**
120  * Returns the symmetry score that was determined when this finder pattern was detected
121  * @return The symmetry score of this finder pattern
122  */
123  inline Scalar symmetryScore() const;
124 
125  /**
126  * Returns true if the four corners of this finder pattern are known, otherwise false
127  * @return True, if the four corners are known, otherwise false
128  */
129  inline bool cornersKnown() const;
130 
131  /**
132  * Returns a pointer to the four corners of this finder pattern.
133  * @return A constant pointer to the four corners of this finder pattern. These values are undefined if `cornersKnown()` returns false.
134  */
135  inline const Vector2* corners() const;
136 
137  /**
138  * Returns the dominant orientation of this finder pattern
139  * @return The vector defining the orientation (will be (1, 0) by default, i.e. if it's not set)
140  */
141  inline const Vector2& orientation() const;
142 
143  /**
144  * Returns whether this finder pattern is of normal reflectance
145  * @return True if so, otherwise false
146  */
147  inline bool isNormalReflectance() const;
148 
149  /**
150  * Comparator to sort finder patterns based on their location in an image
151  * Pattern `a` comes before pattern `b` if (pseudo-code) `a.y * imageWidth + a.x < b.y * imageWidth + b.x`
152  * @param first The first finder pattern to compare
153  * @param second The second finder pattern to compare
154  * @return True if the first pattern comes before the second pattern, otherwise false
155  */
156  static inline bool comesBefore(const FinderPattern& first, const FinderPattern& second);
157 
158  protected:
159 
160  /// The (center) position of the finder pattern within the camera frame.
162 
163  /// The edge length of the finder pattern in pixels, range: (0, infinity).
165 
166  /// The intensity value that has been measured in the center of the finder pattern
167  unsigned int centerIntensity_;
168 
169  /// The threshold that was used during the detection of this finder pattern.
170  unsigned int grayThreshold_;
171 
172  /// The symmetry score of this finder pattern, range: [0, infinity) (lower score = higher symmetry)
174 
175  /// True if the four corners of this finder pattern are known, otherwise false
177 
178  /// The four corners of this finder pattern; points are stored in counter-clockwise order but no guarantee on which corner is the first; if `cornersDetected_` is false these values will be undefined
180 
181  /// Dominant orientation of this finder pattern
183 
184  /// Module width (bit width) in pixels
186 };
187 
188 /**
189  * Definition of a vector holding finder pattern.
190  * @ingroup cvdetectorqrcodes
191  */
192 typedef std::vector<FinderPattern> FinderPatterns;
193 
194 /**
195  * Definition of a 3-tuple of finder patterns
196  * @ingroup cvdetectorqrcodes
197  */
198 typedef std::array<FinderPattern, 3> FinderPatternTriplet;
199 
200 /**
201  * This class implements a detector for finder patterns which are part of QR Codes.
202  * @ingroup cvdetectorqrcodes
203  */
204 class OCEAN_CV_DETECTOR_QRCODES_EXPORT FinderPatternDetector
205 {
206  protected:
207 
208  /// The intensity threshold between two successive pixels to count as a transition from dark to light (or vice versa).
209  static constexpr int deltaThreshold = 30;
210 
211  /**
212  * This class implements a simple history for previous pixel transitions (a sliding window of pixel transitions).
213  */
215  {
216  public:
217 
218  /**
219  * Creates a new history object.
220  */
221  inline TransitionHistory();
222 
223  /**
224  * Returns the history with window size N.
225  * @return The sum of the most recent delta
226  */
227  inline int history1();
228 
229  /**
230  * Returns the history with window size N.
231  * @return The sum of the most recent delta
232  */
233  inline int history2();
234 
235  /**
236  * Returns the history with window size N.
237  * @return The sum of the most recent delta
238  */
239  inline int history3();
240 
241  /**
242  * Returns the history with window size N.
243  * @return The sum of the most recent delta
244  */
245  inline int history4();
246 
247  /**
248  * Returns the history with window size N.
249  * @return The sum of the most recent delta
250  */
251  inline int history5();
252 
253  /**
254  * Adds a new delta object as most recent history.
255  * Existing history objects will be moved by one pixel.
256  * @param newDelta The new delta object to be added
257  */
258  inline void push(const int newDelta);
259 
260  /**
261  * Resets the history object.
262  */
263  inline void reset();
264 
265  protected:
266 
267  /// The most recent deltas.
268  int deltas_[5] = { 0, 0, 0, 0, 0 };
269  };
270 
271  public:
272 
273  /**
274  * Detects finder patterns of a QR code in a 8 bit grayscale image.
275  * @param yFrame The 8 bit grayscale frame in which the finder patterns will be detected, must be valid
276  * @param width The width of the given grayscale frame in pixel, with range [15, infinity)
277  * @param height The height of the given grayscale frame in pixel, with range [15, infinity)
278  * @param minimumDistance The minimum distance in pixels that is enforced between any pair of finder patterns, range: [0, infinity), default: 10
279  * @param paddingElements Optional number of padding elements at the end of each image row, in elements, with range [0, infinity), default: 0
280  * @param worker Optional worker to distribute the computation
281  * @return The detected finder patterns
282  */
283  static FinderPatterns detectFinderPatterns(const uint8_t* const yFrame, const unsigned int width, const unsigned int height, const unsigned int minimumDistance = 10u, const unsigned int paddingElements = 0u, Worker* worker = nullptr);
284 
285  /**
286  * Extract 3-tuples of finder patterns that form good (plausible) candidates for QR code symbols
287  * @param finderPatterns The list finder patterns in which 3-tuples forming potential QR code symbols are sought, must be valid, minimum size: 3
288  * @param distanceScaleTolerance Scale factor that define how much corners of one finder pattern may deviate from the parallel lines of another finder pattern, range: [0, infinity), default: 0.05
289  * @param moduleSizeScaleTolerance Defines the maximum difference scale of the module size between pairs of finder patterns in order to be considered a match, range: [0, 1], default: 0.35
290  * @param angleTolerance Defines the maximum difference of the dominant orientation of pairs of finder patterns in order to be considered a match, measured in radian, range: [0, PI/4), default: deg2rad(9)
291  * @return A list of 3-tuples of finder patterns, will be empty on failure (or if nothing was found)
292  */
293  static IndexTriplets extractIndexTriplets(const FinderPatterns& finderPatterns, const Scalar distanceScaleTolerance = Scalar(0.175), const Scalar moduleSizeScaleTolerance = Scalar(0.35), const Scalar angleTolerance = Numeric::deg2rad(Scalar(9)));
294 
295  protected:
296 
297  /**
298  * Detects finder patterns of QR codes in subregion of a given 8 bit grayscale image.
299  * @param yFrame The 8 bit grayscale frame in which the finder patterns will be detected, must be valid
300  * @param width The width of the given grayscale frame in pixel, with range [15, infinity)
301  * @param height The height of the given grayscale frame in pixel, with range [15, infinity)
302  * @param finderPatterns The resulting finderPatterns, will be added to the end of the vector
303  * @param multiThreadLock Lock object in case this function is executed in multiple threads concurrently, otherwise nullptr
304  * @param paddingElements Optional number of padding elements at the end of each image row, in elements, with range [0, infinity)
305  * @param firstRow The first row to be handled, with range [7, height - 7)
306  * @param numberRows The number of rows to be handled, with range [1, height - 7 - firstRow]
307  */
308  static void detectFinderPatternsSubset(const uint8_t* const yFrame, const unsigned int width, const unsigned int height, FinderPatterns* finderPatterns, Lock* multiThreadLock, const unsigned int paddingElements, const unsigned int firstRow, const unsigned int numberRows);
309 
310  /**
311  * Detects finder patterns of QR codes in a single row of an grayscale image.
312  * @param yFrame The 8 bit grayscale frame in which the finder patterns will be detected, must be valid
313  * @param width The width of the given grayscale frame in pixel, with range [15, infinity)
314  * @param height The height of the given grayscale frame in pixel, with range [15, infinity)
315  * @param y The index of the row in which the finder patterns will be detected, with range [7, height - 8]
316  * @param finderPatterns The resulting detected finder patterns, will be added to the end of the vector
317  * @param paddingElements Optional number of padding elements at the end of each image row, in elements, with range [0, infinity)
318  */
319  static void detectFinderPatternInRow(const uint8_t* const yFrame, const unsigned int width, const unsigned int height, const unsigned int y, FinderPatterns& finderPatterns, const unsigned int paddingElements);
320 
321  /**
322  * Estimates the locations of the corners of finder pattern and computes the dominant orientation of the finder pattern from those corners
323  * @param xCenter The pixel-accurate x-coordinate of the candidate location
324  * @param yCenter The pixel-accurate y-coordinate of the candidate location
325  * @param edgePoints The edge points which will be used to determine the corners of the finder pattern, must be valid and have `edgePointsSize` elements
326  * @param edgePointsSize Number of edge points that are available, range: [2, infinity) must be even
327  * @param location The resulting center location of the finder pattern that is determined from the four corners that will be determined by this function
328  * @param corners The resulting four corners of the finder pattern that will be determined, must be valid, in counter-clockwise order, and must have size of at least 4 elements
329  * @param orientation The resulting main orientation of the finder pattern that will be determined
330  * @param moduleSize The resulting size of the modules in this finder pattern candidate
331  * @param edgePointDistanceTolerance The factor that defines the maximum deviation from the distance between the center and the edge point closest to the center, range: [0, infinity)
332  * @param maxEdgeLineDistance The maximum distance (in pixel) that new edge points may have in order to be accepted as "on the edge line", range: [0, infinity)
333  * @return True on success, otherwise false
334  */
335  static bool estimateFinderPatternCorners(const unsigned int xCenter, const unsigned int yCenter, const Vector2* edgePoints, const unsigned int edgePointsSize, Vector2& location, Vector2* corners, Vector2& orientation, Scalar& moduleSize, const Scalar edgePointDistanceTolerance = 2.25, const Scalar maxEdgeLineDistance = 1.5);
336 
337  /**
338  * Refine the location and corners of a finder pattern
339  * @param yFrame Pointer to the input grayscale image, must be valid
340  * @param width The width of the input grayscale image, range: [1, infinity)
341  * @param height The height of the input grayscale image, range:[1, infinity)
342  * @param finderPattern The resulting finder pattern of which its position and corners will be refined
343  * @param yFramePaddingElements The number of padding elements of the input grayscale image, range: [0, infinity)
344  * @return True if the refinement was successful, otherwise false
345  * @sa estimateFinderPatternCorners()
346  */
347  static bool refineFinderPatternLocation(const uint8_t* const yFrame, const unsigned int width, const unsigned int height, FinderPattern& finderPattern, const unsigned int yFramePaddingElements = 0u);
348 
349  /**
350  * Performs a check around a given candidate location looking for a correct configuration of light and dark pixels (testing 8 angles each yielding 2 edge points)
351  * @param yFrame The 8 bit grayscale frame in which the finder pattern candidate will be tested, must be valid
352  * @param width The width of the given grayscale frame in pixels, range: [15, infinity)
353  * @param height The height of the given grayscale frame in pixels, range: [15, infinity)
354  * @param paddingElements The number of padding elements in the given image with range [0, infinity)
355  * @param xCenter The horizontal location within the frame at which the existence of the finder pattern will be checked, in pixels, with range [0, width - 1]
356  * @param yCenter The vertical location within the frame at which the existence of the finder pattern will be checked, in pixels, with range [0, height - 1]
357  * @param threshold The grayscale threshold separating a bright pixel from a dark pixel, with range [0, 255]
358  * @param blackSquareSegmentMin Minimum diameter of the outer black square in pixels, range: [1, infinity)
359  * @param blackSquareSegmentMax Maximum diameter of the outer black square in pixels, range: [blackSquareSegmentMin, infinity)
360  * @param whiteSquareSegmentMin Minimum diameter of the inner white square in pixels, range: [1, infinity)
361  * @param whiteSquareSegmentMax Maximum diameter of the inner white square in pixels, range: [whiteSquareSegmentMin, infinity)
362  * @param centerSegmentMin Minimum diameter of the center black square in pixels, range: [1, infinity)
363  * @param centerSegmentMax Maximum diameter of the center black square in pixels, range: [centerSegmentMin, infinity)
364  * @param symmetryScore The resulting symmetry score that is computed for the current candidate location `(xCenter, yCenter)`; this score is based on distances so the lower the score, the better. Range: [0, infinity)
365  * @param edgePoints If specified, will hold the resulting points detected during the directional checks on the outside border of the finder pattern candidate. Must be valid, expected size: `2 * angles`
366  * @return True if all edge points of the finder pattern are found in all scanline directions, otherwise false
367  */
368  static bool checkFinderPatternInNeighborhood(const uint8_t* const yFrame, const unsigned width, const unsigned height, const unsigned int paddingElements, const unsigned int xCenter, const unsigned int yCenter, const unsigned int threshold, const unsigned int blackSquareSegmentMin, const unsigned int blackSquareSegmentMax, const unsigned int whiteSquareSegmentMin, const unsigned int whiteSquareSegmentMax, const unsigned int centerSegmentMin, const unsigned int centerSegmentMax, Scalar& symmetryScore, Vector2* edgePoints);
369 
370  /**
371  * Performs a check for a given candidate location in a specified direction (yielding 2 edge points)
372  * @param yFrame The 8 bit grayscale frame in which the finder pattern candidate will be tested, must be valid
373  * @param width The width of the given grayscale frame in pixels, range: [15, infinity)
374  * @param height The height of the given grayscale frame in pixels, range: [15, infinity)
375  * @param paddingElements The number of padding elements in the given image with range [0, infinity)
376  * @param xCenter The horizontal location within the frame at which the existence of the finder pattern will be checked, in pixels, with range [0, width - 1]
377  * @param yCenter The vertical location within the frame at which the existence of the finder pattern will be checked, in pixels, with range [0, height - 1]
378  * @param angle The angle in Radian defining the directions in which edge points will be searched, range: [0, pi)
379  * @param threshold The grayscale threshold separating a bright pixel from a dark pixel, with range [0, 255]
380  * @param blackSquareSegmentMin Minimum diameter of the outer black square in pixels, range: [1, infinity)
381  * @param blackSquareSegmentMax Maximum diameter of the outer black square in pixels, range: [blackSquareSegmentMin, infinity)
382  * @param whiteSquareSegmentMin Minimum diameter of the inner white square in pixels, range: [1, infinity)
383  * @param whiteSquareSegmentMax Maximum diameter of the inner white square in pixels, range: [whiteSquareSegmentMin, infinity)
384  * @param centerSegmentMin Minimum diameter of the center black square in pixels, range: [1, infinity)
385  * @param centerSegmentMax Maximum diameter of the center black square in pixels, range: [centerSegmentMin, infinity)
386  * @param topBorder The resulting location of the last pixel on the current finder pattern in the specified direction of the scanline
387  * @param bottomBorder The resulting location of the last pixel on the current finder pattern in the opposite direction (`angle + pi`) of the specified direction of the scanline
388  * @return True if the two edge points of the finder pattern are found in the specified scanline direction, otherwise false
389  */
390  static bool checkFinderPatternDirectional(const uint8_t* const yFrame, const unsigned int width, const unsigned int height, const unsigned int paddingElements, const unsigned int xCenter, const unsigned int yCenter, const Scalar angle, const unsigned int threshold, const unsigned int blackSquareSegmentMin, const unsigned int blackSquareSegmentMax, const unsigned int whiteSquareSegmentMin, const unsigned int whiteSquareSegmentMax, const unsigned int centerSegmentMin, const unsigned int centerSegmentMax, Vector2& topBorder, Vector2& bottomBorder);
391 
392  /**
393  * Checks whether the given pixel is a transition-to-black pixel (whether the direct left neighbor is a bright pixel).
394  * @param pixel The pixel to be checked, must be valid
395  * @param history The history object containing information about previous pixels
396  * @return True, if so
397  */
398  static inline bool isTransitionToBlack(const uint8_t* pixel, TransitionHistory& history);
399 
400  /**
401  * Checks whether the given pixel is a transition-to-white pixel (whether the direct left neighbor is a dark pixel).
402  * @param pixel The pixel to be checked, must be valid
403  * @param history The history object containing information about previous pixels
404  * @return True, if so
405  */
406  static inline bool isTransitionToWhite(const uint8_t* pixel, TransitionHistory& history);
407 
408  /**
409  * Determines the gray threshold separating bright pixels form dark pixels.
410  * The threshold is based on already actual pixel values for which the association is known already.<br>
411  * The provided start position is a pointer to any pixel within the image, with horizontal range [1, width - segmentSize1 - segmentSize2 - segmentSize3 - segmentSize4 - segmentSize5 - 2].
412  * In addition to the pixels covered by the five segments, the fist pixel left of the segments and the last pixel right of the segments are also used for estimation of the threshold.
413  * @param yPosition The first pixel within an 8 bit grayscale image for which 5 connected segments are known with black, white, black, white, and black pixels, must be valid
414  * @param segmentSize1 The number of pixels covering dark pixels, with range [1, width - ...)
415  * @param segmentSize2 The number of pixels covering bright pixels, with range [1, width - ...)
416  * @param segmentSize3 The number of pixels covering dark pixels, with range [1, width - ...)
417  * @param segmentSize4 The number of pixels covering bright pixels, with range [1, width - ...)
418  * @param segmentSize5 The number of pixels covering dark pixels, with range [1, width - segmentSize1 - segmentSize2 - segmentSize3 - segmentSize4 - 2]
419  * @return The threshold separating bright pixels from dark pixels, with range [0, 255], -1 if no valid threshold could be determined
420  */
421  static inline unsigned int determineThreshold(const uint8_t* yPosition, const unsigned int segmentSize1, const unsigned int segmentSize2, const unsigned int segmentSize3, const unsigned int segmentSize4, const unsigned int segmentSize5);
422 
423  /**
424  * Returns true if a pair of finder patterns is in parallel configuration, i.e., if one is above/below/left of/right of the other (and vice versa)
425  * @param finderPatternA The first finder pattern that will be used
426  * @param finderPatternB The second finder pattern that will be used
427  * @param distanceTolerance A scaling factor that defines how much the configuration may deviate from perfect parallelism, range: [0, infinity), default: 0.05
428  * @return True if the two finder patterns are in a parallel configuration, otherwise false
429  */
430  static inline bool isParallel(const FinderPattern& finderPatternA, const FinderPattern& finderPatternB, const Scalar distanceTolerance = Scalar(0.05));
431 
432  /**
433  * Returns true if a pair of finder patterns is in a diagonal configuration, i.e. the center of one pattern lies on one of the two diagonal (infinite) lines of the other finder pattern (and vice versa)
434  * @param finderPatternA The first finder pattern that will be used
435  * @param finderPatternB The second finder pattern that will be used
436  * @param angleTolerance Defines the angle that the centers of the finder pattern may deviate from the actual diagonal infinite lines (in radian), range: [0, PI/2), default: deg2rad(9)
437  * @return True if the two finder patterns are in a diagonal configuration, otherwise false
438  */
439  static inline bool isDiagonal(const FinderPattern& finderPatternA, const FinderPattern& finderPatternB, const Scalar angleTolerance = Numeric::deg2rad(9));
440 };
441 
443  FinderPattern::FinderPattern(Vector2(-1, -1), Scalar(0), 0u, 0u, Numeric::maxValue())
444 {
445  // nothing to do here
446 }
447 
448 inline FinderPattern::FinderPattern(const Vector2& position, const Scalar length, const unsigned int centerIntensity, const unsigned int grayThreshold, const Scalar symmetryScore) :
449  position_(position),
450  length_(length),
451  centerIntensity_(centerIntensity),
452  grayThreshold_(grayThreshold),
453  symmetryScore_(symmetryScore),
454  cornersKnown_(false),
455  orientation_(1, 0),
456  moduleSize_(length / Scalar(7))
457 {
458  ocean_assert(centerIntensity_ <= 255u);
459  ocean_assert(grayThreshold_ <= 255u);
460 
461  corners_[0] = Vector2(-1, -1);
462  corners_[1] = Vector2(-1, -1);
463  corners_[2] = Vector2(-1, -1);
464  corners_[3] = Vector2(-1, -1);
465 }
466 
467 inline FinderPattern::FinderPattern(const Vector2& position, const Scalar length, const unsigned int centerIntensity, const unsigned int grayThreshold, const Scalar symmetryScore, const Vector2* corners, const Vector2& orientation, const Scalar moduleSize) :
468  position_(position),
469  length_(length),
470  centerIntensity_(centerIntensity),
471  grayThreshold_(grayThreshold),
472  symmetryScore_(symmetryScore),
473  cornersKnown_(true),
474  orientation_(orientation),
475  moduleSize_(moduleSize)
476 {
477  ocean_assert(centerIntensity_ <= 255u);
478  ocean_assert(grayThreshold_ <= 255u);
479 
480  ocean_assert(corners != nullptr);
481 
482  // Expect a counter-clockwise order for the corners
483 
484  ocean_assert((corners[1] - corners[0]).cross(corners[3] - corners[0]) <= 0);
485  ocean_assert((corners[2] - corners[1]).cross(corners[0] - corners[1]) <= 0);
486  ocean_assert((corners[3] - corners[2]).cross(corners[1] - corners[2]) <= 0);
487  ocean_assert((corners[0] - corners[3]).cross(corners[2] - corners[3]) <= 0);
488 
489  corners_[0] = corners[0];
490  corners_[1] = corners[1];
491  corners_[2] = corners[2];
492  corners_[3] = corners[3];
493 }
494 
495 inline const Vector2& FinderPattern::position() const
496 {
497  return position_;
498 }
499 
501 {
502  return length_;
503 }
504 
505 inline unsigned int FinderPattern::centerIntensity() const
506 {
507  return centerIntensity_;
508 }
509 
510 inline unsigned int FinderPattern::grayThreshold() const
511 {
512  return grayThreshold_;
513 }
514 
516 {
517  return moduleSize_;
518 }
519 
521 {
522  return symmetryScore_;
523 }
524 
525 inline bool FinderPattern::cornersKnown() const
526 {
527  return cornersKnown_;
528 }
529 
530 inline const Vector2* FinderPattern::corners() const
531 {
532  // Expect a counter-clockwise order for the corners, if the corners are known
533 
534  ocean_assert(cornersKnown() == false || (corners_[1] - corners_[0]).cross(corners_[3] - corners_[0]) <= 0);
535  ocean_assert(cornersKnown() == false || (corners_[2] - corners_[1]).cross(corners_[0] - corners_[1]) <= 0);
536  ocean_assert(cornersKnown() == false || (corners_[3] - corners_[2]).cross(corners_[1] - corners_[2]) <= 0);
537  ocean_assert(cornersKnown() == false || (corners_[0] - corners_[3]).cross(corners_[2] - corners_[3]) <= 0);
538 
539  return corners_;
540 }
541 
542 inline const Vector2& FinderPattern::orientation() const
543 {
544  ocean_assert(Numeric::isEqualEps(orientation_.length() - Scalar(1)));
545  return orientation_;
546 }
547 
549 {
551 }
552 
553 inline bool FinderPattern::comesBefore(const FinderPattern& first, const FinderPattern& second)
554 {
555  return first.position().y() > second.position().y() || (first.position().y() == second.position().y() && first.position().x() > second.position().x());
556 };
557 
559  : deltas_{ 0, 0, 0, 0, 0 }
560 {
561  // Nothing else to do.
562 }
563 
565 {
566  return deltas_[0];
567 }
568 
570 {
571  return deltas_[0] + deltas_[1];
572 }
573 
575 {
576  return deltas_[0] + deltas_[1] + deltas_[2];
577 }
578 
580 {
581  return deltas_[0] + deltas_[1] + deltas_[2] + deltas_[3];
582 }
583 
585 {
586  return deltas_[0] + deltas_[1] + deltas_[2] + deltas_[3] + deltas_[4];
587 }
588 
589 inline void FinderPatternDetector::TransitionHistory::push(const int newDelta)
590 {
591  deltas_[4] = deltas_[3];
592  deltas_[3] = deltas_[2];
593  deltas_[2] = deltas_[1];
594  deltas_[1] = deltas_[0];
595  deltas_[0] = newDelta;
596 }
597 
599 {
600  deltas_[0] = 0;
601  deltas_[1] = 0;
602  deltas_[2] = 0;
603  deltas_[3] = 0;
604  deltas_[4] = 0;
605 }
606 
607 inline bool FinderPatternDetector::isTransitionToBlack(const uint8_t* pixel, TransitionHistory& history)
608 {
609  const int currentDelta = int(*(pixel + 0) - *(pixel - 1));
610 
611  bool result = false;
612 
613  if (currentDelta < -deltaThreshold)
614  {
615  result = true;
616  }
617  else if ((currentDelta + history.history1() < -deltaThreshold)
618  || (currentDelta + history.history2() < -(deltaThreshold * 5 / 4))
619  || (currentDelta + history.history3() < -(deltaThreshold * 6 / 4))
620  || (currentDelta + history.history4() < -(deltaThreshold * 7 / 4))
621  || (currentDelta + history.history5() < -(deltaThreshold * 8 / 4)))
622  {
623  result = true;
624  }
625 
626  history.push(currentDelta);
627 
628  return result;
629 }
630 
631 inline bool FinderPatternDetector::isTransitionToWhite(const uint8_t* pixel, TransitionHistory& history)
632 {
633  const int currentDelta = int(*(pixel + 0) - *(pixel - 1));
634 
635  bool result = false;
636 
637  if (currentDelta > deltaThreshold)
638  {
639  result = true;
640  }
641  else if ((currentDelta + history.history1() > deltaThreshold)
642  || (currentDelta + history.history2() > (deltaThreshold * 5 / 4))
643  || (currentDelta + history.history3() > (deltaThreshold * 6 / 4))
644  || (currentDelta + history.history4() > (deltaThreshold * 7 / 4))
645  || (currentDelta + history.history5() > (deltaThreshold * 8 / 4)))
646  {
647  result = true;
648  }
649 
650  history.push(currentDelta);
651 
652  return result;
653 }
654 
655 inline unsigned int FinderPatternDetector::determineThreshold(const uint8_t* yPosition, const unsigned int segmentSize1, const unsigned int segmentSize2, const unsigned int segmentSize3, const unsigned int segmentSize4, const unsigned int segmentSize5)
656 {
657  unsigned int sumBlack = 0u;
658  unsigned int sumWhite = 0u;
659 
660  sumWhite += *(yPosition - 1);
661 
662  for (unsigned int n = 0u; n < segmentSize1; ++n)
663  {
664  sumBlack += *yPosition++;
665  }
666 
667  for (unsigned int n = 0u; n < segmentSize2; ++n)
668  {
669  sumWhite += *yPosition++;
670  }
671 
672  for (unsigned int n = 0u; n < segmentSize3; ++n)
673  {
674  sumBlack += *yPosition++;
675  }
676 
677  for (unsigned int n = 0u; n < segmentSize4; ++n)
678  {
679  sumWhite += *yPosition++;
680  }
681 
682  for (unsigned int n = 0u; n < segmentSize5; ++n)
683  {
684  sumBlack += *yPosition++;
685  }
686 
687  sumWhite += *yPosition;
688 
689  const unsigned int averageBlack = sumBlack / (segmentSize1 + segmentSize3 + segmentSize5);
690  const unsigned int averageWhite = sumWhite / (segmentSize2 + segmentSize4 + 2u);
691 
692  if (averageBlack + 2u >= averageWhite)
693  {
694  // the separate between bright and dark pixels is not strong enough
695  return (unsigned int)(-1);
696  }
697 
698  return (averageBlack + averageWhite + 1u) / 2u;
699 }
700 
701 inline bool FinderPatternDetector::isParallel(const FinderPattern& finderPatternA, const FinderPattern& finderPatternB, const Scalar distanceTolerance)
702 {
703  ocean_assert(finderPatternA.cornersKnown() && finderPatternB.cornersKnown());
704  ocean_assert(finderPatternB.corners() != nullptr && finderPatternA.corners() != nullptr);
705  ocean_assert(distanceTolerance >= 0);
706 
707  const Line2 linesB[4] =
708  {
709  Line2(finderPatternB.corners()[1], (finderPatternB.corners()[0] - finderPatternB.corners()[1]).normalized()),
710  Line2(finderPatternB.corners()[2], (finderPatternB.corners()[1] - finderPatternB.corners()[2]).normalized()),
711  Line2(finderPatternB.corners()[3], (finderPatternB.corners()[2] - finderPatternB.corners()[3]).normalized()),
712  Line2(finderPatternB.corners()[0], (finderPatternB.corners()[3] - finderPatternB.corners()[0]).normalized())
713  };
714 
715  const Vector2 lineAB = finderPatternB.position() - finderPatternA.position();
716  const Vector2 directionAB = lineAB.normalizedOrZero();
717 
718  const Scalar squareDistanceThreshold = (lineAB.length() * distanceTolerance) * (lineAB.length() * distanceTolerance);
719 
720  for (unsigned int n = 0u; n < 4u; ++n)
721  {
722  // Reject pairs of lines diverge too much
723  if (Numeric::abs(directionAB * linesB[n].direction()) <= Numeric::cos(Numeric::deg2rad(35)))
724  {
725  continue;
726  }
727 
728  // Check if:
729  //
730  // * the corners `i` and `(i+1)` of finder pattern a are both "close enough" to the n-th line of finder pattern b, i.e., is the line between corners `i` and `(i+1)` roughly parallel to the n-th line of finder pattern b.
731  // * the opposite corners in finder pattern a, `(i+2) % 4` and `(i+3) % 4`, and line opposite to the n-th line in finder pattern b (n + 2 % 4) are roughly parallel as well.
732  //
733  // If both is true, finder patterns a and b are considered parallel.
734 
735  const Scalar sqrDistanceCornerA0 = linesB[n].sqrDistance(finderPatternA.corners()[0]);
736  const Scalar sqrDistanceCornerA1 = linesB[n].sqrDistance(finderPatternA.corners()[1]);
737 
738  if (sqrDistanceCornerA0 < squareDistanceThreshold && sqrDistanceCornerA1 < squareDistanceThreshold)
739  {
740  if (linesB[(n + 2u) & 0b0011u].sqrDistance(finderPatternA.corners()[2]) < squareDistanceThreshold && linesB[(n + 2u) & 0b0011u].sqrDistance(finderPatternA.corners()[3]) < squareDistanceThreshold) // (n + 2u) & 0b0011u == (n + 2u) % 4
741  {
742  return true;
743  }
744  }
745 
746  const Scalar sqrDistanceCornerA2 = linesB[n].sqrDistance(finderPatternA.corners()[2]);
747 
748  if (sqrDistanceCornerA1 < squareDistanceThreshold && sqrDistanceCornerA2 < squareDistanceThreshold)
749  {
750  if (linesB[(n + 2u) & 0b0011u].sqrDistance(finderPatternA.corners()[3]) < squareDistanceThreshold && linesB[(n + 2u) & 0b0011u].sqrDistance(finderPatternA.corners()[0]) < squareDistanceThreshold) // (n + 2u) & 0b0011u == (n + 2u) % 4
751  {
752  return true;
753  }
754  }
755 
756  const Scalar sqrDistanceCornerA3 = linesB[n].sqrDistance(finderPatternA.corners()[3]);
757 
758  if (sqrDistanceCornerA2 < squareDistanceThreshold && sqrDistanceCornerA3 < squareDistanceThreshold)
759  {
760  if (linesB[(n + 2u) & 0b0011u].sqrDistance(finderPatternA.corners()[0]) < squareDistanceThreshold && linesB[(n + 2u) & 0b0011u].sqrDistance(finderPatternA.corners()[1]) < squareDistanceThreshold) // (n + 2u) & 0b0011u == (n + 2u) % 4
761  {
762  return true;
763  }
764  }
765 
766  if (sqrDistanceCornerA3 < squareDistanceThreshold && sqrDistanceCornerA0 < squareDistanceThreshold)
767  {
768  if (linesB[(n + 2u) & 0b0011u].sqrDistance(finderPatternA.corners()[1]) < squareDistanceThreshold && linesB[(n + 2u) & 0b0011u].sqrDistance(finderPatternA.corners()[2]) < squareDistanceThreshold) // (n + 2u) & 0b0011u == (n + 2u) % 4
769  {
770  return true;
771  }
772  }
773  }
774 
775  return false;
776 }
777 
778 inline bool FinderPatternDetector::isDiagonal(const FinderPattern& finderPatternA, const FinderPattern& finderPatternB, const Scalar angleTolerance)
779 {
780  ocean_assert(finderPatternA.cornersKnown() && finderPatternB.cornersKnown());
781  ocean_assert(finderPatternB.corners() != nullptr && finderPatternA.corners() != nullptr);
782  ocean_assert(angleTolerance >= 0 && angleTolerance < Numeric::deg2rad(90));
783 
784  const Vector2 directionAB = (finderPatternB.position() - finderPatternA.position()).normalizedOrZero();
785  const Scalar angleThreshold = Numeric::abs(Numeric::cos(angleTolerance));
786 
787  const Vector2 diagonalsA[2] =
788  {
789  (finderPatternA.corners()[2] - finderPatternA.corners()[0]).normalizedOrZero(),
790  (finderPatternA.corners()[3] - finderPatternA.corners()[1]).normalizedOrZero()
791  };
792 
793  unsigned int diagonalEdgeA = (unsigned int)(-1);
794  unsigned int diagonalEdgeB = (unsigned int)(-1);
795 
796  if (Numeric::abs(diagonalsA[0] * directionAB) >= angleThreshold)
797  {
798  diagonalEdgeA = 0u;
799  }
800  else if (Numeric::abs(diagonalsA[1] * directionAB) >= angleThreshold)
801  {
802  diagonalEdgeA = 1u;
803  }
804 
805  if (diagonalEdgeA >= 2u)
806  {
807  return false;
808  }
809 
810  const Vector2 diagonalsB[2] =
811  {
812  (finderPatternB.corners()[2] - finderPatternB.corners()[0]).normalizedOrZero(),
813  (finderPatternB.corners()[3] - finderPatternB.corners()[1]).normalizedOrZero()
814  };
815 
816  if (Numeric::abs(diagonalsB[0] * directionAB) >= angleThreshold)
817  {
818  diagonalEdgeB = 0u;
819  }
820  else if (Numeric::abs(diagonalsB[1] * directionAB) >= angleThreshold)
821  {
822  diagonalEdgeB = 1u;
823  }
824 
825  return diagonalEdgeA < 2u && diagonalEdgeB < 2u;
826 }
827 
828 } // namespace QRCodes
829 
830 } // namespace Detector
831 
832 } // namespace CV
833 
834 } // namespace Ocean
This class implements a simple history for previous pixel transitions (a sliding window of pixel tran...
Definition: FinderPatternDetector.h:215
int history2()
Returns the history with window size N.
Definition: FinderPatternDetector.h:569
int history3()
Returns the history with window size N.
Definition: FinderPatternDetector.h:574
void push(const int newDelta)
Adds a new delta object as most recent history.
Definition: FinderPatternDetector.h:589
void reset()
Resets the history object.
Definition: FinderPatternDetector.h:598
int history5()
Returns the history with window size N.
Definition: FinderPatternDetector.h:584
TransitionHistory()
Creates a new history object.
Definition: FinderPatternDetector.h:558
int history4()
Returns the history with window size N.
Definition: FinderPatternDetector.h:579
int history1()
Returns the history with window size N.
Definition: FinderPatternDetector.h:564
This class implements a detector for finder patterns which are part of QR Codes.
Definition: FinderPatternDetector.h:205
static bool isDiagonal(const FinderPattern &finderPatternA, const FinderPattern &finderPatternB, const Scalar angleTolerance=Numeric::deg2rad(9))
Returns true if a pair of finder patterns is in a diagonal configuration, i.e.
Definition: FinderPatternDetector.h:778
static constexpr int deltaThreshold
The intensity threshold between two successive pixels to count as a transition from dark to light (or...
Definition: FinderPatternDetector.h:209
static bool checkFinderPatternInNeighborhood(const uint8_t *const yFrame, const unsigned width, const unsigned height, const unsigned int paddingElements, const unsigned int xCenter, const unsigned int yCenter, const unsigned int threshold, const unsigned int blackSquareSegmentMin, const unsigned int blackSquareSegmentMax, const unsigned int whiteSquareSegmentMin, const unsigned int whiteSquareSegmentMax, const unsigned int centerSegmentMin, const unsigned int centerSegmentMax, Scalar &symmetryScore, Vector2 *edgePoints)
Performs a check around a given candidate location looking for a correct configuration of light and d...
static bool refineFinderPatternLocation(const uint8_t *const yFrame, const unsigned int width, const unsigned int height, FinderPattern &finderPattern, const unsigned int yFramePaddingElements=0u)
Refine the location and corners of a finder pattern.
static IndexTriplets extractIndexTriplets(const FinderPatterns &finderPatterns, const Scalar distanceScaleTolerance=Scalar(0.175), const Scalar moduleSizeScaleTolerance=Scalar(0.35), const Scalar angleTolerance=Numeric::deg2rad(Scalar(9)))
Extract 3-tuples of finder patterns that form good (plausible) candidates for QR code symbols.
static bool isTransitionToBlack(const uint8_t *pixel, TransitionHistory &history)
Checks whether the given pixel is a transition-to-black pixel (whether the direct left neighbor is a ...
Definition: FinderPatternDetector.h:607
static bool checkFinderPatternDirectional(const uint8_t *const yFrame, const unsigned int width, const unsigned int height, const unsigned int paddingElements, const unsigned int xCenter, const unsigned int yCenter, const Scalar angle, const unsigned int threshold, const unsigned int blackSquareSegmentMin, const unsigned int blackSquareSegmentMax, const unsigned int whiteSquareSegmentMin, const unsigned int whiteSquareSegmentMax, const unsigned int centerSegmentMin, const unsigned int centerSegmentMax, Vector2 &topBorder, Vector2 &bottomBorder)
Performs a check for a given candidate location in a specified direction (yielding 2 edge points)
static FinderPatterns detectFinderPatterns(const uint8_t *const yFrame, const unsigned int width, const unsigned int height, const unsigned int minimumDistance=10u, const unsigned int paddingElements=0u, Worker *worker=nullptr)
Detects finder patterns of a QR code in a 8 bit grayscale image.
static unsigned int determineThreshold(const uint8_t *yPosition, const unsigned int segmentSize1, const unsigned int segmentSize2, const unsigned int segmentSize3, const unsigned int segmentSize4, const unsigned int segmentSize5)
Determines the gray threshold separating bright pixels form dark pixels.
Definition: FinderPatternDetector.h:655
static bool estimateFinderPatternCorners(const unsigned int xCenter, const unsigned int yCenter, const Vector2 *edgePoints, const unsigned int edgePointsSize, Vector2 &location, Vector2 *corners, Vector2 &orientation, Scalar &moduleSize, const Scalar edgePointDistanceTolerance=2.25, const Scalar maxEdgeLineDistance=1.5)
Estimates the locations of the corners of finder pattern and computes the dominant orientation of the...
static bool isParallel(const FinderPattern &finderPatternA, const FinderPattern &finderPatternB, const Scalar distanceTolerance=Scalar(0.05))
Returns true if a pair of finder patterns is in parallel configuration, i.e., if one is above/below/l...
Definition: FinderPatternDetector.h:701
static void detectFinderPatternsSubset(const uint8_t *const yFrame, const unsigned int width, const unsigned int height, FinderPatterns *finderPatterns, Lock *multiThreadLock, const unsigned int paddingElements, const unsigned int firstRow, const unsigned int numberRows)
Detects finder patterns of QR codes in subregion of a given 8 bit grayscale image.
static void detectFinderPatternInRow(const uint8_t *const yFrame, const unsigned int width, const unsigned int height, const unsigned int y, FinderPatterns &finderPatterns, const unsigned int paddingElements)
Detects finder patterns of QR codes in a single row of an grayscale image.
static bool isTransitionToWhite(const uint8_t *pixel, TransitionHistory &history)
Checks whether the given pixel is a transition-to-white pixel (whether the direct left neighbor is a ...
Definition: FinderPatternDetector.h:631
Definition of a class for finder patterns of QR codes (squares in the top-left, top-right and bottom-...
Definition: FinderPatternDetector.h:58
const Vector2 * corners() const
Returns a pointer to the four corners of this finder pattern.
Definition: FinderPatternDetector.h:530
Scalar symmetryScore() const
Returns the symmetry score that was determined when this finder pattern was detected.
Definition: FinderPatternDetector.h:520
const Vector2 & orientation() const
Returns the dominant orientation of this finder pattern.
Definition: FinderPatternDetector.h:542
Vector2 corners_[4]
The four corners of this finder pattern; points are stored in counter-clockwise order but no guarante...
Definition: FinderPatternDetector.h:179
bool cornersKnown_
True if the four corners of this finder pattern are known, otherwise false.
Definition: FinderPatternDetector.h:176
Scalar length() const
Returns the radius of the finder pattern.
Definition: FinderPatternDetector.h:500
bool cornersKnown() const
Returns true if the four corners of this finder pattern are known, otherwise false.
Definition: FinderPatternDetector.h:525
const Vector2 & position() const
Returns the (center) position of the finder pattern.
Definition: FinderPatternDetector.h:495
unsigned int grayThreshold() const
Returns the threshold that was used for the detection of this finder pattern.
Definition: FinderPatternDetector.h:510
Vector2 orientation_
Dominant orientation of this finder pattern.
Definition: FinderPatternDetector.h:182
bool isNormalReflectance() const
Returns whether this finder pattern is of normal reflectance.
Definition: FinderPatternDetector.h:548
static bool comesBefore(const FinderPattern &first, const FinderPattern &second)
Comparator to sort finder patterns based on their location in an image Pattern a comes before pattern...
Definition: FinderPatternDetector.h:553
Scalar symmetryScore_
The symmetry score of this finder pattern, range: [0, infinity) (lower score = higher symmetry)
Definition: FinderPatternDetector.h:173
Vector2 position_
The (center) position of the finder pattern within the camera frame.
Definition: FinderPatternDetector.h:161
Scalar moduleSize_
Module width (bit width) in pixels.
Definition: FinderPatternDetector.h:185
FinderPattern()
Creates an invalid finder pattern object.
Definition: FinderPatternDetector.h:442
Scalar length_
The edge length of the finder pattern in pixels, range: (0, infinity).
Definition: FinderPatternDetector.h:164
unsigned int grayThreshold_
The threshold that was used during the detection of this finder pattern.
Definition: FinderPatternDetector.h:170
unsigned int centerIntensity_
The intensity value that has been measured in the center of the finder pattern.
Definition: FinderPatternDetector.h:167
Scalar moduleSize() const
Returns the width of a module (= bit) in pixels.
Definition: FinderPatternDetector.h:515
unsigned int centerIntensity() const
Returns the intensity value that was measured in the center of the finder pattern.
Definition: FinderPatternDetector.h:505
static bool isBlack(const T &intensityValue, const T &threshold)
Determines whether an intensity value is black according to threshold value.
Definition: TransitionDetector.h:181
This class implements an infinite line in 2D space.
Definition: Line2.h:83
T sqrDistance(const VectorT2< T > &point) const
Returns the square distance between the line and a given point.
Definition: Line2.h:498
This class implements a recursive lock object.
Definition: Lock.h:31
This class provides basic numeric functionalities.
Definition: Numeric.h:57
static constexpr T deg2rad(const T deg)
Converts deg to rad.
Definition: Numeric.h:3232
static T abs(const T value)
Returns the absolute value of a given value.
Definition: Numeric.h:1220
static constexpr bool isEqualEps(const T value)
Returns whether a value is smaller than or equal to a small epsilon.
Definition: Numeric.h:2087
static T cos(const T value)
Returns the cosine of a given value.
Definition: Numeric.h:1584
const T & x() const noexcept
Returns the x value.
Definition: Vector2.h:698
const T & y() const noexcept
Returns the y value.
Definition: Vector2.h:710
VectorT2< T > normalized() const
Returns the normalized vector.
Definition: Vector2.h:558
VectorT2< T > normalizedOrZero() const
Returns the normalized vector.
Definition: Vector2.h:572
T length() const
Returns the length of the vector.
Definition: Vector2.h:615
This class implements a worker able to distribute function calls over different threads.
Definition: Worker.h:33
unsigned int sqrDistance(const char first, const char second)
Returns the square distance between two values.
Definition: base/Utilities.h:1089
std::array< FinderPattern, 3 > FinderPatternTriplet
Definition of a 3-tuple of finder patterns.
Definition: FinderPatternDetector.h:198
std::vector< FinderPattern > FinderPatterns
Definition of a vector holding finder pattern.
Definition: FinderPatternDetector.h:192
std::vector< IndexTriplet > IndexTriplets
Definition of a vector index triplets.
Definition: FinderPatternDetector.h:51
std::array< unsigned int, 3 > IndexTriplet
Definition of a triplet of indices.
Definition: FinderPatternDetector.h:45
float Scalar
Definition of a scalar type.
Definition: Math.h:128
LineT2< Scalar > Line2
Definition of the Line2 object, depending on the OCEAN_MATH_USE_SINGLE_PRECISION either with single o...
Definition: Line2.h:21
VectorT2< Scalar > Vector2
Definition of a 2D vector.
Definition: Vector2.h:21
std::vector< QRCode > QRCodes
Definition of a vector of QR codes.
Definition: QRCode.h:25
The namespace covering the entire Ocean framework.
Definition: Accessor.h:15