From 8f3867756c0f17d2ffeed2c29cc173fbb15b81d1 Mon Sep 17 00:00:00 2001 From: Vadim Pisarevsky Date: Fri, 21 Feb 2020 18:18:24 +0300 Subject: [PATCH] Merge pull request #16594 from vpisarev:hull_ordering_fix fixed the ordering of contour convex hull points * partially fixed the issue #4539 * fixed warnings and test failures * fixed integer overflow (issue #14521) * added comment to force buildbot to re-run * extended the test for the issue 4539. Check the expected behaviour on the original contour as well * added comment; fixed typo, renamed another variable for a little better clarity * added yet another part to the test for issue #4539, where we run convexHull and convexityDetects on the original contour, without any manipulations. the rest of the test stays the same --- .../imgproc/misc/java/test/ImgprocTest.java | 2 +- modules/imgproc/src/convhull.cpp | 77 +++++++-- modules/imgproc/test/test_convhull.cpp | 152 ++++++++++++++++++ 3 files changed, 219 insertions(+), 12 deletions(-) diff --git a/modules/imgproc/misc/java/test/ImgprocTest.java b/modules/imgproc/misc/java/test/ImgprocTest.java index 52da455993..cc664c8002 100644 --- a/modules/imgproc/misc/java/test/ImgprocTest.java +++ b/modules/imgproc/misc/java/test/ImgprocTest.java @@ -427,7 +427,7 @@ public class ImgprocTest extends OpenCVTestCase { Imgproc.convexHull(points, hull); MatOfInt expHull = new MatOfInt( - 1, 2, 3, 0 + 0, 1, 2, 3 ); assertMatEqual(expHull, hull, EPS); } diff --git a/modules/imgproc/src/convhull.cpp b/modules/imgproc/src/convhull.cpp index e288f6a626..b964ca3f62 100644 --- a/modules/imgproc/src/convhull.cpp +++ b/modules/imgproc/src/convhull.cpp @@ -45,7 +45,7 @@ namespace cv { -template +template static int Sklansky_( Point_<_Tp>** array, int start, int end, int* stack, int nsign, int sign2 ) { int incr = end > start ? 1 : -1; @@ -79,7 +79,7 @@ static int Sklansky_( Point_<_Tp>** array, int start, int end, int* stack, int n _Tp ax = array[pcur]->x - array[pprev]->x; _Tp bx = array[pnext]->x - array[pcur]->x; _Tp ay = cury - array[pprev]->y; - _Tp convexity = ay*bx - ax*by; // if >0 then convex angle + _DotTp convexity = (_DotTp)ay*bx - (_DotTp)ax*by; // if >0 then convex angle if( CV_SIGN( convexity ) == sign2 && (ax != 0 || ay != 0) ) { @@ -122,7 +122,13 @@ template struct CHullCmpPoints { bool operator()(const Point_<_Tp>* p1, const Point_<_Tp>* p2) const - { return p1->x < p2->x || (p1->x == p2->x && p1->y < p2->y); } + { + if( p1->x != p2->x ) + return p1->x < p2->x; + if( p1->y != p2->y ) + return p1->y < p2->y; + return p1 < p2; + } }; @@ -194,12 +200,12 @@ void convexHull( InputArray _points, OutputArray _hull, bool clockwise, bool ret // upper half int *tl_stack = stack; int tl_count = !is_float ? - Sklansky_( pointer, 0, maxy_ind, tl_stack, -1, 1) : - Sklansky_( pointerf, 0, maxy_ind, tl_stack, -1, 1); + Sklansky_( pointer, 0, maxy_ind, tl_stack, -1, 1) : + Sklansky_( pointerf, 0, maxy_ind, tl_stack, -1, 1); int *tr_stack = stack + tl_count; int tr_count = !is_float ? - Sklansky_( pointer, total-1, maxy_ind, tr_stack, -1, -1) : - Sklansky_( pointerf, total-1, maxy_ind, tr_stack, -1, -1); + Sklansky_( pointer, total-1, maxy_ind, tr_stack, -1, -1) : + Sklansky_( pointerf, total-1, maxy_ind, tr_stack, -1, -1); // gather upper part of convex hull to output if( !clockwise ) @@ -217,12 +223,12 @@ void convexHull( InputArray _points, OutputArray _hull, bool clockwise, bool ret // lower half int *bl_stack = stack; int bl_count = !is_float ? - Sklansky_( pointer, 0, miny_ind, bl_stack, 1, -1) : - Sklansky_( pointerf, 0, miny_ind, bl_stack, 1, -1); + Sklansky_( pointer, 0, miny_ind, bl_stack, 1, -1) : + Sklansky_( pointerf, 0, miny_ind, bl_stack, 1, -1); int *br_stack = stack + bl_count; int br_count = !is_float ? - Sklansky_( pointer, total-1, miny_ind, br_stack, 1, 1) : - Sklansky_( pointerf, total-1, miny_ind, br_stack, 1, 1); + Sklansky_( pointer, total-1, miny_ind, br_stack, 1, 1) : + Sklansky_( pointerf, total-1, miny_ind, br_stack, 1, 1); if( clockwise ) { @@ -250,6 +256,45 @@ void convexHull( InputArray _points, OutputArray _hull, bool clockwise, bool ret hullbuf[nout++] = int(pointer[bl_stack[i]] - data0); for( i = br_count-1; i > 0; i-- ) hullbuf[nout++] = int(pointer[br_stack[i]] - data0); + + // try to make the convex hull indices form + // an ascending or descending sequence by the cyclic + // shift of the output sequence. + if( nout >= 3 ) + { + int min_idx = 0, max_idx = 0, lt = 0; + for( i = 1; i < nout; i++ ) + { + int idx = hullbuf[i]; + lt += hullbuf[i-1] < idx; + if( lt > 1 && lt <= i-2 ) + break; + if( idx < hullbuf[min_idx] ) + min_idx = i; + if( idx > hullbuf[max_idx] ) + max_idx = i; + } + int mmdist = std::abs(max_idx - min_idx); + if( (mmdist == 1 || mmdist == nout-1) && (lt <= 1 || lt >= nout-2) ) + { + int ascending = (max_idx + 1) % nout == min_idx; + int i0 = ascending ? min_idx : max_idx, j = i0; + if( i0 > 0 ) + { + for( i = 0; i < nout; i++ ) + { + int curr_idx = stack[i] = hullbuf[j]; + int next_j = j+1 < nout ? j+1 : 0; + int next_idx = hullbuf[next_j]; + if( i < nout-1 && (ascending != (curr_idx < next_idx)) ) + break; + j = next_j; + } + if( i == nout ) + memcpy(hullbuf, stack, nout*sizeof(hullbuf[0])); + } + } + } } if( !returnPoints ) @@ -299,12 +344,22 @@ void convexityDefects( InputArray _points, InputArray _hull, OutputArray _defect int hcurr = hptr[rev_orientation ? 0 : hpoints-1]; CV_Assert( 0 <= hcurr && hcurr < npoints ); + int increasing_idx = -1; + for( i = 0; i < hpoints; i++ ) { int hnext = hptr[rev_orientation ? hpoints - i - 1 : i]; CV_Assert( 0 <= hnext && hnext < npoints ); Point pt0 = ptr[hcurr], pt1 = ptr[hnext]; + if( increasing_idx < 0 ) + increasing_idx = !(hcurr < hnext); + else if( increasing_idx != (hcurr < hnext)) + { + CV_Error(Error::StsBadArg, + "The convex hull indices are not monotonous, which can be in the case when the input contour contains self-intersections"); + } + double dx0 = pt1.x - pt0.x; double dy0 = pt1.y - pt0.y; double scale = dx0 == 0 && dy0 == 0 ? 0. : 1./std::sqrt(dx0*dx0 + dy0*dy0); diff --git a/modules/imgproc/test/test_convhull.cpp b/modules/imgproc/test/test_convhull.cpp index fc29b7fbb5..5e353286fe 100644 --- a/modules/imgproc/test/test_convhull.cpp +++ b/modules/imgproc/test/test_convhull.cpp @@ -2154,5 +2154,157 @@ TEST(Imgproc_FitLine, regression_4903) EXPECT_GE(fabs(lineParam[1]), fabs(lineParam[0]) * 4) << lineParam; } +#if 0 +#define DRAW(x) x +#else +#define DRAW(x) +#endif + +// the Python test by @hannarud is converted to C++; see the issue #4539 +TEST(Imgproc_ConvexityDefects, ordering_4539) +{ + int contour[][2] = + { + {26, 9}, {25, 10}, {24, 10}, {23, 10}, {22, 10}, {21, 10}, {20, 11}, {19, 11}, {18, 11}, {17, 12}, + {17, 13}, {18, 14}, {18, 15}, {18, 16}, {18, 17}, {19, 18}, {19, 19}, {20, 20}, {21, 21}, {21, 22}, + {22, 23}, {22, 24}, {23, 25}, {23, 26}, {24, 27}, {25, 28}, {26, 29}, {27, 30}, {27, 31}, {28, 32}, + {29, 32}, {30, 33}, {31, 34}, {30, 35}, {29, 35}, {30, 35}, {31, 34}, {32, 34}, {33, 34}, {34, 33}, + {35, 32}, {35, 31}, {35, 30}, {36, 29}, {37, 28}, {37, 27}, {38, 26}, {39, 25}, {40, 24}, {40, 23}, + {41, 22}, {42, 21}, {42, 20}, {42, 19}, {43, 18}, {43, 17}, {44, 16}, {45, 15}, {45, 14}, {46, 13}, + {46, 12}, {45, 11}, {44, 11}, {43, 11}, {42, 10}, {41, 10}, {40, 9}, {39, 9}, {38, 9}, {37, 9}, + {36, 9}, {35, 9}, {34, 9}, {33, 9}, {32, 9}, {31, 9}, {30, 9}, {29, 9}, {28, 9}, {27, 9} + }; + int npoints = (int)(sizeof(contour)/sizeof(contour[0][0])/2); + Mat contour_(1, npoints, CV_32SC2, contour); + vector hull; + vector hull_ind; + vector defects; + + // 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 ); + + int scale = 20; + contour_ *= (double)scale; + + Mat canvas_gray(Size(60*scale, 45*scale), CV_8U, Scalar::all(0)); + const Point* ptptr = contour_.ptr(); + fillPoly(canvas_gray, &ptptr, &npoints, 1, Scalar(255, 255, 255)); + + vector > contours; + findContours(canvas_gray, contours, noArray(), RETR_LIST, CHAIN_APPROX_SIMPLE); + convexHull(contours[0], hull_ind, false, false); + + // 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 ); + +#if 1 + // one way to eliminate the contour self-intersection in this particular case is to apply dilate(), + // so that the self-repeating points are not self-repeating anymore + dilate(canvas_gray, canvas_gray, Mat()); +#else + // another popular technique to eliminate such thin "hair" is to use morphological "close" operation, + // which is erode() + dilate() + erode(canvas_gray, canvas_gray, Mat()); + dilate(canvas_gray, canvas_gray, Mat()); +#endif + + // after the "fix", the newly retrieved contour should not have self-intersections, + // and everything should work well + findContours(canvas_gray, contours, noArray(), RETR_LIST, CHAIN_APPROX_SIMPLE); + convexHull(contours[0], hull, false, true); + convexHull(contours[0], hull_ind, false, false); + + DRAW(Mat canvas(Size(60*scale, 45*scale), CV_8UC3, Scalar::all(0)); + drawContours(canvas, contours, -1, Scalar(255, 255, 255), -1)); + + size_t nhull = hull.size(); + ASSERT_EQ( nhull, hull_ind.size() ); + + if( nhull > 2 ) + { + bool initial_lt = hull_ind[0] < hull_ind[1]; + for( size_t i = 0; i < nhull; i++ ) + { + int ind = hull_ind[i]; + Point pt = contours[0][ind]; + + ASSERT_EQ(pt, hull[i]); + if( i > 0 ) + { + // check that the convex hull indices are monotone + if( initial_lt ) + { + ASSERT_LT(hull_ind[i-1], hull_ind[i]); + } + else + { + ASSERT_GT(hull_ind[i-1], hull_ind[i]); + } + } + DRAW(circle(canvas, pt, 7, Scalar(180, 0, 180), -1, LINE_AA); + putText(canvas, format("%d (%d)", (int)i, ind), pt+Point(15, 0), FONT_HERSHEY_SIMPLEX, 0.4, Scalar(200, 0, 200), 1, LINE_AA)); + //printf("%d. ind=%d, pt=(%d, %d)\n", (int)i, ind, pt.x, pt.y); + } + } + + convexityDefects(contours[0], hull_ind, defects); + + for(size_t i = 0; i < defects.size(); i++ ) + { + Vec4i d = defects[i]; + //printf("defect %d. start=%d, end=%d, farthest=%d, depth=%d\n", (int)i, d[0], d[1], d[2], d[3]); + EXPECT_LT(d[0], d[1]); + EXPECT_LE(d[0], d[2]); + EXPECT_LE(d[2], d[1]); + + DRAW(Point start = contours[0][d[0]]; + Point end = contours[0][d[1]]; + Point far = contours[0][d[2]]; + line(canvas, start, end, Scalar(255, 255, 128), 3, LINE_AA); + line(canvas, start, far, Scalar(255, 150, 255), 3, LINE_AA); + line(canvas, end, far, Scalar(255, 150, 255), 3, LINE_AA); + circle(canvas, start, 7, Scalar(0, 0, 255), -1, LINE_AA); + circle(canvas, end, 7, Scalar(0, 0, 255), -1, LINE_AA); + circle(canvas, far, 7, Scalar(255, 0, 0), -1, LINE_AA)); + } + + DRAW(imshow("defects", canvas); + waitKey()); +} + +#undef DRAW + +TEST(Imgproc_ConvexHull, overflow) +{ + std::vector points; + std::vector pointsf; + + points.push_back(Point(14763, 2890)); + points.push_back(Point(14388, 72088)); + points.push_back(Point(62810, 72274)); + points.push_back(Point(63166, 3945)); + points.push_back(Point(56782, 3945)); + points.push_back(Point(56763, 3077)); + points.push_back(Point(34666, 2965)); + points.push_back(Point(34547, 2953)); + points.push_back(Point(34508, 2866)); + points.push_back(Point(34429, 2965)); + + size_t i, n = points.size(); + for( i = 0; i < n; i++ ) + pointsf.push_back(Point2f(points[i])); + + std::vector hull; + std::vector hullf; + + convexHull(points, hull, false, false); + convexHull(pointsf, hullf, false, false); + + ASSERT_EQ(hull, hullf); +} + }} // namespace /* End of file. */