Merge pull request #29547 from alessio-antochi:fix-approxpoly-test

Rework accuracy test for legacy C-API cvApproxPoly and CV_PerimeterTest - #29547

### 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

**Details:**
This PR restores the deleted C-API `cvApproxPoly` tests (part of #24957).
I used `cv::pointPolygonTest` to make sure every original point is within the allowed `epsilon` distance to the final approximated polygon rather than calculate the distance to the corresponding line.

**Update:**
Restored `CV_PerimeterTest` *TEST(Imgproc_ContourPerimeter, accuracy)*.
Created a parameterized test for open/closed curves with int/float type shapes. I  also added a simple 10x10 square test.
This commit is contained in:
Alessio Roberto Antochi
2026-09-01 10:05:46 +03:00
committed by GitHub
parent 0627765f01
commit 16e83222dc
2 changed files with 147 additions and 13 deletions

View File

@@ -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<Point> 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<Point> 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<Point2f> 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<Point2f> inputPoints = {
{ {0.f, 0.f}, {4.f, 2.f}, {11.f, 1.f}, {8.f, 0.f} }

View File

@@ -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<bool> Geometry_ArcLength;
template<typename T>
static double refArcLength(const std::vector<T>& 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<Point2f> 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<Point2i> 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<Point2f> 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