mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 00:03:03 +04:00
Merge pull request #28628 from GideokKim:fix/fitellipse-det-threshold
imgproc: fix fitEllipseDirect/AMS determinant threshold for near-circular data
This commit is contained in:
@@ -579,7 +579,9 @@ cv::RotatedRect cv::fitEllipseAMS( InputArray _points )
|
||||
M(4,3)=DM(3,4);
|
||||
M(4,4)=DM(4,4);
|
||||
|
||||
if (fabs(cv::determinant(M)) > 1.0e-10) {
|
||||
double npow = (double)n * (double)n;
|
||||
npow = npow * npow * (double)n; // n^5
|
||||
if (fabs(cv::determinant(M)) > 1.0e-10 / npow) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -703,6 +705,8 @@ cv::RotatedRect cv::fitEllipseDirect( InputArray _points )
|
||||
Matx<double, 3, 1> pVec;
|
||||
|
||||
double x0, y0, a, b, theta, Ts;
|
||||
Mat eVal, eVec;
|
||||
double cond[3];
|
||||
double s = 0;
|
||||
|
||||
for( i = 0; i < n; i++ )
|
||||
@@ -763,6 +767,11 @@ cv::RotatedRect cv::fitEllipseDirect( InputArray _points )
|
||||
Ts=(-(DM(3,5)*DM(4,4)*DM(5,3)) + DM(3,4)*DM(4,5)*DM(5,3) + DM(3,5)*DM(4,3)*DM(5,4) - \
|
||||
DM(3,3)*DM(4,5)*DM(5,4) - DM(3,4)*DM(4,3)*DM(5,5) + DM(3,3)*DM(4,4)*DM(5,5));
|
||||
|
||||
if (fabs(Ts) < DBL_EPSILON) {
|
||||
eps = (float)(s/(n*2)*1e-2);
|
||||
continue;
|
||||
}
|
||||
|
||||
M(0,0) = (DM(2,0) + (DM(2,3)*TM(0,0) + DM(2,4)*TM(1,0) + DM(2,5)*TM(2,0))/Ts)/2.;
|
||||
M(0,1) = (DM(2,1) + (DM(2,3)*TM(0,1) + DM(2,4)*TM(1,1) + DM(2,5)*TM(2,1))/Ts)/2.;
|
||||
M(0,2) = (DM(2,2) + (DM(2,3)*TM(0,2) + DM(2,4)*TM(1,2) + DM(2,5)*TM(2,2))/Ts)/2.;
|
||||
@@ -773,18 +782,9 @@ cv::RotatedRect cv::fitEllipseDirect( InputArray _points )
|
||||
M(2,1) = (DM(0,1) + (DM(0,3)*TM(0,1) + DM(0,4)*TM(1,1) + DM(0,5)*TM(2,1))/Ts)/2.;
|
||||
M(2,2) = (DM(0,2) + (DM(0,3)*TM(0,2) + DM(0,4)*TM(1,2) + DM(0,5)*TM(2,2))/Ts)/2.;
|
||||
|
||||
double det = cv::determinant(M);
|
||||
if (fabs(det) > 1.0e-10)
|
||||
break;
|
||||
eps = (float)(s/(n*2)*1e-2);
|
||||
}
|
||||
|
||||
if( iter < 2 ) {
|
||||
Mat eVal, eVec;
|
||||
eigenNonSymmetric(M, eVal, eVec);
|
||||
|
||||
// Select the eigen vector {a,b,c} which satisfies 4ac-b^2 > 0
|
||||
double cond[3];
|
||||
cond[0]=(4.0 * eVec.at<double>(0,0) * eVec.at<double>(0,2) - eVec.at<double>(0,1) * eVec.at<double>(0,1));
|
||||
cond[1]=(4.0 * eVec.at<double>(1,0) * eVec.at<double>(1,2) - eVec.at<double>(1,1) * eVec.at<double>(1,1));
|
||||
cond[2]=(4.0 * eVec.at<double>(2,0) * eVec.at<double>(2,2) - eVec.at<double>(2,1) * eVec.at<double>(2,1));
|
||||
@@ -793,6 +793,18 @@ cv::RotatedRect cv::fitEllipseDirect( InputArray _points )
|
||||
} else {
|
||||
i = (cond[0]<cond[2]) ? 2 : 0;
|
||||
}
|
||||
// Check that the ellipse condition 4ac-b^2 is meaningfully positive
|
||||
// (not just positive due to floating-point noise from complex eigenvalues)
|
||||
{
|
||||
double v0 = eVec.at<double>(i,0), v1 = eVec.at<double>(i,1), v2 = eVec.at<double>(i,2);
|
||||
double vnorm2 = v0*v0 + v1*v1 + v2*v2;
|
||||
if (cond[i] > 1e-6 * vnorm2)
|
||||
break;
|
||||
}
|
||||
eps = (float)(s/(n*2)*1e-2);
|
||||
}
|
||||
|
||||
if( iter < 2 ) {
|
||||
double norm = std::sqrt(eVec.at<double>(i,0)*eVec.at<double>(i,0) + eVec.at<double>(i,1)*eVec.at<double>(i,1) + eVec.at<double>(i,2)*eVec.at<double>(i,2));
|
||||
if (((eVec.at<double>(i,0)<0.0 ? -1 : 1) * (eVec.at<double>(i,1)<0.0 ? -1 : 1) * (eVec.at<double>(i,2)<0.0 ? -1 : 1)) <= 0.0) {
|
||||
norm=-1.0*norm;
|
||||
|
||||
@@ -337,6 +337,26 @@ TEST(Imgproc_FitEllipseAMS_Issue_7, accuracy) {
|
||||
EXPECT_TRUE(checkEllipse(ellipseAMSTest, ellipseAMSTrue, tol));
|
||||
}
|
||||
|
||||
TEST(Imgproc_FitEllipseAMS_NearCircular, accuracy)
|
||||
{
|
||||
std::vector<cv::Point2f> points;
|
||||
double cx = 27.0, cy = 27.0, a = 17.0, b = 16.5;
|
||||
for (int i = 0; i < 360; i++) {
|
||||
double theta = 2.0 * CV_PI * i / 360.0;
|
||||
points.push_back(cv::Point2f(
|
||||
(float)(cx + a * cos(theta)),
|
||||
(float)(cy + b * sin(theta))));
|
||||
}
|
||||
|
||||
cv::RotatedRect ams = cv::fitEllipseAMS(points);
|
||||
|
||||
// AMS should produce a valid result close to ground truth
|
||||
EXPECT_NEAR(ams.center.x, 27.0, 0.5);
|
||||
EXPECT_NEAR(ams.center.y, 27.0, 0.5);
|
||||
EXPECT_NEAR(std::max(ams.size.width, ams.size.height), 34.0, 1.0);
|
||||
EXPECT_NEAR(std::min(ams.size.width, ams.size.height), 33.0, 1.0);
|
||||
}
|
||||
|
||||
TEST(Imgproc_FitEllipseAMS_HorizontalLine, accuracy) {
|
||||
vector<Point2f> pts({{-300, 100}, {-200, 100}, {-100, 100}, {0, 100}, {100, 100}, {200, 100}, {300, 100}});
|
||||
const RotatedRect el = fitEllipseAMS(pts);
|
||||
|
||||
@@ -337,6 +337,28 @@ TEST(Imgproc_FitEllipseDirect_Issue_7, accuracy) {
|
||||
EXPECT_TRUE(checkEllipse(ellipseDirectTest, ellipseDirectTrue, tol));
|
||||
}
|
||||
|
||||
TEST(Imgproc_FitEllipseDirect_NearCircular, accuracy)
|
||||
{
|
||||
// 360 points on a near-circular ellipse (a=17, b=16.5)
|
||||
// This data previously triggered unnecessary fallback to fitEllipseNoDirect
|
||||
std::vector<cv::Point2f> points;
|
||||
double cx = 27.0, cy = 27.0, a = 17.0, b = 16.5;
|
||||
for (int i = 0; i < 360; i++) {
|
||||
double theta = 2.0 * CV_PI * i / 360.0;
|
||||
points.push_back(cv::Point2f(
|
||||
(float)(cx + a * cos(theta)),
|
||||
(float)(cy + b * sin(theta))));
|
||||
}
|
||||
|
||||
cv::RotatedRect direct = cv::fitEllipseDirect(points);
|
||||
|
||||
// Direct should produce a valid result close to ground truth
|
||||
EXPECT_NEAR(direct.center.x, 27.0, 0.1);
|
||||
EXPECT_NEAR(direct.center.y, 27.0, 0.1);
|
||||
EXPECT_NEAR(std::max(direct.size.width, direct.size.height), 34.0, 0.5);
|
||||
EXPECT_NEAR(std::min(direct.size.width, direct.size.height), 33.0, 0.5);
|
||||
}
|
||||
|
||||
TEST(Imgproc_FitEllipseDirect_HorizontalLine, accuracy) {
|
||||
vector<Point2f> pts({{-300, 100}, {-200, 100}, {-100, 100}, {0, 100}, {100, 100}, {200, 100}, {300, 100}});
|
||||
const RotatedRect el = fitEllipseDirect(pts);
|
||||
|
||||
Reference in New Issue
Block a user