diff --git a/modules/geometry/test/test_approxpoly.cpp b/modules/geometry/test/test_approxpoly.cpp index 9a70d46408..57bb59cf49 100644 --- a/modules/geometry/test/test_approxpoly.cpp +++ b/modules/geometry/test/test_approxpoly.cpp @@ -43,21 +43,58 @@ namespace opencv_test { namespace { -// -// TODO!!!: -// check_slice (and/or check) seem(s) to be broken, or this is a bug in function -// (or its inability to handle possible self-intersections in the generated contours). -// -// At least, if // return TotalErrors; -// is uncommented in check_slice, the test fails easily. -// So, now (and it looks like since 0.9.6) -// we only check that the set of vertices of the approximated polygon is -// a subset of vertices of the original contour. -// + +// Tests that every point on the original polygon is +// within epsilon distance to the approximated polygon. +TEST(Geometry_ApproxPoly, accuracy) +{ + RNG& rng = TS::ptr()->get_rng(); + const int polygon_count = 30; + + for (int i = 0; i < polygon_count; ++i) + { + // create a random noisy polygon + vector curve; + Point center {rng.uniform(100, 400), rng.uniform(100, 400)}; + double base_radius = rng.uniform(20.0, 100.0); + int points_count = rng.uniform(20, 100); + + for (int p = 0; p < points_count; ++p) + { + double angle = p * (2 * CV_PI) / points_count; + double radius = base_radius + rng.uniform(-10.0, 10.0); + curve.push_back(Point(cvRound(center.x + radius * cos(angle)), + cvRound(center.y + radius * sin(angle)))); + } + + Rect bound = boundingRect(curve); + double max_diameter = sqrt(bound.width * bound.width + bound.height * bound.height); + const int epsilons_count = 10; + double eps_step = max_diameter / epsilons_count; + + // test different epsilon values + for (double eps = eps_step; eps < max_diameter; eps += eps_step) + { + vector approx_curve; + approxPolyDP(curve, approx_curve, eps, true); + + EXPECT_LE(approx_curve.size(), curve.size()); + EXPECT_GT(approx_curve.size(), 0U); + + // every point on original curve should be within epsilon distance to the new polygon + const double tol = std::max(1e-3, 16 * FLT_EPSILON * max_diameter); + for (const auto& pt : curve) + { + const double min_dist = std::abs(cv::pointPolygonTest(approx_curve, Point2f(pt), true)); + EXPECT_LE(min_dist, eps + tol); + } + } + } +} //Tests to make sure that unreasonable epsilon (error) //values never get passed to the Douglas-Peucker algorithm. -TEST(Imgproc_ApproxPoly, bad_epsilon) +TEST(Geometry_ApproxPoly, bad_epsilon) { std::vector inputPoints; inputPoints.push_back(Point2f(0.0f, 0.0f)); @@ -76,7 +113,7 @@ TEST(Imgproc_ApproxPoly, bad_epsilon) ASSERT_ANY_THROW(approxPolyDP(inputPoints, outputPoints, eps, false)); } -TEST(Imgproc_ApproxPoly, distace_between_point_and_segment) +TEST(Geometry_ApproxPoly, distance_between_point_and_segment) { vector inputPoints = { { {0.f, 0.f}, {4.f, 2.f}, {11.f, 1.f}, {8.f, 0.f} } diff --git a/modules/geometry/test/test_arclength.cpp b/modules/geometry/test/test_arclength.cpp new file mode 100644 index 0000000000..c5373aae36 --- /dev/null +++ b/modules/geometry/test/test_arclength.cpp @@ -0,0 +1,97 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +namespace opencv_test { namespace { + +typedef testing::TestWithParam Geometry_ArcLength; + +template +static double refArcLength(const std::vector& curve, bool is_closed) +{ + double expected_length = 0.0; + size_t npoints = curve.size(); + T prev_pt = curve[is_closed ? npoints - 1 : 0]; + int start_idx = is_closed ? 0 : 1; + for (size_t i = start_idx; i < npoints; i++) + { + T pt = curve[i]; + expected_length += cv::norm(pt - prev_pt); + prev_pt = pt; + } + + return expected_length; +} + +// Test arcLength accuracy +// Covers both open/closed curves and int/float coordinates +TEST_P(Geometry_ArcLength, accuracy_float) +{ + const bool is_closed = GetParam(); + + RNG& rng = TS::ptr()->get_rng(); + + // generate random points + const int npoints = rng.uniform(3, 100); + vector curvef; + + for (int i = 0; i < npoints; i++) + { + Point2f pt(rng.uniform(-1000.f, 1000.f), rng.uniform(-1000.f, 1000.f)); + curvef.push_back(pt); + } + + double length = cv::arcLength(curvef, is_closed); + double expected_length = refArcLength(curvef, is_closed); + + EXPECT_NEAR(expected_length, length, FLT_EPSILON * 100 * expected_length); +} + +TEST_P(Geometry_ArcLength, accuracy_int) +{ + const bool is_closed = GetParam(); + + RNG& rng = TS::ptr()->get_rng(); + + // generate random points + const int npoints = rng.uniform(3, 100); + vector curvei; + + for (int i = 0; i < npoints; i++) + { + Point2f pt(rng.uniform(-1000.f, 1000.f), rng.uniform(-1000.f, 1000.f)); + curvei.push_back(Point2i((int)pt.x, (int)pt.y)); + } + + double length = cv::arcLength(curvei, is_closed); + double expected_length = refArcLength(curvei, is_closed); + + EXPECT_NEAR(expected_length, length, FLT_EPSILON * expected_length); +} + +// Simple test with a manually defined shape +TEST(Geometry_ArcLength, simple_square) +{ + // create a 10x10 square + vector square; + square.push_back(Point2f(0, 0)); + square.push_back(Point2f(10, 0)); + square.push_back(Point2f(10, 10)); + square.push_back(Point2f(0, 10)); + + // closed perimeter + EXPECT_DOUBLE_EQ(cv::arcLength(square, true), 40.0); + + // open perimeter (missing the edge from (0,10) to (0,0)) + EXPECT_DOUBLE_EQ(cv::arcLength(square, false), 30.0); +} + +INSTANTIATE_TEST_CASE_P( + Geometry, + Geometry_ArcLength, + testing::Bool() +); + +}} // namespace opencv_test