From 853ee9b96114f279630508b9f46be8ded6e8865e Mon Sep 17 00:00:00 2001 From: Jonas Perolini <74473718+JonasPerolini@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:40:20 +0200 Subject: [PATCH] Merge pull request #29329 from JonasPerolini:get-border-errors-once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimization ArUco: get border errors in one pass for both normal and inverted markers #29329 This PR is a follow up to optimization in https://github.com/opencv/opencv/pull/29267. ## Context: When detecting inverted markers, we call `_getBorderErrors` twice: - `int borderErrors = _getBorderErrors(cellPixelRatio, ...);` - `Mat invCellPixelRatio = 1.f - cellPixelRatio;` - `int invBError = _getBorderErrors(invCellPixelRatio, ...);` meaning: 1. Scan border cells once. 2. Allocate a new Mat. 3. Invert the entire `cellPixelRatio` matrix, not just the border. 4. Scan border cells again on the inverted matrix. 5. If inverted marker is better, copy the full inverted matrix back ## Solution only call `_getBorderErrors` once and compute both - borderErrors: `cellPixelRatio > validBitIdThreshold` - invBorderErrors: `1 - cellPixelRatio > validBitIdThreshold` This saves: 1. full invert into temporary Mat: (`Mat invCellPixelRatio = 1.f - cellPixelRatio;`) 2. scan temporary border: the second `_getBorderErrors` 3. copy temporary Mat back into `cellPixelRatio`: `invCellPixelRatio.copyTo(cellPixelRatio);` Note that the solution does not implement: ``` if(params.detectInvertedMarker) { _getBorderErrorsBoth(...); } else { borderErrors = _getBorderErrors(...); } ``` because the extra cost to computing invBorderErrors: `if(1.f - ratio > validBitIdThreshold) invBorderErrors++;` is negligible. this also avoids having to maintain two functions: `_getBorderErrorsBoth` and `_getBorderErrors` ## Tests: No visible results when testing the full marker detection pipeline because this step is not expensive. There is a roughly 8% noise when re-running the marker detections making it hard to spot small speed diffs. When testing only this specific step with a float matrix of size: `(markerSize + 2*border)^2` on a AMD Ryzen 7 (8 cores / 16 threads): - The new code is 30-45× faster at this stage depending on the marker size and whether the candidate was actually an inverted one, saving ~530-580 ns per candidate, mainly because we removed `Mat invCellPixelRatio = 1.f - cellPixelRatio` - The new code is equivalent when detecting non-inverted markers +/- 3 ns --- ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [ ] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- .../objdetect/src/aruco/aruco_detector.cpp | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/modules/objdetect/src/aruco/aruco_detector.cpp b/modules/objdetect/src/aruco/aruco_detector.cpp index 434e709810..819d78db10 100644 --- a/modules/objdetect/src/aruco/aruco_detector.cpp +++ b/modules/objdetect/src/aruco/aruco_detector.cpp @@ -383,31 +383,41 @@ static Mat _extractCellPixelRatio(InputArray _image, const vector& corn /** - * @brief Return number of erroneous bits in border, i.e. bits for which pixel ratio > validBitIdThreshold. + * @brief Return number of erroneous bits in border. + * + * Black border error if cellPixelRatio > validBitIdThreshold -> borderErrors + * White border error if 1 - cellPixelRatio > validBitIdThreshold + * <=> cellPixelRatio < 1 - validBitIdThreshold) -> invBorderErrors (inverted markers) */ - static int _getBorderErrors(const Mat &cellPixelRatio, int markerSize, int borderSize, float validBitIdThreshold) { +static void _getBorderErrors(const Mat &cellPixelRatio, int markerSize, int borderSize, float validBitIdThreshold, + int &borderErrors, int &invBorderErrors) { int sizeWithBorders = markerSize + 2 * borderSize; CV_Assert(markerSize > 0 && cellPixelRatio.cols == sizeWithBorders && cellPixelRatio.rows == sizeWithBorders); // Get border error. cellPixelRatio has the opposite color as the borders. - int totalErrors = 0; + const float invThreshold = 1.f - validBitIdThreshold; + borderErrors = invBorderErrors = 0; + const auto countCell = [&](float ratio) { + if(ratio > validBitIdThreshold) borderErrors++; + if(ratio < invThreshold) invBorderErrors++; + }; for(int y = 0; y < sizeWithBorders; y++) { + const float* row = cellPixelRatio.ptr(y); for(int k = 0; k < borderSize; k++) { // Left and right vertical sides - if(cellPixelRatio.ptr(y)[k] > validBitIdThreshold) totalErrors++; - if(cellPixelRatio.ptr(y)[sizeWithBorders - 1 - k] > validBitIdThreshold) totalErrors++; + countCell(row[k]); + countCell(row[sizeWithBorders - 1 - k]); } } for(int x = borderSize; x < sizeWithBorders - borderSize; x++) { for(int k = 0; k < borderSize; k++) { // Top and bottom horizontal sides - if(cellPixelRatio.ptr(k)[x] > validBitIdThreshold) totalErrors++; - if(cellPixelRatio.ptr(sizeWithBorders - 1 - k)[x] > validBitIdThreshold) totalErrors++; + countCell(cellPixelRatio.ptr(k)[x]); + countCell(cellPixelRatio.ptr(sizeWithBorders - 1 - k)[x]); } } - return totalErrors; } @@ -486,19 +496,16 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i // analyze border bits int maximumErrorsInBorder = int(dictionary.markerSize * dictionary.markerSize * params.maxErroneousBitsInBorderRate); - int borderErrors = - _getBorderErrors(cellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold); + int borderErrors = 0, invBorderErrors = 0; + _getBorderErrors(cellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold, + borderErrors, invBorderErrors); // check if it is a white marker - if(params.detectInvertedMarker){ - Mat invCellPixelRatio = 1.f - cellPixelRatio; - int invBError = _getBorderErrors(invCellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold); - // white marker - if(invBError maximumErrorsInBorder) return 0; // border is wrong