mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 15:53: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:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user