Skip to content

fixed bad shape of markers (1x4) in several cases and added tests #3105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

AleksandrPanov
Copy link
Contributor

@AleksandrPanov AleksandrPanov commented Nov 11, 2021

Merge with extra: opencv/opencv_extra#935

Fixes opencv/opencv#14014
Problem:

  • detectMarkers() and refineDetectedMarkers() returns OutputArrayOfArrays corners and rejectedCorners with different dimensions (for similar cases).
  • This problem is only relevant for vector input.
  • In this case detectMarkers() always returns corners and rejectedCorners as vector<Mat>, with Mat dimension 4x1.
  • In this case refineDetectedMarkers() works differently, if it finds a new marker. Then the dimension of Mats inside the vector<Mat> changes from 4x1 to 1x4.
  • The reason for the problem is that detectMarkers() and refineDetectedMarkers() create corners and rejectedCorners with different ways.
  • Current tests show no errors because they use vector<vector<Point2f> > for corners and rejectedCorners. Also, the current tests only check the number of refined corners (dimension and value are not checked).

In this PR refineDetectedMarkers() for create OutputArrayOfArrays corners and rejectedCorners uses _copyVector2Output(), just like detectMarkers().

opencv_extra=fix_refineDetectedMarkers_shape

Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on a code under GPL or other license that is incompatible with OpenCV
  • The PR is proposed to proper branch
  • There is reference to 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

@@ -1563,7 +1563,7 @@ void refineDetectedMarkers(InputArray _image, const Ptr<Board> &_board,

_detectedCorners.create((int)finalAcceptedCorners.size(), 1, CV_32FC2);
for(unsigned int i = 0; i < finalAcceptedCorners.size(); i++) {
_detectedCorners.create(4, 1, CV_32FC2, i, true);
_detectedCorners.create(1, 4, CV_32FC2, i, true);
Copy link
Member

@alalek alalek Nov 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Below we have 4,1 again: _rejectedCorners.create(4, 1, ...)


BTW, all usages from aruco module:

$ grep -Rn '.create(4, 1,' ./modules/aruco/
./modules/aruco/src/aruco.cpp:584:            out.create(4, 1, CV_32FC2, i);
./modules/aruco/src/aruco.cpp:591:            out.create(4, 1, CV_32FC2, i);
./modules/aruco/src/aruco.cpp:598:            out.create(4, 1, CV_32FC2, i);
./modules/aruco/src/aruco.cpp:696:    _objPoints.create(4, 1, CV_32FC3);
./modules/aruco/src/aruco.cpp:1402:            _detectedCorners.create(4, 1, CV_32FC2, i, true);
./modules/aruco/src/aruco.cpp:1420:            _rejectedCorners.create(4, 1, CV_32FC2, i, true);
./modules/aruco/src/charuco.cpp:822:            _diamondCorners.create(4, 1, CV_32FC2, i, true);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is surprising that the dimension of the matrices out[i], _objPoints[i], _detectedCorners[i], _rejectedCorners[i], _diamondCorners[i] is: (1 (rows) , 4 (cols)). But create with (4 (rows), 1 (cols)) was used.
why did m's size change?
Flag allowTransposed is ignored in this case (same values for true/false).
Looks like a bug in void _OutputArray::create(int _rows, int _cols, int mtype, int i, bool allowTransposed, int fixedDepthMask) const.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such changes affecting API are considered as a "breaking change".

So proper and complete change should have:

  • clear justification
  • consistent solution (fix 1 place only and left 6 others is not a good way)
  • adapt coding rules to avoid such/similar problems in the future.

surprising

There is allowTransposed parameter on this line.

Copy link
Contributor Author

@AleksandrPanov AleksandrPanov Feb 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strange code change is removed. Now refineDetectedMarkers() for create OutputArrayOfArrays corners and rejectedCorners uses _copyVector2Output(), just like detectMarkers().

It's strange that before detectMarkers() and refineDetectedMarkers() created OutputArrayOfArrays corners and rejectedCorners in different ways.


aruco::detectMarkers(img, dico, corners, ids, detectorParams, rejectedPoints);
ASSERT_FALSE(corners.empty());
EXPECT_EQ(Size(4, 1), corners[0].size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EXPECT_EQ(Size(4, 1), corners[0].size());

Why do you think that Size(1, 4) is wrong?

Copy link
Contributor Author

@AleksandrPanov AleksandrPanov Nov 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size(1, 4) is not wrong (because it's just four 2d-points) , but corners[i] must be the same size in all cases to avoid undefined behavior.
I also think that InputOutputArrayOfArrays should not be used to pass array of pointers. You suggested replacing the InputOutputArrayOfArrays with a InputOutputArray with ndarray shape [npoints, 4, 2]. I'll try to do that.

Copy link
Contributor Author

@AleksandrPanov AleksandrPanov Nov 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me clarify:
In all tests and all examples in OpenCV detectMarkers and refineDetectedMarkers return corners with Size(4, 1) (for corners[i]). It is strange if this condition is violated for no reason.

@AleksandrPanov AleksandrPanov force-pushed the fix_refineDetectedMarkers_shape branch from 211d636 to d72a91f Compare February 1, 2022 16:16
@AleksandrPanov AleksandrPanov marked this pull request as ready for review February 7, 2022 11:36
@AleksandrPanov
Copy link
Contributor Author

AleksandrPanov commented Feb 9, 2022

@alalek, could you recheck PR? I changed the description of the PR. Code changes are made more clear. PR description has been improved.

Comment on lines -1600 to +1611
_detectedCorners.clear();
_detectedIds.clear();

// parse output
Mat(finalAcceptedIds).copyTo(_detectedIds);

_detectedCorners.create((int)finalAcceptedCorners.size(), 1, CV_32FC2);
for(unsigned int i = 0; i < finalAcceptedCorners.size(); i++) {
_detectedCorners.create(4, 1, CV_32FC2, i, true);
for(int j = 0; j < 4; j++) {
_detectedCorners.getMat(i).ptr< Point2f >()[j] =
finalAcceptedCorners[i].ptr< Point2f >()[j];
}
}
_copyVector2Output(finalAcceptedCorners, _detectedCorners);

// recalculate _rejectedCorners based on alreadyIdentified
vector< Mat > finalRejected;
vector<vector<Point2f> > finalRejected;
for(unsigned int i = 0; i < alreadyIdentified.size(); i++) {
if(!alreadyIdentified[i]) {
finalRejected.push_back(_rejectedCorners.getMat(i).clone());
}
}

_rejectedCorners.clear();
_rejectedCorners.create((int)finalRejected.size(), 1, CV_32FC2);
for(unsigned int i = 0; i < finalRejected.size(); i++) {
_rejectedCorners.create(4, 1, CV_32FC2, i, true);
for(int j = 0; j < 4; j++) {
_rejectedCorners.getMat(i).ptr< Point2f >()[j] =
finalRejected[i].ptr< Point2f >()[j];
}
}
_copyVector2Output(finalRejected, _rejectedCorners);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, refineDetectedMarkers() uses _copyVector2Output() to copy, just as detectMarkers() does.

@AleksandrPanov AleksandrPanov removed their assignment Feb 9, 2022
Copy link
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

refineDetectedMarkers return different arrays depending on if new markers were found
3 participants