From a3bdbf555339b28d161e393131f954b0d3d8e16d Mon Sep 17 00:00:00 2001 From: Kumataro Date: Fri, 23 Aug 2024 18:35:13 +0900 Subject: [PATCH] Merge pull request #26022 from Kumataro:fix26016 Imgproc: use double to determine whether the corners points are within src #26022 close #26016 Related https://github.com/opencv/opencv_contrib/pull/3778 ### 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 - [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/core/include/opencv2/core/types.hpp | 32 ++++++++++++++++++--- modules/core/test/test_misc.cpp | 17 ++++++++++- modules/features2d/src/keypoint.cpp | 4 ++- modules/imgproc/test/test_cornersubpix.cpp | 27 +++++++++++++++++ modules/stitching/test/test_matchers.cpp | 6 ++-- 5 files changed, 78 insertions(+), 8 deletions(-) diff --git a/modules/core/include/opencv2/core/types.hpp b/modules/core/include/opencv2/core/types.hpp index 8e56d5dd93..df4fd21885 100644 --- a/modules/core/include/opencv2/core/types.hpp +++ b/modules/core/include/opencv2/core/types.hpp @@ -475,7 +475,14 @@ public: template operator Rect_<_Tp2>() const; //! checks whether the rectangle contains the point - bool contains(const Point_<_Tp>& pt) const; + /*! @warning After OpenCV 4.11.0, when calling Rect.contains() with cv::Point2f / cv::Point2d point, point should not convert/round to int. + * ``` + * Rect_ r(0,0,500,500); Point_ pt(250.0f, 499.9f); + * r.contains(pt) returns false.(OpenCV 4.10.0 or before) + * r.contains(pt) returns true. (OpenCV 4.11.0 or later) + * ``` + */ + template inline bool contains(const Point_<_Tp2>& pt) const; _Tp x; //!< x coordinate of the top-left corner _Tp y; //!< y coordinate of the top-left corner @@ -1861,12 +1868,29 @@ Rect_<_Tp>::operator Rect_<_Tp2>() const return Rect_<_Tp2>(saturate_cast<_Tp2>(x), saturate_cast<_Tp2>(y), saturate_cast<_Tp2>(width), saturate_cast<_Tp2>(height)); } -template inline -bool Rect_<_Tp>::contains(const Point_<_Tp>& pt) const +template template inline +bool Rect_<_Tp>::contains(const Point_<_Tp2>& pt) const { return x <= pt.x && pt.x < x + width && y <= pt.y && pt.y < y + height; } - +// See https://github.com/opencv/opencv/issues/26016 +template<> template<> inline +bool Rect_::contains(const Point_& pt) const +{ + // std::numeric_limits::digits is 31. + // std::numeric_limits::digits is 53. + // So conversion int->double does not lead to accuracy errors. + const Rect_ _rect(static_cast(x), static_cast(y), static_cast(width), static_cast(height)); + return _rect.contains(pt); +} +template<> template<> inline +bool Rect_::contains(const Point_& _pt) const +{ + // std::numeric_limits::digits is 24. + // std::numeric_limits::digits is 53. + // So conversion float->double does not lead to accuracy errors. + return contains(Point_(static_cast(_pt.x), static_cast(_pt.y))); +} template static inline Rect_<_Tp>& operator += ( Rect_<_Tp>& a, const Point_<_Tp>& b ) diff --git a/modules/core/test/test_misc.cpp b/modules/core/test/test_misc.cpp index 39d0788d64..6e34e2b8d4 100644 --- a/modules/core/test/test_misc.cpp +++ b/modules/core/test/test_misc.cpp @@ -908,7 +908,22 @@ TYPED_TEST_P(Rect_Test, Overflows) { EXPECT_EQ(R(), R(20, 0, 10, 10) & R(0, num_lowest, 10, 10)); EXPECT_EQ(R(), R(num_lowest, 0, 10, 10) & R(0, num_lowest, 10, 10)); } -REGISTER_TYPED_TEST_CASE_P(Rect_Test, Overflows); + +// See https://github.com/opencv/opencv/issues/26016 +// Rect_.contains(Point_) needs template specialization. +// This is test for a point on the edge and its nearest points. +template T cv_nexttoward(T v, T v2); +template<> int cv_nexttoward(int v, int v2) { CV_UNUSED(v); return v2; } +template<> float cv_nexttoward(float v, float v2) { return std::nextafter(v,v2); } +template<> double cv_nexttoward(double v, double v2) { return std::nexttoward(v,v2); } +TYPED_TEST_P(Rect_Test, OnTheEdge) { + Rect_ rect(0,0,500,500); + TypeParam h = static_cast(rect.height); + ASSERT_TRUE ( rect.contains( Point_(250, cv_nexttoward(h, h - 1)))); + ASSERT_FALSE( rect.contains( Point_(250, cv_nexttoward(h, h )))); + ASSERT_FALSE( rect.contains( Point_(250, cv_nexttoward(h, h + 1)))); +} +REGISTER_TYPED_TEST_CASE_P(Rect_Test, Overflows, OnTheEdge); typedef ::testing::Types RectTypes; INSTANTIATE_TYPED_TEST_CASE_P(Negative_Test, Rect_Test, RectTypes); diff --git a/modules/features2d/src/keypoint.cpp b/modules/features2d/src/keypoint.cpp index 4d2007f6d7..aad9fe36e3 100644 --- a/modules/features2d/src/keypoint.cpp +++ b/modules/features2d/src/keypoint.cpp @@ -96,7 +96,9 @@ struct RoiPredicate bool operator()( const KeyPoint& keyPt ) const { - return !r.contains( keyPt.pt ); + // workaround for https://github.com/opencv/opencv/issues/26016 + // To keep its behaviour, keyPt.pt casts to Point_. + return !r.contains( Point_(keyPt.pt) ); } Rect r; diff --git a/modules/imgproc/test/test_cornersubpix.cpp b/modules/imgproc/test/test_cornersubpix.cpp index 86484d2482..5a2e633397 100644 --- a/modules/imgproc/test/test_cornersubpix.cpp +++ b/modules/imgproc/test/test_cornersubpix.cpp @@ -65,4 +65,31 @@ TEST(Imgproc_CornerSubPix, out_of_image_corners) ASSERT_TRUE(Rect(0, 0, image.cols, image.rows).contains(corners.front())); } +// See https://github.com/opencv/opencv/issues/26016 +TEST(Imgproc_CornerSubPix, corners_on_the_edge) +{ + cv::Mat image(500, 500, CV_8UC1); + cv::Size win(1, 1); + cv::Size zeroZone(-1, -1); + cv::TermCriteria criteria; + + std::vector cornersOK1 = { cv::Point2f(250, std::nextafter(499.5f, 499.5f - 1.0f)) }; + EXPECT_NO_THROW( cv::cornerSubPix(image, cornersOK1, win, zeroZone, criteria) ) << cornersOK1; + + std::vector cornersOK2 = { cv::Point2f(250, 499.5f) }; + EXPECT_NO_THROW( cv::cornerSubPix(image, cornersOK2, win, zeroZone, criteria) ) << cornersOK2; + + std::vector cornersOK3 = { cv::Point2f(250, std::nextafter(499.5f, 499.5f + 1.0f)) }; + EXPECT_NO_THROW( cv::cornerSubPix(image, cornersOK3, win, zeroZone, criteria) ) << cornersOK3; + + std::vector cornersOK4 = { cv::Point2f(250, std::nextafter(500.0f, 500.0f - 1.0f)) }; + EXPECT_NO_THROW( cv::cornerSubPix(image, cornersOK4, win, zeroZone, criteria) ) << cornersOK4; + + std::vector cornersNG1 = { cv::Point2f(250, 500.0f) }; + EXPECT_ANY_THROW( cv::cornerSubPix(image, cornersNG1, win, zeroZone, criteria) ) << cornersNG1; + + std::vector cornersNG2 = { cv::Point2f(250, std::nextafter(500.0f, 500.0f + 1.0f)) }; + EXPECT_ANY_THROW( cv::cornerSubPix(image, cornersNG2, win, zeroZone, criteria) ) << cornersNG2; +} + }} // namespace diff --git a/modules/stitching/test/test_matchers.cpp b/modules/stitching/test/test_matchers.cpp index 2deb676fb3..16d254ef6d 100644 --- a/modules/stitching/test/test_matchers.cpp +++ b/modules/stitching/test/test_matchers.cpp @@ -67,9 +67,11 @@ TEST(SurfFeaturesFinder, CanFindInROIs) int tl_rect_count = 0, br_rect_count = 0, bad_count = 0; for (const auto &keypoint : roi_features.keypoints) { - if (rois[0].contains(keypoint.pt)) + // Workaround for https://github.com/opencv/opencv/issues/26016 + // To keep its behaviour, keypoint.pt casts to Point_. + if (rois[0].contains(Point_(keypoint.pt))) tl_rect_count++; - else if (rois[1].contains(keypoint.pt)) + else if (rois[1].contains(Point_(keypoint.pt))) br_rect_count++; else bad_count++;