Skip to content

Commit 0596c05

Browse files
buq2asmorkalov
andauthored
Merge pull request #3220 from buq2:aruco-apriltag-infinite-loop-fix
Fix infinite loop on ArUco apriltag refinement * Fix infinite loop on ArUco apriltag refinement Software entered infinite loop when image height was smaller than 10*cv::getNumThreads(). With high core count machines this could happen with very reasonable image sizes. Fix is to ensure that chunksize is at least 1. * Test aruco detection with different number of threads Test ensures that different aruco detection methods do not produce different results based on number of threads. Test was created after observing infinite loop caused by small image and large number of threads when using apriltag corner refinement. * Test refactoring. * Syntax fix for pre-C++11 compilers. Co-authored-by: Alexander Smorkalov <[email protected]>
1 parent 02c6b5e commit 0596c05

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

modules/aruco/src/apriltag_quad_thresh.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,7 @@ out = Mat::zeros(h, w, CV_8UC3);
15231523
zarray_t *quads = _zarray_create(sizeof(struct sQuad));
15241524

15251525
//int chunksize = 1 + sz / (APRILTAG_TASKS_PER_THREAD_TARGET * numberOfThreads);
1526-
int chunksize = h / (10 * getNumThreads());
1526+
int chunksize = std::max(1, h / (10 * getNumThreads()));
15271527
int sz = _zarray_size(clusters);
15281528

15291529
// TODO PARALLELIZE

modules/aruco/test/test_arucodetection.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,4 +717,91 @@ TEST(CV_ArucoDetectMarkers, regression_2492)
717717
}
718718
}
719719

720+
struct ArucoThreading: public testing::TestWithParam<cv::aruco::CornerRefineMethod>
721+
{
722+
struct NumThreadsSetter {
723+
NumThreadsSetter(const int num_threads)
724+
: original_num_threads_(cv::getNumThreads()) {
725+
cv::setNumThreads(num_threads);
726+
}
727+
728+
~NumThreadsSetter() {
729+
cv::setNumThreads(original_num_threads_);
730+
}
731+
private:
732+
int original_num_threads_;
733+
};
734+
};
735+
736+
TEST_P(ArucoThreading, number_of_threads_does_not_change_results)
737+
{
738+
cv::Ptr<cv::aruco::DetectorParameters> params = cv::aruco::DetectorParameters::create();
739+
// We are not testing against different dictionaries
740+
// As we are interested mostly in small images, smaller
741+
// markers is better -> 4x4
742+
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50);
743+
744+
// Height of the test image can be chosen quite freely
745+
// We aim to test against small images as in those the
746+
// number of threads has most effect
747+
const int height_img = 20;
748+
// Just to get nice white boarder
749+
const int shift = height_img > 10 ? 5 : 1;
750+
const int height_marker = height_img-2*shift;
751+
752+
// Create a test image
753+
cv::Mat img_marker;
754+
cv::aruco::drawMarker(dictionary, 23, height_marker, img_marker, 1);
755+
756+
// Copy to bigger image to get a white border
757+
cv::Mat img(height_img, height_img, CV_8UC1, cv::Scalar(255));
758+
img_marker.copyTo(img(cv::Rect(shift, shift, height_marker, height_marker)));
759+
760+
params->cornerRefinementMethod = GetParam();
761+
762+
std::vector<std::vector<cv::Point2f> > original_corners;
763+
std::vector<int> original_ids;
764+
{
765+
NumThreadsSetter thread_num_setter(1);
766+
cv::aruco::detectMarkers(img, dictionary, original_corners, original_ids, params);
767+
}
768+
769+
ASSERT_EQ(original_ids.size(), 1);
770+
ASSERT_EQ(original_corners.size(), 1);
771+
772+
int num_threads_to_test[] = { 2, 8, 16, 32, height_img-1, height_img, height_img+1};
773+
774+
for (size_t i_num_threads = 0; i_num_threads < sizeof(num_threads_to_test)/sizeof(int); ++i_num_threads) {
775+
NumThreadsSetter thread_num_setter(num_threads_to_test[i_num_threads]);
776+
777+
std::vector<std::vector<cv::Point2f> > corners;
778+
std::vector<int> ids;
779+
cv::aruco::detectMarkers(img, dictionary, corners, ids, params);
780+
781+
// If we don't find any markers, the test is broken
782+
ASSERT_EQ(ids.size(), 1);
783+
784+
// Make sure we got the same result as the first time
785+
ASSERT_EQ(corners.size(), original_corners.size());
786+
ASSERT_EQ(ids.size(), original_ids.size());
787+
ASSERT_EQ(ids.size(), corners.size());
788+
for (size_t i = 0; i < corners.size(); ++i) {
789+
EXPECT_EQ(ids[i], original_ids[i]);
790+
for (size_t j = 0; j < corners[i].size(); ++j) {
791+
EXPECT_NEAR(corners[i][j].x, original_corners[i][j].x, 0.1f);
792+
EXPECT_NEAR(corners[i][j].y, original_corners[i][j].y, 0.1f);
793+
}
794+
}
795+
}
796+
}
797+
798+
INSTANTIATE_TEST_CASE_P(
799+
CV_ArucoDetectMarkers, ArucoThreading,
800+
::testing::Values(
801+
cv::aruco::CORNER_REFINE_NONE,
802+
cv::aruco::CORNER_REFINE_SUBPIX,
803+
cv::aruco::CORNER_REFINE_CONTOUR,
804+
cv::aruco::CORNER_REFINE_APRILTAG
805+
));
806+
720807
}} // namespace

0 commit comments

Comments
 (0)