calib3d: fix IPPE rotation vector for rotations near 180 degrees

PoseSolver::rot2vec reads the rotation axis from R - R^T and divides
by sin(theta). Both vanish when the rotation is close to 180 degrees,
which is the pose of a square tag seen exactly head-on, so
SOLVEPNP_IPPE_SQUARE returned a pose flipped by 180 degrees. Fall back
to Rodrigues in that case and clamp the acos argument. The fast path
for all other rotations is unchanged.
This commit is contained in:
Alessio Timofte
2026-10-19 18:26:52 +02:00
parent f2be0a0a28
commit c85f560671
2 changed files with 31 additions and 1 deletions

View File

@@ -303,13 +303,17 @@ void PoseSolver::rot2vec(InputArray _R, OutputArray _r)
Mat rvec = _r.getMat();
double trace = R.at<double>(0, 0) + R.at<double>(1, 1) + R.at<double>(2, 2);
double w_norm = acos((trace - 1.0) / 2.0);
double w_norm = acos(std::min(std::max((trace - 1.0) / 2.0, -1.0), 1.0));
double eps = std::numeric_limits<float>::epsilon();
double d = 1 / (2 * sin(w_norm)) * w_norm;
if (w_norm < eps) //rotation is the identity
{
rvec.setTo(0);
}
else if (sin(w_norm) < 1e-5) //close to 180 degrees, the axis can't be read from R - R^T
{
Rodrigues(R, rvec);
}
else
{
double c0 = R.at<double>(2, 1) - R.at<double>(1, 2);

View File

@@ -2404,4 +2404,30 @@ TEST(AP3P, ctheta1p_nan_23607)
}
}
TEST(Calib3d_SolvePnP, regression_30402)
{
// A tag seen exactly head-on: the pose has a 180 degree rotation about X.
const double L = 0.05;
std::vector<Point3d> objectPoints = {
Point3d(-L, L, 0), Point3d( L, L, 0), Point3d( L, -L, 0), Point3d(-L, -L, 0)
};
Matx33d intrinsics(800, 0, 320,
0, 800, 240,
0, 0, 1);
Vec3d rvecGT(CV_PI, 0, 0), tvecGT(0, 0, 0.5);
std::vector<Point2d> imagePoints;
projectPoints(objectPoints, rvecGT, tvecGT, intrinsics, noArray(), imagePoints);
Mat rvec, tvec;
ASSERT_TRUE(solvePnP(objectPoints, imagePoints, intrinsics, noArray(), rvec, tvec,
false, SOLVEPNP_IPPE_SQUARE));
Mat R, RGT;
cv::Rodrigues(rvec, R);
cv::Rodrigues(rvecGT, RGT);
EXPECT_LE(cvtest::norm(R, RGT, NORM_INF), 1e-6);
EXPECT_LE(cvtest::norm(tvec, Mat(tvecGT), NORM_INF), 1e-6);
}
}} // namespace