From 26a7647e0e7c665e48e058f71cc1ab952cc4303d Mon Sep 17 00:00:00 2001 From: Andrew Chinery Date: Tue, 13 Sep 2022 14:35:42 +0100 Subject: [PATCH] Fix stitching Python bindings PR #22329 --- .../opencv2/stitching/detail/matchers.hpp | 21 +++++++--- .../opencv2/stitching/detail/seam_finders.hpp | 2 +- .../misc/python/test/test_stitching.py | 42 +++++++++++++++++++ modules/stitching/src/matchers.cpp | 8 ++-- modules/stitching/test/test_matchers.cpp | 26 ++++++++++++ 5 files changed, 89 insertions(+), 10 deletions(-) diff --git a/modules/stitching/include/opencv2/stitching/detail/matchers.hpp b/modules/stitching/include/opencv2/stitching/detail/matchers.hpp index 1b7d7d6897..2edc9564ba 100644 --- a/modules/stitching/include/opencv2/stitching/detail/matchers.hpp +++ b/modules/stitching/include/opencv2/stitching/detail/matchers.hpp @@ -138,7 +138,7 @@ public: @sa detail::MatchesInfo */ CV_WRAP_AS(apply2) void operator ()(const std::vector &features, CV_OUT std::vector &pairwise_matches, - const cv::UMat &mask = cv::UMat()); + const cv::UMat &mask = cv::UMat()) { match(features, pairwise_matches, mask); }; /** @return True, if it's possible to use the same matcher instance in parallel, false otherwise */ @@ -161,6 +161,16 @@ protected: virtual void match(const ImageFeatures &features1, const ImageFeatures &features2, MatchesInfo& matches_info) = 0; + /** @brief This method implements logic to match features between arbitrary number of features. + By default this checks every pair of inputs in the input, but the behaviour can be changed by subclasses. + + @param features vector of image features + @param pairwise_matches found matches + @param mask (optional) mask indicating which image pairs should be matched + */ + virtual void match(const std::vector &features, std::vector &pairwise_matches, + const cv::UMat &mask = cv::UMat()); + bool is_thread_safe_; }; @@ -202,11 +212,12 @@ public: CV_WRAP BestOf2NearestRangeMatcher(int range_width = 5, bool try_use_gpu = false, float match_conf = 0.3f, int num_matches_thresh1 = 6, int num_matches_thresh2 = 6); - void operator ()(const std::vector &features, std::vector &pairwise_matches, - const cv::UMat &mask = cv::UMat()); - - protected: + // indicate that we do not want to hide the base class match method with a different signature + using BestOf2NearestMatcher::match; + void match(const std::vector &features, std::vector &pairwise_matches, + const cv::UMat &mask = cv::UMat()) CV_OVERRIDE; + int range_width_; }; diff --git a/modules/stitching/include/opencv2/stitching/detail/seam_finders.hpp b/modules/stitching/include/opencv2/stitching/detail/seam_finders.hpp index 71dae7fdff..9ccfd14424 100644 --- a/modules/stitching/include/opencv2/stitching/detail/seam_finders.hpp +++ b/modules/stitching/include/opencv2/stitching/detail/seam_finders.hpp @@ -248,7 +248,7 @@ public: ~GraphCutSeamFinder(); CV_WRAP void find(const std::vector &src, const std::vector &corners, - std::vector &masks) CV_OVERRIDE; + CV_IN_OUT std::vector &masks) CV_OVERRIDE; private: // To avoid GCGraph dependency diff --git a/modules/stitching/misc/python/test/test_stitching.py b/modules/stitching/misc/python/test/test_stitching.py index 2e7b2b5818..5f143d0013 100644 --- a/modules/stitching/misc/python/test/test_stitching.py +++ b/modules/stitching/misc/python/test/test_stitching.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import cv2 as cv +import numpy as np from tests_common import NewOpenCVTests @@ -134,6 +135,47 @@ class stitching_matches_info_test(NewOpenCVTests): self.assertIsNotNone(matches_info.matches) self.assertIsNotNone(matches_info.inliers_mask) +class stitching_range_matcher_test(NewOpenCVTests): + + def test_simple(self): + images = [ + self.get_sample('stitching/a1.png'), + self.get_sample('stitching/a2.png'), + self.get_sample('stitching/a3.png') + ] + + orb = cv.ORB_create() + + features = [cv.detail.computeImageFeatures2(orb, img) for img in images] + + matcher = cv.detail_BestOf2NearestRangeMatcher(range_width=1) + matches = matcher.apply2(features) + + # matches[1] is image 0 and image 1, should have non-zero confidence + self.assertNotEqual(matches[1].confidence, 0) + + # matches[2] is image 0 and image 2, should have zero confidence due to range_width=1 + self.assertEqual(matches[2].confidence, 0) + + +class stitching_seam_finder_graph_cuts(NewOpenCVTests): + + def test_simple(self): + images = [ + self.get_sample('stitching/a1.png'), + self.get_sample('stitching/a2.png'), + self.get_sample('stitching/a3.png') + ] + + images = [cv.resize(img, [100, 100]) for img in images] + + finder = cv.detail_GraphCutSeamFinder('COST_COLOR_GRAD') + masks = [cv.UMat(255 * np.ones((img.shape[0], img.shape[1]), np.uint8)) for img in images] + images_f = [img.astype(np.float32) for img in images] + masks_warped = finder.find(images_f, [(0, 0), (75, 0), (150, 0)], masks) + + self.assertIsNotNone(masks_warped) + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/stitching/src/matchers.cpp b/modules/stitching/src/matchers.cpp index 4791584366..cf742dd4b8 100644 --- a/modules/stitching/src/matchers.cpp +++ b/modules/stitching/src/matchers.cpp @@ -335,8 +335,8 @@ MatchesInfo& MatchesInfo::operator =(const MatchesInfo &other) ////////////////////////////////////////////////////////////////////////////// -void FeaturesMatcher::operator ()(const std::vector &features, std::vector &pairwise_matches, - const UMat &mask) +void FeaturesMatcher::match(const std::vector &features, std::vector &pairwise_matches, + const UMat &mask) { const int num_images = static_cast(features.size()); @@ -484,8 +484,8 @@ BestOf2NearestRangeMatcher::BestOf2NearestRangeMatcher(int range_width, bool try } -void BestOf2NearestRangeMatcher::operator ()(const std::vector &features, std::vector &pairwise_matches, - const UMat &mask) +void BestOf2NearestRangeMatcher::match(const std::vector &features, std::vector &pairwise_matches, + const UMat &mask) { const int num_images = static_cast(features.size()); diff --git a/modules/stitching/test/test_matchers.cpp b/modules/stitching/test/test_matchers.cpp index 08c5aa56db..2deb676fb3 100644 --- a/modules/stitching/test/test_matchers.cpp +++ b/modules/stitching/test/test_matchers.cpp @@ -114,4 +114,30 @@ TEST(ParallelFeaturesFinder, IsSameWithSerial) } } +TEST(RangeMatcher, MatchesRangeOnly) +{ + Ptr finder = ORB::create(); + + Mat img0 = imread(string(cvtest::TS::ptr()->get_data_path()) + "stitching/a1.png", IMREAD_GRAYSCALE); + Mat img1 = imread(string(cvtest::TS::ptr()->get_data_path()) + "stitching/a2.png", IMREAD_GRAYSCALE); + Mat img2 = imread(string(cvtest::TS::ptr()->get_data_path()) + "stitching/a3.png", IMREAD_GRAYSCALE); + + vector features(3); + + computeImageFeatures(finder, img0, features[0]); + computeImageFeatures(finder, img1, features[1]); + computeImageFeatures(finder, img2, features[2]); + + vector pairwise_matches; + Ptr matcher = makePtr(1); + + (*matcher)(features, pairwise_matches); + + // matches[1] will be image 0 and image 1, should have non-zero confidence + EXPECT_NE(pairwise_matches[1].confidence, .0); + + // matches[2] will be image 0 and image 2, should have zero confidence due to range_width=1 + EXPECT_DOUBLE_EQ(pairwise_matches[2].confidence, .0); +} + }} // namespace