From dbd3ef9a6f838d3ac42f8cf4d9d1dd5c709cb807 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Sun, 2 Mar 2025 12:44:39 +0300 Subject: [PATCH] Merge pull request #26926 from MaximSmolskiy:fix-getPerspectiveTransform-for-singular-case Fix getPerspectiveTransform for singular case #26926 ### Pull Request Readiness Checklist Fix #26916 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 - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/imgproc/src/imgwarp.cpp | 40 ++++++++++++++++++++++++--- modules/imgproc/test/test_imgwarp.cpp | 35 +++++++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 609b3f4557..d512bbf887 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -3392,7 +3392,7 @@ cv::Matx23d cv::getRotationMatrix2D_(Point2f center, double angle, double scale) * vi = --------------------- * c20*xi + c21*yi + c22 * - * Coefficients are calculated by solving linear system: + * Coefficients are calculated by solving one of 2 linear systems: * / x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\ * | x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1| * | x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2| @@ -3404,12 +3404,28 @@ cv::Matx23d cv::getRotationMatrix2D_(Point2f center, double angle, double scale) * * where: * cij - matrix coefficients, c22 = 1 + * + * or + * + * / x0 y0 1 0 0 0 -x0*u0 -y0*u0 -u0 \ /c00\ /0\ + * | x1 y1 1 0 0 0 -x1*u1 -y1*u1 -u1 | |c01| |0| + * | x2 y2 1 0 0 0 -x2*u2 -y2*u2 -u2 | |c02| |0| + * | x3 y3 1 0 0 0 -x3*u3 -y3*u3 -u3 |.|c10|=|0|, + * | 0 0 0 x0 y0 1 -x0*v0 -y0*v0 -v0 | |c11| |0| + * | 0 0 0 x1 y1 1 -x1*v1 -y1*v1 -v1 | |c12| |0| + * | 0 0 0 x2 y2 1 -x2*v2 -y2*v2 -v2 | |c20| |0| + * \ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 -v3 / |c21| \0/ + * \c22/ + * + * where: + * cij - matrix coefficients, c00^2 + c01^2 + c02^2 + c10^2 + c11^2 + c12^2 + c20^2 + c21^2 + c22^2 = 1 */ cv::Mat cv::getPerspectiveTransform(const Point2f src[], const Point2f dst[], int solveMethod) { CV_INSTRUMENT_REGION(); - Mat M(3, 3, CV_64F), X(8, 1, CV_64F, M.ptr()); + // try c22 = 1 + Mat M(3, 3, CV_64F), X8(8, 1, CV_64F, M.ptr()); double a[8][8], b[8]; Mat A(8, 8, CV_64F, a), B(8, 1, CV_64F, b); @@ -3428,8 +3444,24 @@ cv::Mat cv::getPerspectiveTransform(const Point2f src[], const Point2f dst[], in b[i+4] = dst[i].y; } - solve(A, B, X, solveMethod); - M.ptr()[8] = 1.; + if (solve(A, B, X8, solveMethod) && norm(A * X8, B) < 1e-8) + { + M.ptr()[8] = 1.; + + return M; + } + + // c00^2 + c01^2 + c02^2 + c10^2 + c11^2 + c12^2 + c20^2 + c21^2 + c22^2 = 1 + hconcat(A, -B, A); + + Mat AtA; + mulTransposed(A, AtA, true); + + Mat D, U; + SVDecomp(AtA, D, U, noArray()); + + Mat X9(9, 1, CV_64F, M.ptr()); + U.col(8).copyTo(X9); return M; } diff --git a/modules/imgproc/test/test_imgwarp.cpp b/modules/imgproc/test/test_imgwarp.cpp index 4cf99b4704..2ff1ca88ab 100644 --- a/modules/imgproc/test/test_imgwarp.cpp +++ b/modules/imgproc/test/test_imgwarp.cpp @@ -1765,5 +1765,40 @@ TEST(Imgproc_Remap, issue_23562) } } +TEST(Imgproc_getPerspectiveTransform, issue_26916) +{ + double src_data[] = {320, 512, 960, 512, 0, 1024, 1280, 1024}; + const Mat src_points(4, 2, CV_64FC1, src_data); + + double dst_data[] = {0, 0, 1280, 0, 0, 1024, 1280, 1024}; + const Mat dst_points(4, 2, CV_64FC1, dst_data); + + Mat src_points_f; + src_points.convertTo(src_points_f, CV_32FC1); + + Mat dst_points_f; + dst_points.convertTo(dst_points_f, CV_32FC1); + + Mat perspective_transform = getPerspectiveTransform(src_points_f, dst_points_f); + EXPECT_NEAR(perspective_transform.at(2, 2), 0, 1e-16); + EXPECT_NEAR(cv::norm(perspective_transform), 1, 1e-14); + + const Mat ones = Mat::ones(4, 1, CV_64FC1); + + Mat homogeneous_src_points; + hconcat(src_points, ones, homogeneous_src_points); + + Mat obtained_homogeneous_dst_points = (perspective_transform * homogeneous_src_points.t()).t(); + for (int row = 0; row < 4; ++row) + { + obtained_homogeneous_dst_points.row(row) /= obtained_homogeneous_dst_points.at(row, 2); + } + + Mat expected_homogeneous_dst_points; + hconcat(dst_points, ones, expected_homogeneous_dst_points); + + EXPECT_MAT_NEAR(obtained_homogeneous_dst_points, expected_homogeneous_dst_points, 1e-10); +} + }} // namespace /* End of file. */