diff --git a/modules/imgproc/src/convhull.cpp b/modules/imgproc/src/convhull.cpp index ed7b5a4a52..0340870397 100644 --- a/modules/imgproc/src/convhull.cpp +++ b/modules/imgproc/src/convhull.cpp @@ -219,9 +219,9 @@ void convexHull( InputArray _points, OutputArray _hull, bool clockwise, bool ret } for( i = 0; i < tl_count-1; i++ ) - hullbuf[nout++] = int(pointer[tl_stack[i]] - data0); + hullbuf[nout++] = tl_stack[i]; for( i = tr_count - 1; i > 0; i-- ) - hullbuf[nout++] = int(pointer[tr_stack[i]] - data0); + hullbuf[nout++] = tr_stack[i]; int stop_idx = tr_count > 2 ? tr_stack[1] : tl_count > 2 ? tl_stack[tl_count - 2] : -1; // lower half @@ -257,9 +257,43 @@ void convexHull( InputArray _points, OutputArray _hull, bool clockwise, bool ret } for( i = 0; i < bl_count-1; i++ ) - hullbuf[nout++] = int(pointer[bl_stack[i]] - data0); + hullbuf[nout++] = bl_stack[i]; for( i = br_count-1; i > 0; i-- ) - hullbuf[nout++] = int(pointer[br_stack[i]] - data0); + hullbuf[nout++] = br_stack[i]; + + if (!returnPoints) + { + // Try keep monotonous indices in case of self-intersection. + for (i = 0; i < nout; ++i) + { + auto prev = pointer[hullbuf[(i == 0 ? nout : i) - 1]]; + auto next = pointer[hullbuf[(i + 1) % nout]]; + auto cur = pointer[hullbuf[i]]; + if ((prev < cur && cur < next) || (prev > cur && cur > next)) + { + continue; + } + for (int j = hullbuf[i] + 1; j < total; ++j) + { + cur = pointer[j]; + if (*pointer[hullbuf[i]] == *cur) + { + if ((prev < cur && cur < next) || (prev > cur && cur > next)) + { + hullbuf[i] = j; + break; + } + } + else + break; + } + } + + } + for (i = 0; i < nout; ++i) + { + hullbuf[i] = int(pointer[hullbuf[i]] - data0); + } // try to make the convex hull indices form // an ascending or descending sequence by the cyclic diff --git a/modules/imgproc/test/test_convhull.cpp b/modules/imgproc/test/test_convhull.cpp index 24c1604d2a..2d9dab5a57 100644 --- a/modules/imgproc/test/test_convhull.cpp +++ b/modules/imgproc/test/test_convhull.cpp @@ -304,9 +304,11 @@ TEST(Imgproc_ConvexityDefects, ordering_4539) vector hull_ind; vector defects; +#if 0 // deprecated behavior // first, check the original contour as-is, without intermediate fillPoly/drawContours. convexHull(contour_, hull_ind, false, false); EXPECT_THROW( convexityDefects(contour_, hull_ind, defects), cv::Exception ); +#endif int scale = 20; contour_ *= (double)scale; @@ -319,10 +321,12 @@ TEST(Imgproc_ConvexityDefects, ordering_4539) findContours(canvas_gray, contours, noArray(), RETR_LIST, CHAIN_APPROX_SIMPLE); convexHull(contours[0], hull_ind, false, false); +#if 0 // deprecated behavior // the original contour contains self-intersections, // therefore convexHull does not return a monotonous sequence of points // and therefore convexityDefects throws an exception EXPECT_THROW( convexityDefects(contours[0], hull_ind, defects), cv::Exception ); +#endif #if 1 // one way to eliminate the contour self-intersection in this particular case is to apply dilate(), @@ -1283,6 +1287,56 @@ INSTANTIATE_TEST_CASE_P(Imgproc, minAreaRect_of_line, std::make_tuple(Point2f(9, 19), Point2f(4, 7), Point2f(6.5, 13), Size2f(0, 13), -22.6198654f) )); +typedef testing::TestWithParam, Mat>, bool> > convexHull_monotonous; +TEST_P(convexHull_monotonous, self_intersecting_contour) +{ + std::vector contour = get<0>(get<0>(GetParam())); + Mat ref = get<1>(get<0>(GetParam())).clone(); + bool clockwise = get<1>(GetParam()); + if (!clockwise) + { + std::reverse(ref.begin(), ref.end()); + } + + Mat indices; + convexHull(contour, indices, clockwise, false); + + Point minLoc; + minMaxLoc(indices, nullptr, nullptr, &minLoc); + std::rotate(indices.begin(), indices.begin() + minLoc.y, indices.end()); + + minMaxLoc(ref, nullptr, nullptr, &minLoc); + std::rotate(ref.begin(), ref.begin() + minLoc.y, ref.end()); + + ASSERT_EQ( cvtest::norm(indices, ref, NORM_INF), 0) << indices; +} +INSTANTIATE_TEST_CASE_P(Imgproc, convexHull_monotonous, + testing::Combine( + testing::Values( + std::make_tuple( + std::vector{ + Point(3, 2), Point(3, 4), Point(2, 5), Point(1, 5), + Point(2, 5), Point(3, 4), Point(6, 4), Point(6, 2) + }, + (Mat_(5, 1) << 0, 3, 4, 6, 7) + ), + std::make_tuple( + std::vector{ + Point(3, -2), Point(3, -4), Point(2, -5), Point(1, -5), + Point(2, -5), Point(3, -4), Point(6, -4), Point(6, -2) + }, + (Mat_(5, 1) << 3, 0, 7, 6, 4) + ), + std::make_tuple( + std::vector{ + Point(1, 1), Point(1, 0), Point(0, 0), Point(1, 0), Point(0, 1) + }, + (Mat_(4, 1) << 0, 1, 2, 4) + ) + ), + testing::Bool() +)); + }} // namespace /* End of file. */