From fe160f3eed3ec0344baff4bfb6a0771d01b5882d Mon Sep 17 00:00:00 2001 From: Anshu Date: Wed, 4 Mar 2026 17:46:07 +0530 Subject: [PATCH] Merge pull request #28548 from 0AnshuAditya0:fix-minEnclosingCircle-welzl-28546 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit imgproc: fix minEnclosingCircle O(n^3) worst case by adding Welzl shuffle #28548 Welzl's algorithm requires random permutation of input points to achieve expected O(n) time. Without shuffling, sorted inputs such as those produced by findContours() trigger O(n^3) worst case. Fix: copy input to std::vector and apply cv::randShuffle() before processing. Uses OpenCV's RNG so cv::setRNGSeed() ensures reproducible behavior. Original benchmark (5088 contour points, Release, AVX2): findContours output: 3.93 ms → 0.033 ms (119x speedup) Random points: 0.051 ms → 0.049 ms (no regression) Perf test results (this PR, Release, AVX2): | Input | N | Time | |-------|---|------| | Sequential circle points | 10000 | 0.03 ms | | Sequential circle points | 5000 | 0.01 ms | | Random points (CV_32F) | 100000 | 1.87 ms | | Random points (CV_32S) | 100000 | 2.81 ms | Fixes #28546 ### 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 - [x] The PR is proposed to the proper branch - [x] 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 --- modules/imgproc/perf/perf_contours.cpp | 35 ++++++++++++++++++++++++++ modules/imgproc/src/shapedescr.cpp | 25 +++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/modules/imgproc/perf/perf_contours.cpp b/modules/imgproc/perf/perf_contours.cpp index bc8b530016..8fabdb334b 100644 --- a/modules/imgproc/perf/perf_contours.cpp +++ b/modules/imgproc/perf/perf_contours.cpp @@ -106,4 +106,39 @@ PERF_TEST_P(TestBoundingRect, BoundingRect, SANITY_CHECK_NOTHING(); } +typedef TestBaseWithParam< tuple > TestMinEnclosingCircle; +PERF_TEST_P(TestMinEnclosingCircle, minEnclosingCircle, + Combine( + testing::Values(CV_32S, CV_32F), + Values(400, 1000, 10000, 100000) + )) +{ + int ptType = get<0>(GetParam()); + int n = get<1>(GetParam()); + Mat pts(n, 2, ptType); + declare.in(pts, WARMUP_RNG); + + Point2f center; + float radius; + TEST_CYCLE() minEnclosingCircle(pts, center, radius); + SANITY_CHECK_NOTHING(); +} + +typedef TestBaseWithParam TestMinEnclosingCircleWorstCase; +PERF_TEST_P(TestMinEnclosingCircleWorstCase, minEnclosingCircle_sequential, + Values(400, 1000, 5000, 10000)) +{ + int n = GetParam(); + vector contour; + for(int i = 0; i < n; ++i) { + float angle = (float)(i * 2 * CV_PI / n); + contour.push_back(Point2f(cos(angle) * 100, sin(angle) * 100)); + } + + Point2f center; + float radius; + TEST_CYCLE() minEnclosingCircle(contour, center, radius); + SANITY_CHECK_NOTHING(); +} + } } // namespace diff --git a/modules/imgproc/src/shapedescr.cpp b/modules/imgproc/src/shapedescr.cpp index a713cfeed4..6ddd855e32 100644 --- a/modules/imgproc/src/shapedescr.cpp +++ b/modules/imgproc/src/shapedescr.cpp @@ -130,8 +130,31 @@ void findSecondPoint(const PT *pts, int i, Point2f ¢er, float &radius) template -static void findMinEnclosingCircle(const PT *pts, int count, Point2f ¢er, float &radius) +static void findMinEnclosingCircle(const PT *pts_in, int count, Point2f ¢er, float &radius) { + // Welzl's algorithm requires random permutation for expected O(n) time. + // Without shuffling, sorted inputs trigger O(n^3) worst case. + cv::AutoBuffer pts_buf(count); + std::copy(pts_in, pts_in + count, pts_buf.data()); + PT* pts = pts_buf.data(); + if (count > 10) + { + Cv32suf x0, y0, xn, yn; + x0.f = (float)pts[0].x; + y0.f = (float)pts[0].y; + xn.f = (float)pts[count-1].x; + yn.f = (float)pts[count-1].y; + + uint32_t seed = (uint32_t)count ^ x0.u ^ (y0.u << 8) ^ (xn.u << 16) ^ (yn.u << 24); + cv::RNG rng(seed); + + for (int i = 1; i < count; ++i) + { + int j = rng.uniform(0, i + 1); + std::swap(pts[i], pts[j]); + } + } + center.x = (float)(pts[0].x + pts[1].x) / 2.0f; center.y = (float)(pts[0].y + pts[1].y) / 2.0f; float dx = (float)(pts[0].x - pts[1].x);