From 36598677cffe5ab7a22ffb4af3a2e2a73fe55db4 Mon Sep 17 00:00:00 2001 From: Quentin Chateau Date: Sun, 25 Oct 2020 16:58:27 +0100 Subject: [PATCH] Merge pull request #18646 from qchateau:wave-auto * stitching: add WAVE_CORRECT_AUTO * stitching: use CV_EXPORTS --- .../stitching/detail/motion_estimators.hpp | 12 ++++- modules/stitching/src/motion_estimators.cpp | 45 +++++++++++++++++ modules/stitching/test/test_precomp.hpp | 1 + .../stitching/test/test_wave_correction.cpp | 50 +++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 modules/stitching/test/test_wave_correction.cpp diff --git a/modules/stitching/include/opencv2/stitching/detail/motion_estimators.hpp b/modules/stitching/include/opencv2/stitching/detail/motion_estimators.hpp index ff05af1814..ad21ee1277 100644 --- a/modules/stitching/include/opencv2/stitching/detail/motion_estimators.hpp +++ b/modules/stitching/include/opencv2/stitching/detail/motion_estimators.hpp @@ -328,9 +328,19 @@ private: enum WaveCorrectKind { WAVE_CORRECT_HORIZ, - WAVE_CORRECT_VERT + WAVE_CORRECT_VERT, + WAVE_CORRECT_AUTO }; +/** @brief Tries to detect the wave correction kind depending +on whether a panorama spans horizontally or vertically + +@param rmats Camera rotation matrices. +@return The correction kind to use for this panorama + */ +CV_EXPORTS +WaveCorrectKind autoDetectWaveCorrectKind(const std::vector &rmats); + /** @brief Tries to make panorama more horizontal (or vertical). @param rmats Camera rotation matrices. diff --git a/modules/stitching/src/motion_estimators.cpp b/modules/stitching/src/motion_estimators.cpp index d9848dbe7f..c0b46b101d 100644 --- a/modules/stitching/src/motion_estimators.cpp +++ b/modules/stitching/src/motion_estimators.cpp @@ -886,6 +886,45 @@ void BundleAdjusterAffinePartial::calcJacobian(Mat &jac) ////////////////////////////////////////////////////////////////////////////// +WaveCorrectKind autoDetectWaveCorrectKind(const std::vector &rmats) +{ + std::vector xs, ys; + xs.reserve(rmats.size()); + ys.reserve(rmats.size()); + + // Project a [0, 0, 1, 1] point to the camera image frame + // Ignore intrinsic parameters and camera translation as they + // have little influence + // This also means we can simply use "rmat.col(2)" as the + // projected point homogeneous coordinate + for (const Mat& rmat: rmats) + { + CV_Assert(rmat.type() == CV_32F); + xs.push_back(rmat.at(0, 2) / rmat.at(2, 2)); + ys.push_back(rmat.at(1, 2) / rmat.at(2, 2)); + } + + // Calculate the delta between the max and min values for + // both the X and Y axis + auto min_max_x = std::minmax_element(xs.begin(), xs.end()); + auto min_max_y = std::minmax_element(ys.begin(), ys.end()); + double delta_x = *min_max_x.second - *min_max_x.first; + double delta_y = *min_max_y.second - *min_max_y.first; + + // If the Y delta is the biggest, it means the images + // mostly span along the vertical axis: correct this axis + if (delta_y > delta_x) + { + LOGLN(" using vertical wave correction"); + return WAVE_CORRECT_VERT; + } + else + { + LOGLN(" using horizontal wave correction"); + return WAVE_CORRECT_HORIZ; + } +} + void waveCorrect(std::vector &rmats, WaveCorrectKind kind) { LOGLN("Wave correcting..."); @@ -898,12 +937,18 @@ void waveCorrect(std::vector &rmats, WaveCorrectKind kind) return; } + if (kind == WAVE_CORRECT_AUTO) + { + kind = autoDetectWaveCorrectKind(rmats); + } + Mat moment = Mat::zeros(3, 3, CV_32F); for (size_t i = 0; i < rmats.size(); ++i) { Mat col = rmats[i].col(0); moment += col * col.t(); } + Mat eigen_vals, eigen_vecs; eigen(moment, eigen_vals, eigen_vecs); diff --git a/modules/stitching/test/test_precomp.hpp b/modules/stitching/test/test_precomp.hpp index 8e7709a7ec..e761fb1fb0 100644 --- a/modules/stitching/test/test_precomp.hpp +++ b/modules/stitching/test/test_precomp.hpp @@ -6,6 +6,7 @@ #include "opencv2/ts.hpp" #include "opencv2/stitching.hpp" +#include "opencv2/stitching/detail/motion_estimators.hpp" #include "opencv2/stitching/detail/matchers.hpp" #include "opencv2/stitching/detail/blenders.hpp" #include "opencv2/stitching/detail/exposure_compensate.hpp" diff --git a/modules/stitching/test/test_wave_correction.cpp b/modules/stitching/test/test_wave_correction.cpp new file mode 100644 index 0000000000..1ac8ff07aa --- /dev/null +++ b/modules/stitching/test/test_wave_correction.cpp @@ -0,0 +1,50 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +namespace opencv_test { +namespace { + +detail::WaveCorrectKind correctionKind(const std::vector& images) +{ + + Ptr stitcher = Stitcher::create(Stitcher::PANORAMA); + stitcher->estimateTransform(images); + + std::vector rmats; + auto cameras = stitcher->cameras(); + for (const auto& camera: cameras) + rmats.push_back(camera.R); + + return detail::autoDetectWaveCorrectKind(rmats); +} + +TEST(WaveCorrection, AutoWaveCorrection) +{ + std::vector images(2); + imread(cvtest::TS::ptr()->get_data_path() + "stitching/s1.jpg").copyTo(images[0]); + imread(cvtest::TS::ptr()->get_data_path() + "stitching/s2.jpg").copyTo(images[1]); + + EXPECT_EQ(detail::WAVE_CORRECT_HORIZ, correctionKind(images)); + + std::vector rotated_images(2); + rotate(images[0], rotated_images[0], cv::ROTATE_90_CLOCKWISE); + rotate(images[1], rotated_images[1], cv::ROTATE_90_CLOCKWISE); + + EXPECT_EQ(detail::WAVE_CORRECT_VERT, correctionKind(rotated_images)); + + rotate(images[0], rotated_images[0], cv::ROTATE_90_COUNTERCLOCKWISE); + rotate(images[1], rotated_images[1], cv::ROTATE_90_COUNTERCLOCKWISE); + + EXPECT_EQ(detail::WAVE_CORRECT_VERT, correctionKind(rotated_images)); + + rotate(images[0], rotated_images[0], cv::ROTATE_180); + rotate(images[1], rotated_images[1], cv::ROTATE_180); + + EXPECT_EQ(detail::WAVE_CORRECT_HORIZ, correctionKind(rotated_images)); +} + +} // namespace +} // namespace opencv_test