1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 07:43:03 +04:00

Merge pull request #29329 from JonasPerolini:get-border-errors-once

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
This commit is contained in:
Jonas Perolini
2026-06-18 08:40:20 +02:00
committed by GitHub
parent 837f715eec
commit 853ee9b961
+26 -19
View File
@@ -383,31 +383,41 @@ static Mat _extractCellPixelRatio(InputArray _image, const vector<Point2f>& 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<float>(y);
for(int k = 0; k < borderSize; k++) {
// Left and right vertical sides
if(cellPixelRatio.ptr<float>(y)[k] > validBitIdThreshold) totalErrors++;
if(cellPixelRatio.ptr<float>(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<float>(k)[x] > validBitIdThreshold) totalErrors++;
if(cellPixelRatio.ptr<float>(sizeWithBorders - 1 - k)[x] > validBitIdThreshold) totalErrors++;
countCell(cellPixelRatio.ptr<float>(k)[x]);
countCell(cellPixelRatio.ptr<float>(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<borderErrors){
borderErrors = invBError;
invCellPixelRatio.copyTo(cellPixelRatio);
typ=2;
}
if(params.detectInvertedMarker && invBorderErrors < borderErrors) {
// white marker: invert the observed ratios in place and continue as a normal marker
borderErrors = invBorderErrors;
subtract(Scalar::all(1), cellPixelRatio, cellPixelRatio);
typ=2;
}
if(borderErrors > maximumErrorsInBorder) return 0; // border is wrong