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
pull/16535/head
Vadim Pisarevsky 5 years ago committed by GitHub
parent a0f5eb282c
commit 8f3867756c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      modules/imgproc/misc/java/test/ImgprocTest.java
  2. 77
      modules/imgproc/src/convhull.cpp
  3. 152
      modules/imgproc/test/test_convhull.cpp

@ -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);
}

@ -45,7 +45,7 @@
namespace cv
{
template<typename _Tp>
template<typename _Tp, typename _DotTp>
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<typename _Tp>
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_<int, int64>( pointer, 0, maxy_ind, tl_stack, -1, 1) :
Sklansky_<float, double>( 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_<int, int64>( pointer, total-1, maxy_ind, tr_stack, -1, -1) :
Sklansky_<float, double>( 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_<int, int64>( pointer, 0, miny_ind, bl_stack, 1, -1) :
Sklansky_<float, double>( 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_<int, int64>( pointer, total-1, miny_ind, br_stack, 1, 1) :
Sklansky_<float, double>( 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);

@ -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<Point> hull;
vector<int> hull_ind;
vector<Vec4i> 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<Point>();
fillPoly(canvas_gray, &ptptr, &npoints, 1, Scalar(255, 255, 255));
vector<vector<Point> > 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<Point> points;
std::vector<Point2f> 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<int> hull;
std::vector<int> hullf;
convexHull(points, hull, false, false);
convexHull(pointsf, hullf, false, false);
ASSERT_EQ(hull, hullf);
}
}} // namespace
/* End of file. */

Loading…
Cancel
Save