diff --git a/modules/stitching/CMakeLists.txt b/modules/stitching/CMakeLists.txt index c9b8e51ee..33ce94ba9 100644 --- a/modules/stitching/CMakeLists.txt +++ b/modules/stitching/CMakeLists.txt @@ -1,40 +1,2 @@ -project(stitching) - -include_directories( - "${CMAKE_CURRENT_SOURCE_DIR}" - "${OpenCV_SOURCE_DIR}/modules/core/include" - "${OpenCV_SOURCE_DIR}/modules/imgproc/include" - "${OpenCV_SOURCE_DIR}/modules/objdetect/include" - "${OpenCV_SOURCE_DIR}/modules/ml/include" - "${OpenCV_SOURCE_DIR}/modules/highgui/include" - "${OpenCV_SOURCE_DIR}/modules/video/include" - "${OpenCV_SOURCE_DIR}/modules/features2d/include" - "${OpenCV_SOURCE_DIR}/modules/flann/include" - "${OpenCV_SOURCE_DIR}/modules/calib3d/include" - "${OpenCV_SOURCE_DIR}/modules/legacy/include" - "${OpenCV_SOURCE_DIR}/modules/imgproc/src" # for gcgraph.hpp - "${OpenCV_SOURCE_DIR}/modules/gpu/include" - ) - -set(stitching_libs opencv_core opencv_imgproc opencv_highgui opencv_features2d opencv_calib3d opencv_gpu) - -FILE(GLOB stitching_files "*.hpp" "*.cpp") - -set(the_target opencv_stitching) -add_executable(${the_target} ${stitching_files}) - -add_dependencies(${the_target} ${stitching_libs}) -set_target_properties(${the_target} PROPERTIES - DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" - ARCHIVE_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH} - RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} - INSTALL_NAME_DIR lib - OUTPUT_NAME "opencv_stitching") - -if(ENABLE_SOLUTION_FOLDERS) - set_target_properties(${the_target} PROPERTIES FOLDER "applications") -endif() - -target_link_libraries(${the_target} ${stitching_libs}) - -install(TARGETS ${the_target} RUNTIME DESTINATION bin COMPONENT main) +include_directories("${OpenCV_SOURCE_DIR}/modules/imgproc/src") # For gcgraph.hpp +define_opencv_module(stitching opencv_core opencv_imgproc opencv_features2d opencv_calib3d opencv_gpu opencv_flann opencv_objdetect) diff --git a/modules/stitching/autocalib.hpp b/modules/stitching/include/opencv2/stitching/autocalib.hpp similarity index 85% rename from modules/stitching/autocalib.hpp rename to modules/stitching/include/opencv2/stitching/autocalib.hpp index 0807ee70e..30c9b6bda 100644 --- a/modules/stitching/autocalib.hpp +++ b/modules/stitching/include/opencv2/stitching/autocalib.hpp @@ -39,19 +39,24 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#ifndef __OPENCV_AUTOCALIB_HPP__ -#define __OPENCV_AUTOCALIB_HPP__ +#ifndef __OPENCV_STITCHING_AUTOCALIB_HPP__ +#define __OPENCV_STITCHING_AUTOCALIB_HPP__ -#include "precomp.hpp" +#include "opencv2/core/core.hpp" #include "matchers.hpp" +namespace cv +{ + // See "Construction of Panoramic Image Mosaics with Global and Local Alignment" // by Heung-Yeung Shum and Richard Szeliski. -void focalsFromHomography(const cv::Mat &H, double &f0, double &f1, bool &f0_ok, bool &f1_ok); +void focalsFromHomography(const Mat &H, double &f0, double &f1, bool &f0_ok, bool &f1_ok); void estimateFocal(const std::vector &features, const std::vector &pairwise_matches, std::vector &focals); -bool calibrateRotatingCamera(const std::vector &Hs, cv::Mat &K); +bool calibrateRotatingCamera(const std::vector &Hs, Mat &K); -#endif // __OPENCV_AUTOCALIB_HPP__ +} // namespace cv + +#endif // __OPENCV_STITCHING_AUTOCALIB_HPP__ diff --git a/modules/stitching/blenders.hpp b/modules/stitching/include/opencv2/stitching/blenders.hpp similarity index 66% rename from modules/stitching/blenders.hpp rename to modules/stitching/include/opencv2/stitching/blenders.hpp index 119d70a31..dc0a37a43 100644 --- a/modules/stitching/blenders.hpp +++ b/modules/stitching/include/opencv2/stitching/blenders.hpp @@ -39,26 +39,29 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#ifndef __OPENCV_BLENDERS_HPP__ -#define __OPENCV_BLENDERS_HPP__ +#ifndef __OPENCV_STITCHING_BLENDERS_HPP__ +#define __OPENCV_STITCHING_BLENDERS_HPP__ -#include "precomp.hpp" +#include "opencv2/core/core.hpp" + +namespace cv +{ // Simple blender which puts one image over another class Blender { public: enum { NO, FEATHER, MULTI_BAND }; - static cv::Ptr createDefault(int type, bool try_gpu = false); + static Ptr createDefault(int type, bool try_gpu = false); - void prepare(const std::vector &corners, const std::vector &sizes); - virtual void prepare(cv::Rect dst_roi); - virtual void feed(const cv::Mat &img, const cv::Mat &mask, cv::Point tl); - virtual void blend(cv::Mat &dst, cv::Mat &dst_mask); + void prepare(const std::vector &corners, const std::vector &sizes); + virtual void prepare(Rect dst_roi); + virtual void feed(const Mat &img, const Mat &mask, Point tl); + virtual void blend(Mat &dst, Mat &dst_mask); protected: - cv::Mat dst_, dst_mask_; - cv::Rect dst_roi_; + Mat dst_, dst_mask_; + Rect dst_roi_; }; @@ -69,14 +72,14 @@ public: float sharpness() const { return sharpness_; } void setSharpness(float val) { sharpness_ = val; } - void prepare(cv::Rect dst_roi); - void feed(const cv::Mat &img, const cv::Mat &mask, cv::Point tl); - void blend(cv::Mat &dst, cv::Mat &dst_mask); + void prepare(Rect dst_roi); + void feed(const Mat &img, const Mat &mask, Point tl); + void blend(Mat &dst, Mat &dst_mask); private: float sharpness_; - cv::Mat weight_map_; - cv::Mat dst_weight_map_; + Mat weight_map_; + Mat dst_weight_map_; }; @@ -87,15 +90,15 @@ public: int numBands() const { return actual_num_bands_; } void setNumBands(int val) { actual_num_bands_ = val; } - void prepare(cv::Rect dst_roi); - void feed(const cv::Mat &img, const cv::Mat &mask, cv::Point tl); - void blend(cv::Mat &dst, cv::Mat &dst_mask); + void prepare(Rect dst_roi); + void feed(const Mat &img, const Mat &mask, Point tl); + void blend(Mat &dst, Mat &dst_mask); private: int actual_num_bands_, num_bands_; - std::vector dst_pyr_laplace_; - std::vector dst_band_weights_; - cv::Rect dst_roi_final_; + std::vector dst_pyr_laplace_; + std::vector dst_band_weights_; + Rect dst_roi_final_; bool can_use_gpu_; }; @@ -103,15 +106,17 @@ private: ////////////////////////////////////////////////////////////////////////////// // Auxiliary functions -void normalize(const cv::Mat& weight, cv::Mat& src); +void normalizeUsingWeightMap(const Mat& weight, Mat& src); -void createWeightMap(const cv::Mat& mask, float sharpness, cv::Mat& weight); +void createWeightMap(const Mat& mask, float sharpness, Mat& weight); -void createLaplacePyr(const cv::Mat &img, int num_levels, std::vector& pyr); +void createLaplacePyr(const Mat &img, int num_levels, std::vector& pyr); -void createLaplacePyrGpu(const cv::Mat &img, int num_levels, std::vector& pyr); +void createLaplacePyrGpu(const Mat &img, int num_levels, std::vector& pyr); // Restores source image -void restoreImageFromLaplacePyr(std::vector& pyr); +void restoreImageFromLaplacePyr(std::vector& pyr); -#endif // __OPENCV_BLENDERS_HPP__ +} // namespace cv + +#endif // __OPENCV_STITCHING_BLENDERS_HPP__ diff --git a/modules/stitching/camera.hpp b/modules/stitching/include/opencv2/stitching/camera.hpp similarity index 90% rename from modules/stitching/camera.hpp rename to modules/stitching/include/opencv2/stitching/camera.hpp index 15ce8985a..d7ea9e6c0 100644 --- a/modules/stitching/camera.hpp +++ b/modules/stitching/include/opencv2/stitching/camera.hpp @@ -39,11 +39,13 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#ifndef __OPENCV_CAMERA_HPP__ -#define __OPENCV_CAMERA_HPP__ +#ifndef __OPENCV_STITCHING_CAMERA_HPP__ +#define __OPENCV_STITCHING_CAMERA_HPP__ -#include "precomp.hpp" +#include "opencv2/core/core.hpp" +namespace cv +{ struct CameraParams { @@ -52,8 +54,10 @@ struct CameraParams const CameraParams& operator =(const CameraParams& other); double focal; // Focal length - cv::Mat R; // Rotation - cv::Mat t; // Translation + Mat R; // Rotation + Mat t; // Translation }; -#endif // #ifndef __OPENCV_CAMERA_HPP__ +} // namespace cv + +#endif // #ifndef __OPENCV_STITCHING_CAMERA_HPP__ diff --git a/modules/stitching/exposure_compensate.hpp b/modules/stitching/include/opencv2/stitching/exposure_compensate.hpp similarity index 63% rename from modules/stitching/exposure_compensate.hpp rename to modules/stitching/include/opencv2/stitching/exposure_compensate.hpp index 2c3985149..dcff81294 100644 --- a/modules/stitching/exposure_compensate.hpp +++ b/modules/stitching/include/opencv2/stitching/exposure_compensate.hpp @@ -38,61 +38,65 @@ // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // -//M*/ -#ifndef __OPENCV_EXPOSURE_COMPENSATE_HPP__ -#define __OPENCV_EXPOSURE_COMPENSATE_HPP__ - -#include "precomp.hpp" - - -class ExposureCompensator -{ -public: - enum { NO, GAIN, GAIN_BLOCKS }; - static cv::Ptr createDefault(int type); - - void feed(const std::vector &corners, const std::vector &images, - const std::vector &masks); - virtual void feed(const std::vector &corners, const std::vector &images, - const std::vector > &masks) = 0; - virtual void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask) = 0; -}; - - -class NoExposureCompensator : public ExposureCompensator -{ -public: - void feed(const std::vector &/*corners*/, const std::vector &/*images*/, - const std::vector > &/*masks*/) {}; - void apply(int /*index*/, cv::Point /*corner*/, cv::Mat &/*image*/, const cv::Mat &/*mask*/) {}; -}; - - -class GainCompensator : public ExposureCompensator -{ -public: - void feed(const std::vector &corners, const std::vector &images, - const std::vector > &masks); - void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask); - std::vector gains() const; - -private: - cv::Mat_ gains_; -}; - - -class BlocksGainCompensator : public ExposureCompensator -{ -public: - BlocksGainCompensator(int bl_width = 32, int bl_height = 32) - : bl_width_(bl_width), bl_height_(bl_height) {} - void feed(const std::vector &corners, const std::vector &images, - const std::vector > &masks); - void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask); - -private: - int bl_width_, bl_height_; - std::vector > gain_maps_; -}; - -#endif // __OPENCV_EXPOSURE_COMPENSATE_HPP__ \ No newline at end of file +//M*/ +#ifndef __OPENCV_STITCHING_EXPOSURE_COMPENSATE_HPP__ +#define __OPENCV_STITCHING_EXPOSURE_COMPENSATE_HPP__ + +#include "opencv2/core/core.hpp" + +namespace cv +{ + +class ExposureCompensator +{ +public: + enum { NO, GAIN, GAIN_BLOCKS }; + static Ptr createDefault(int type); + + void feed(const std::vector &corners, const std::vector &images, + const std::vector &masks); + virtual void feed(const std::vector &corners, const std::vector &images, + const std::vector > &masks) = 0; + virtual void apply(int index, Point corner, Mat &image, const Mat &mask) = 0; +}; + + +class NoExposureCompensator : public ExposureCompensator +{ +public: + void feed(const std::vector &/*corners*/, const std::vector &/*images*/, + const std::vector > &/*masks*/) {}; + void apply(int /*index*/, Point /*corner*/, Mat &/*image*/, const Mat &/*mask*/) {}; +}; + + +class GainCompensator : public ExposureCompensator +{ +public: + void feed(const std::vector &corners, const std::vector &images, + const std::vector > &masks); + void apply(int index, Point corner, Mat &image, const Mat &mask); + std::vector gains() const; + +private: + Mat_ gains_; +}; + + +class BlocksGainCompensator : public ExposureCompensator +{ +public: + BlocksGainCompensator(int bl_width = 32, int bl_height = 32) + : bl_width_(bl_width), bl_height_(bl_height) {} + void feed(const std::vector &corners, const std::vector &images, + const std::vector > &masks); + void apply(int index, Point corner, Mat &image, const Mat &mask); + +private: + int bl_width_, bl_height_; + std::vector > gain_maps_; +}; + +} // namespace cv + +#endif // __OPENCV_STITCHING_EXPOSURE_COMPENSATE_HPP__ diff --git a/modules/stitching/matchers.hpp b/modules/stitching/include/opencv2/stitching/matchers.hpp similarity index 92% rename from modules/stitching/matchers.hpp rename to modules/stitching/include/opencv2/stitching/matchers.hpp index f35dcd486..da890c192 100644 --- a/modules/stitching/matchers.hpp +++ b/modules/stitching/include/opencv2/stitching/matchers.hpp @@ -39,10 +39,14 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#ifndef __OPENCV_MATCHERS_HPP__ -#define __OPENCV_MATCHERS_HPP__ +#ifndef __OPENCV_STITCHING_MATCHERS_HPP__ +#define __OPENCV_STITCHING_MATCHERS_HPP__ -#include "precomp.hpp" +#include "opencv2/core/core.hpp" +#include "opencv2/features2d/features2d.hpp" + +namespace cv +{ struct ImageFeatures { @@ -136,4 +140,6 @@ protected: cv::Ptr impl_; }; -#endif // __OPENCV_MATCHERS_HPP__ +} // namespace cv + +#endif // __OPENCV_STITCHING_MATCHERS_HPP__ diff --git a/modules/stitching/motion_estimators.hpp b/modules/stitching/include/opencv2/stitching/motion_estimators.hpp similarity index 90% rename from modules/stitching/motion_estimators.hpp rename to modules/stitching/include/opencv2/stitching/motion_estimators.hpp index 4618563f8..c1b2cab52 100644 --- a/modules/stitching/motion_estimators.hpp +++ b/modules/stitching/include/opencv2/stitching/motion_estimators.hpp @@ -39,14 +39,17 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#ifndef __OPENCV_MOTION_ESTIMATORS_HPP__ -#define __OPENCV_MOTION_ESTIMATORS_HPP__ +#ifndef __OPENCV_STITCHING_MOTION_ESTIMATORS_HPP__ +#define __OPENCV_STITCHING_MOTION_ESTIMATORS_HPP__ -#include "precomp.hpp" +#include "opencv2/core/core.hpp" #include "matchers.hpp" #include "util.hpp" #include "camera.hpp" +namespace cv +{ + class Estimator { public: @@ -88,24 +91,24 @@ private: void estimate(const std::vector &features, const std::vector &pairwise_matches, std::vector &cameras); - void calcError(cv::Mat &err); + void calcError(Mat &err); void calcJacobian(); int num_images_; int total_num_matches_; const ImageFeatures *features_; const MatchesInfo *pairwise_matches_; - cv::Mat cameras_; + Mat cameras_; std::vector > edges_; int cost_space_; float conf_thresh_; - cv::Mat err_, err1_, err2_; - cv::Mat J_; + Mat err_, err1_, err2_; + Mat J_; }; -void waveCorrect(std::vector &rmats); +void waveCorrect(std::vector &rmats); ////////////////////////////////////////////////////////////////////////////// @@ -121,4 +124,6 @@ std::vector leaveBiggestComponent(std::vector &features, std void findMaxSpanningTree(int num_images, const std::vector &pairwise_matches, Graph &span_tree, std::vector ¢ers); -#endif // __OPENCV_MOTION_ESTIMATORS_HPP__ +} // namespace cv + +#endif // __OPENCV_STITCHING_MOTION_ESTIMATORS_HPP__ diff --git a/modules/stitching/seam_finders.hpp b/modules/stitching/include/opencv2/stitching/seam_finders.hpp similarity index 71% rename from modules/stitching/seam_finders.hpp rename to modules/stitching/include/opencv2/stitching/seam_finders.hpp index 66c678d8d..805ace68c 100644 --- a/modules/stitching/seam_finders.hpp +++ b/modules/stitching/include/opencv2/stitching/seam_finders.hpp @@ -38,66 +38,71 @@ // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // -//M*/ -#ifndef __OPENCV_SEAM_FINDERS_HPP__ -#define __OPENCV_SEAM_FINDERS_HPP__ - -#include "precomp.hpp" - -class SeamFinder -{ -public: - enum { NO, VORONOI, GC_COLOR, GC_COLOR_GRAD }; - static cv::Ptr createDefault(int type); - - virtual ~SeamFinder() {} - virtual void find(const std::vector &src, const std::vector &corners, - std::vector &masks) = 0; -}; - - -class NoSeamFinder : public SeamFinder -{ -public: - void find(const std::vector&, const std::vector&, std::vector&) {} -}; - - -class PairwiseSeamFinder : public SeamFinder -{ -public: - virtual void find(const std::vector &src, const std::vector &corners, - std::vector &masks); - -protected: - virtual void findInPair(size_t first, size_t second, cv::Rect roi) = 0; - - std::vector images_; - std::vector corners_; - std::vector masks_; -}; - - -class VoronoiSeamFinder : public PairwiseSeamFinder -{ -private: - void findInPair(size_t first, size_t second, cv::Rect roi); -}; - - -class GraphCutSeamFinder : public SeamFinder -{ -public: - enum { COST_COLOR, COST_COLOR_GRAD }; - GraphCutSeamFinder(int cost_type = COST_COLOR_GRAD, float terminal_cost = 10000.f, - float bad_region_penalty = 1000.f); - - void find(const std::vector &src, const std::vector &corners, - std::vector &masks); - -private: - class Impl; - cv::Ptr impl_; -}; - -#endif // __OPENCV_SEAM_FINDERS_HPP__ +//M*/ +#ifndef __OPENCV_STITCHING_SEAM_FINDERS_HPP__ +#define __OPENCV_STITCHING_SEAM_FINDERS_HPP__ + +#include "opencv2/core/core.hpp" + +namespace cv +{ + +class SeamFinder +{ +public: + enum { NO, VORONOI, GC_COLOR, GC_COLOR_GRAD }; + static Ptr createDefault(int type); + + virtual ~SeamFinder() {} + virtual void find(const std::vector &src, const std::vector &corners, + std::vector &masks) = 0; +}; + + +class NoSeamFinder : public SeamFinder +{ +public: + void find(const std::vector&, const std::vector&, std::vector&) {} +}; + + +class PairwiseSeamFinder : public SeamFinder +{ +public: + virtual void find(const std::vector &src, const std::vector &corners, + std::vector &masks); + +protected: + virtual void findInPair(size_t first, size_t second, Rect roi) = 0; + + std::vector images_; + std::vector corners_; + std::vector masks_; +}; + + +class VoronoiSeamFinder : public PairwiseSeamFinder +{ +private: + void findInPair(size_t first, size_t second, Rect roi); +}; + + +class GraphCutSeamFinder : public SeamFinder +{ +public: + enum { COST_COLOR, COST_COLOR_GRAD }; + GraphCutSeamFinder(int cost_type = COST_COLOR_GRAD, float terminal_cost = 10000.f, + float bad_region_penalty = 1000.f); + + void find(const std::vector &src, const std::vector &corners, + std::vector &masks); + +private: + class Impl; + Ptr impl_; +}; + +} // namespace cv + +#endif // __OPENCV_STITCHING_SEAM_FINDERS_HPP__ diff --git a/modules/stitching/include/opencv2/stitching/stitching.hpp b/modules/stitching/include/opencv2/stitching/stitching.hpp new file mode 100644 index 000000000..0751d0db4 --- /dev/null +++ b/modules/stitching/include/opencv2/stitching/stitching.hpp @@ -0,0 +1,54 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#ifndef __OPENCV_STITCHING_HPP__ +#define __OPENCV_STITCHING_HPP__ + +#include "opencv2/stitching/autocalib.hpp" +#include "opencv2/stitching/blenders.hpp" +#include "opencv2/stitching/camera.hpp" +#include "opencv2/stitching/exposure_compensate.hpp" +#include "opencv2/stitching/matchers.hpp" +#include "opencv2/stitching/motion_estimators.hpp" +#include "opencv2/stitching/seam_finders.hpp" +#include "opencv2/stitching/util.hpp" +#include "opencv2/stitching/warpers.hpp" + +#endif // __OPENCV_STITCHING_HPP__ diff --git a/modules/stitching/util.hpp b/modules/stitching/include/opencv2/stitching/util.hpp similarity index 89% rename from modules/stitching/util.hpp rename to modules/stitching/include/opencv2/stitching/util.hpp index 265a20b6c..2af347f21 100644 --- a/modules/stitching/util.hpp +++ b/modules/stitching/include/opencv2/stitching/util.hpp @@ -43,7 +43,7 @@ #define __OPENCV_STITCHING_UTIL_HPP__ #include -#include "precomp.hpp" +#include "opencv2/core/core.hpp" #define ENABLE_LOG 1 @@ -56,6 +56,8 @@ #define LOGLN(msg) LOG(msg << std::endl) +namespace cv +{ class DisjointSets { @@ -104,14 +106,16 @@ private: ////////////////////////////////////////////////////////////////////////////// // Auxiliary functions -bool overlapRoi(cv::Point tl1, cv::Point tl2, cv::Size sz1, cv::Size sz2, cv::Rect &roi); -cv::Rect resultRoi(const std::vector &corners, const std::vector &images); -cv::Rect resultRoi(const std::vector &corners, const std::vector &sizes); -cv::Point resultTl(const std::vector &corners); +bool overlapRoi(Point tl1, Point tl2, Size sz1, Size sz2, Rect &roi); +Rect resultRoi(const std::vector &corners, const std::vector &images); +Rect resultRoi(const std::vector &corners, const std::vector &sizes); +Point resultTl(const std::vector &corners); // Returns random 'count' element subset of the {0,1,...,size-1} set void selectRandomSubset(int count, int size, std::vector &subset); +} // namespace cv + #include "util_inl.hpp" #endif // __OPENCV_STITCHING_UTIL_HPP__ diff --git a/modules/stitching/util_inl.hpp b/modules/stitching/include/opencv2/stitching/util_inl.hpp similarity index 92% rename from modules/stitching/util_inl.hpp rename to modules/stitching/include/opencv2/stitching/util_inl.hpp index e96b15384..750000242 100644 --- a/modules/stitching/util_inl.hpp +++ b/modules/stitching/include/opencv2/stitching/util_inl.hpp @@ -38,82 +38,88 @@ // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // -//M*/ -#ifndef __OPENCV_STITCHING_UTIL_INL_HPP__ -#define __OPENCV_STITCHING_UTIL_INL_HPP__ - -#include -#include "util.hpp" // Make your IDE see declarations - -template -B Graph::forEach(B body) const -{ - for (int i = 0; i < numVertices(); ++i) - { - std::list::const_iterator edge = edges_[i].begin(); - for (; edge != edges_[i].end(); ++edge) - body(*edge); - } - return body; -} - - -template -B Graph::walkBreadthFirst(int from, B body) const -{ - std::vector was(numVertices(), false); - std::queue vertices; - - was[from] = true; - vertices.push(from); - - while (!vertices.empty()) - { - int vertex = vertices.front(); - vertices.pop(); - - std::list::const_iterator edge = edges_[vertex].begin(); - for (; edge != edges_[vertex].end(); ++edge) - { - if (!was[edge->to]) - { - body(*edge); - was[edge->to] = true; - vertices.push(edge->to); - } - } - } - - return body; -} - - -////////////////////////////////////////////////////////////////////////////// -// Some auxiliary math functions - -static inline -float normL2(const cv::Point3f& a) -{ - return a.x * a.x + a.y * a.y + a.z * a.z; -} - - -static inline -float normL2(const cv::Point3f& a, const cv::Point3f& b) -{ - return normL2(a - b); -} - - -static inline -double normL2sq(const cv::Mat &r) -{ - return r.dot(r); -} - - -static inline int sqr(int x) { return x * x; } -static inline float sqr(float x) { return x * x; } -static inline double sqr(double x) { return x * x; } - -#endif // __OPENCV_STITCHING_UTIL_INL_HPP__ +//M*/ +#ifndef __OPENCV_STITCHING_UTIL_INL_HPP__ +#define __OPENCV_STITCHING_UTIL_INL_HPP__ + +#include +#include "opencv2/core/core.hpp" +#include "util.hpp" // Make your IDE see declarations + +namespace cv +{ + +template +B Graph::forEach(B body) const +{ + for (int i = 0; i < numVertices(); ++i) + { + std::list::const_iterator edge = edges_[i].begin(); + for (; edge != edges_[i].end(); ++edge) + body(*edge); + } + return body; +} + + +template +B Graph::walkBreadthFirst(int from, B body) const +{ + std::vector was(numVertices(), false); + std::queue vertices; + + was[from] = true; + vertices.push(from); + + while (!vertices.empty()) + { + int vertex = vertices.front(); + vertices.pop(); + + std::list::const_iterator edge = edges_[vertex].begin(); + for (; edge != edges_[vertex].end(); ++edge) + { + if (!was[edge->to]) + { + body(*edge); + was[edge->to] = true; + vertices.push(edge->to); + } + } + } + + return body; +} + + +////////////////////////////////////////////////////////////////////////////// +// Some auxiliary math functions + +static inline +float normL2(const Point3f& a) +{ + return a.x * a.x + a.y * a.y + a.z * a.z; +} + + +static inline +float normL2(const Point3f& a, const Point3f& b) +{ + return normL2(a - b); +} + + +static inline +double normL2sq(const Mat &r) +{ + return r.dot(r); +} + + +static inline int sqr(int x) { return x * x; } +static inline float sqr(float x) { return x * x; } +static inline double sqr(double x) { return x * x; } + +} // namespace cv + +#endif // __OPENCV_STITCHING_UTIL_INL_HPP__ diff --git a/modules/stitching/warpers.hpp b/modules/stitching/include/opencv2/stitching/warpers.hpp similarity index 73% rename from modules/stitching/warpers.hpp rename to modules/stitching/include/opencv2/stitching/warpers.hpp index b39fb1a95..9dbadb2f4 100644 --- a/modules/stitching/warpers.hpp +++ b/modules/stitching/include/opencv2/stitching/warpers.hpp @@ -39,29 +39,34 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#ifndef __OPENCV_WARPERS_HPP__ -#define __OPENCV_WARPERS_HPP__ +#ifndef __OPENCV_STITCHING_WARPERS_HPP__ +#define __OPENCV_STITCHING_WARPERS_HPP__ -#include "precomp.hpp" +#include "opencv2/core/core.hpp" +#include "opencv2/imgproc/imgproc.hpp" +#include "opencv2/gpu/gpu.hpp" + +namespace cv +{ class Warper { public: enum { PLANE, CYLINDRICAL, SPHERICAL }; - static cv::Ptr createByCameraFocal(float focal, int type, bool try_gpu = false); + static Ptr createByCameraFocal(float focal, int type, bool try_gpu = false); virtual ~Warper() {} - virtual cv::Point warp(const cv::Mat &src, float focal, const cv::Mat& R, cv::Mat &dst, - int interp_mode = cv::INTER_LINEAR, int border_mode = cv::BORDER_REFLECT) = 0; - virtual cv::Rect warpRoi(const cv::Size &sz, float focal, const cv::Mat &R) = 0; + virtual Point warp(const Mat &src, float focal, const Mat& R, Mat &dst, + int interp_mode = INTER_LINEAR, int border_mode = BORDER_REFLECT) = 0; + virtual Rect warpRoi(const Size &sz, float focal, const Mat &R) = 0; }; struct ProjectorBase { - void setTransformation(const cv::Mat& R); + void setTransformation(const Mat& R); - cv::Size size; + Size size; float focal; float r[9]; float rinv[9]; @@ -73,20 +78,20 @@ template class WarperBase : public Warper { public: - virtual cv::Point warp(const cv::Mat &src, float focal, const cv::Mat &R, cv::Mat &dst, + virtual Point warp(const Mat &src, float focal, const Mat &R, Mat &dst, int interp_mode, int border_mode); - virtual cv::Rect warpRoi(const cv::Size &sz, float focal, const cv::Mat &R); + virtual Rect warpRoi(const Size &sz, float focal, const Mat &R); protected: // Detects ROI of the destination image. It's correct for any projection. - virtual void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br); + virtual void detectResultRoi(Point &dst_tl, Point &dst_br); // Detects ROI of the destination image by walking over image border. // Correctness for any projection isn't guaranteed. - void detectResultRoiByBorder(cv::Point &dst_tl, cv::Point &dst_br); + void detectResultRoiByBorder(Point &dst_tl, Point &dst_br); - cv::Size src_size_; + Size src_size_; P projector_; }; @@ -110,7 +115,7 @@ public: } protected: - void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br); + void detectResultRoi(Point &dst_tl, Point &dst_br); }; @@ -118,11 +123,11 @@ class PlaneWarperGpu : public PlaneWarper { public: PlaneWarperGpu(float plane_dist = 1.f, float scale = 1.f) : PlaneWarper(plane_dist, scale) {} - cv::Point warp(const cv::Mat &src, float focal, const cv::Mat &R, cv::Mat &dst, + Point warp(const Mat &src, float focal, const Mat &R, Mat &dst, int interp_mode, int border_mode); private: - cv::gpu::GpuMat d_xmap_, d_ymap_, d_dst_, d_src_; + gpu::GpuMat d_xmap_, d_ymap_, d_dst_, d_src_; }; @@ -141,7 +146,7 @@ public: SphericalWarper(float scale = 300.f) { projector_.scale = scale; } protected: - void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br); + void detectResultRoi(Point &dst_tl, Point &dst_br); }; @@ -149,11 +154,11 @@ class SphericalWarperGpu : public SphericalWarper { public: SphericalWarperGpu(float scale = 300.f) : SphericalWarper(scale) {} - cv::Point warp(const cv::Mat &src, float focal, const cv::Mat &R, cv::Mat &dst, + Point warp(const Mat &src, float focal, const Mat &R, Mat &dst, int interp_mode, int border_mode); private: - cv::gpu::GpuMat d_xmap_, d_ymap_, d_dst_, d_src_; + gpu::GpuMat d_xmap_, d_ymap_, d_dst_, d_src_; }; @@ -171,7 +176,7 @@ public: CylindricalWarper(float scale = 300.f) { projector_.scale = scale; } protected: - void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br) + void detectResultRoi(Point &dst_tl, Point &dst_br) { WarperBase::detectResultRoiByBorder(dst_tl, dst_br); } @@ -182,13 +187,15 @@ class CylindricalWarperGpu : public CylindricalWarper { public: CylindricalWarperGpu(float scale = 300.f) : CylindricalWarper(scale) {} - cv::Point warp(const cv::Mat &src, float focal, const cv::Mat &R, cv::Mat &dst, + Point warp(const Mat &src, float focal, const Mat &R, Mat &dst, int interp_mode, int border_mode); private: - cv::gpu::GpuMat d_xmap_, d_ymap_, d_dst_, d_src_; + gpu::GpuMat d_xmap_, d_ymap_, d_dst_, d_src_; }; +} // namespace cv + #include "warpers_inl.hpp" -#endif // __OPENCV_WARPERS_HPP__ +#endif // __OPENCV_STITCHING_WARPERS_HPP__ diff --git a/modules/stitching/warpers_inl.hpp b/modules/stitching/include/opencv2/stitching/warpers_inl.hpp similarity index 88% rename from modules/stitching/warpers_inl.hpp rename to modules/stitching/include/opencv2/stitching/warpers_inl.hpp index e385ff1c6..2fd7862a7 100644 --- a/modules/stitching/warpers_inl.hpp +++ b/modules/stitching/include/opencv2/stitching/warpers_inl.hpp @@ -39,13 +39,17 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#ifndef __OPENCV_WARPERS_INL_HPP__ -#define __OPENCV_WARPERS_INL_HPP__ +#ifndef __OPENCV_STITCHING_WARPERS_INL_HPP__ +#define __OPENCV_STITCHING_WARPERS_INL_HPP__ +#include "opencv2/core/core.hpp" #include "warpers.hpp" // Make your IDE see declarations +namespace cv +{ + template -cv::Point WarperBase

::warp(const cv::Mat &src, float focal, const cv::Mat &R, cv::Mat &dst, +Point WarperBase

::warp(const Mat &src, float focal, const Mat &R, Mat &dst, int interp_mode, int border_mode) { src_size_ = src.size(); @@ -54,11 +58,11 @@ cv::Point WarperBase

::warp(const cv::Mat &src, float focal, const cv::Mat &R, projector_.focal = focal; projector_.setTransformation(R); - cv::Point dst_tl, dst_br; + Point dst_tl, dst_br; detectResultRoi(dst_tl, dst_br); - cv::Mat xmap(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F); - cv::Mat ymap(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F); + Mat xmap(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F); + Mat ymap(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F); float x, y; for (int v = dst_tl.y; v <= dst_br.y; ++v) @@ -79,7 +83,7 @@ cv::Point WarperBase

::warp(const cv::Mat &src, float focal, const cv::Mat &R, template -cv::Rect WarperBase

::warpRoi(const cv::Size &sz, float focal, const cv::Mat &R) +Rect WarperBase

::warpRoi(const Size &sz, float focal, const Mat &R) { src_size_ = sz; @@ -87,15 +91,15 @@ cv::Rect WarperBase

::warpRoi(const cv::Size &sz, float focal, const cv::Mat & projector_.focal = focal; projector_.setTransformation(R); - cv::Point dst_tl, dst_br; + Point dst_tl, dst_br; detectResultRoi(dst_tl, dst_br); - return cv::Rect(dst_tl, cv::Point(dst_br.x + 1, dst_br.y + 1)); + return Rect(dst_tl, Point(dst_br.x + 1, dst_br.y + 1)); } template -void WarperBase

::detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br) +void WarperBase

::detectResultRoi(Point &dst_tl, Point &dst_br) { float tl_uf = std::numeric_limits::max(); float tl_vf = std::numeric_limits::max(); @@ -121,7 +125,7 @@ void WarperBase

::detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br) template -void WarperBase

::detectResultRoiByBorder(cv::Point &dst_tl, cv::Point &dst_br) +void WarperBase

::detectResultRoiByBorder(Point &dst_tl, Point &dst_br) { float tl_uf = std::numeric_limits::max(); float tl_vf = std::numeric_limits::max(); @@ -252,4 +256,6 @@ void CylindricalProjector::mapBackward(float u, float v, float &x, float &y) y = focal * y / z + size.height * 0.5f; } -#endif // __OPENCV_WARPERS_INL_HPP__ +} // namespace cv + +#endif // __OPENCV_STITCHING_WARPERS_INL_HPP__ diff --git a/modules/stitching/autocalib.cpp b/modules/stitching/src/autocalib.cpp similarity index 92% rename from modules/stitching/autocalib.cpp rename to modules/stitching/src/autocalib.cpp index 03abb6344..109eb01be 100644 --- a/modules/stitching/autocalib.cpp +++ b/modules/stitching/src/autocalib.cpp @@ -39,11 +39,12 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#include "autocalib.hpp" -#include "util.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; + +namespace cv +{ void focalsFromHomography(const Mat& H, double &f0, double &f1, bool &f0_ok, bool &f1_ok) { @@ -59,8 +60,8 @@ void focalsFromHomography(const Mat& H, double &f0, double &f1, bool &f0_ok, boo d2 = (h[7] - h[6]) * (h[7] + h[6]); v1 = -(h[0] * h[1] + h[3] * h[4]) / d1; v2 = (h[0] * h[0] + h[3] * h[3] - h[1] * h[1] - h[4] * h[4]) / d2; - if (v1 < v2) swap(v1, v2); - if (v1 > 0 && v2 > 0) f1 = sqrt(abs(d1) > abs(d2) ? v1 : v2); + if (v1 < v2) std::swap(v1, v2); + if (v1 > 0 && v2 > 0) f1 = sqrt(std::abs(d1) > std::abs(d2) ? v1 : v2); else if (v1 > 0) f1 = sqrt(v1); else f1_ok = false; @@ -69,8 +70,8 @@ void focalsFromHomography(const Mat& H, double &f0, double &f1, bool &f0_ok, boo d2 = h[0] * h[0] + h[1] * h[1] - h[3] * h[3] - h[4] * h[4]; v1 = -h[2] * h[5] / d1; v2 = (h[5] * h[5] - h[2] * h[2]) / d2; - if (v1 < v2) swap(v1, v2); - if (v1 > 0 && v2 > 0) f0 = sqrt(abs(d1) > abs(d2) ? v1 : v2); + if (v1 < v2) std::swap(v1, v2); + if (v1 > 0 && v2 > 0) f0 = sqrt(std::abs(d1) > std::abs(d2) ? v1 : v2); else if (v1 > 0) f0 = sqrt(v1); else f0_ok = false; } @@ -182,3 +183,5 @@ bool calibrateRotatingCamera(const vector &Hs, Mat &K) K = W.t(); return true; } + +} // namespace cv diff --git a/modules/stitching/blenders.cpp b/modules/stitching/src/blenders.cpp similarity index 95% rename from modules/stitching/blenders.cpp rename to modules/stitching/src/blenders.cpp index 3c35e41a8..ea1d2039f 100644 --- a/modules/stitching/blenders.cpp +++ b/modules/stitching/src/blenders.cpp @@ -39,11 +39,12 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#include "blenders.hpp" -#include "util.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; + +namespace cv +{ static const float WEIGHT_EPS = 1e-5f; @@ -147,7 +148,7 @@ void FeatherBlender::feed(const Mat &img, const Mat &mask, Point tl) void FeatherBlender::blend(Mat &dst, Mat &dst_mask) { - normalize(dst_weight_map_, dst_); + normalizeUsingWeightMap(dst_weight_map_, dst_); dst_mask_ = dst_weight_map_ > WEIGHT_EPS; Blender::blend(dst, dst_mask); } @@ -281,7 +282,7 @@ void MultiBandBlender::feed(const Mat &img, const Mat &mask, Point tl) void MultiBandBlender::blend(Mat &dst, Mat &dst_mask) { for (int i = 0; i <= num_bands_; ++i) - normalize(dst_band_weights_[i], dst_pyr_laplace_[i]); + normalizeUsingWeightMap(dst_band_weights_[i], dst_pyr_laplace_[i]); restoreImageFromLaplacePyr(dst_pyr_laplace_); @@ -299,7 +300,7 @@ void MultiBandBlender::blend(Mat &dst, Mat &dst_mask) ////////////////////////////////////////////////////////////////////////////// // Auxiliary functions -void normalize(const Mat& weight, Mat& src) +void normalizeUsingWeightMap(const Mat& weight, Mat& src) { CV_Assert(weight.type() == CV_32F); CV_Assert(src.type() == CV_16SC3); @@ -374,4 +375,4 @@ void restoreImageFromLaplacePyr(vector &pyr) } } - +} // namespace cv diff --git a/modules/stitching/camera.cpp b/modules/stitching/src/camera.cpp similarity index 85% rename from modules/stitching/camera.cpp rename to modules/stitching/src/camera.cpp index 3c295e722..4c7cea6b5 100644 --- a/modules/stitching/camera.cpp +++ b/modules/stitching/src/camera.cpp @@ -1,9 +1,9 @@ -#include -#include "camera.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; +namespace cv +{ CameraParams::CameraParams() : focal(1), R(Mat::eye(3, 3, CV_64F)), t(Mat::zeros(3, 1, CV_64F)) {} @@ -16,3 +16,5 @@ const CameraParams& CameraParams::operator =(const CameraParams &other) t = other.t.clone(); return *this; } + +} // namespace cv diff --git a/modules/stitching/exposure_compensate.cpp b/modules/stitching/src/exposure_compensate.cpp similarity index 96% rename from modules/stitching/exposure_compensate.cpp rename to modules/stitching/src/exposure_compensate.cpp index 218ac5ee6..017b0df11 100644 --- a/modules/stitching/exposure_compensate.cpp +++ b/modules/stitching/src/exposure_compensate.cpp @@ -39,14 +39,13 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ - -#include "exposure_compensate.hpp" -#include "util.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; using namespace cv::gpu; +namespace cv +{ Ptr ExposureCompensator::createDefault(int type) { @@ -243,3 +242,5 @@ void BlocksGainCompensator::apply(int index, Point /*corner*/, Mat &image, const } } } + +} // namespace cv diff --git a/modules/stitching/matchers.cpp b/modules/stitching/src/matchers.cpp similarity index 94% rename from modules/stitching/matchers.cpp rename to modules/stitching/src/matchers.cpp index 6a61c4b88..d1ed0168a 100644 --- a/modules/stitching/matchers.cpp +++ b/modules/stitching/src/matchers.cpp @@ -39,17 +39,13 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#include -#include -#include "matchers.hpp" -#include "util.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; using namespace cv::gpu; - -////////////////////////////////////////////////////////////////////////////// +namespace cv +{ void FeaturesFinder::operator ()(const Mat &image, ImageFeatures &features) { @@ -58,10 +54,13 @@ void FeaturesFinder::operator ()(const Mat &image, ImageFeatures &features) //features.img = image.clone(); } -////////////////////////////////////////////////////////////////////////////// +} // namespace cv + namespace { + using namespace cv; + class CpuSurfFeaturesFinder : public FeaturesFinder { public: @@ -153,9 +152,12 @@ namespace keypoints_.release(); descriptors_.release(); } -} // anonymous namespace +} // namespace +namespace cv +{ + SurfFeaturesFinder::SurfFeaturesFinder(bool try_use_gpu, double hess_thresh, int num_octaves, int num_layers, int num_octaves_descr, int num_layers_descr) { @@ -240,8 +242,8 @@ struct MatchPairsBody pairwise_matches[dual_pair_idx].H = pairwise_matches[pair_idx].H.inv(); for (size_t j = 0; j < pairwise_matches[dual_pair_idx].matches.size(); ++j) - swap(pairwise_matches[dual_pair_idx].matches[j].queryIdx, - pairwise_matches[dual_pair_idx].matches[j].trainIdx); + std::swap(pairwise_matches[dual_pair_idx].matches[j].queryIdx, + pairwise_matches[dual_pair_idx].matches[j].trainIdx); LOG("."); } } @@ -457,7 +459,7 @@ void BestOf2NearestMatcher::match(const ImageFeatures &features1, const ImageFea // Find pair-wise motion matches_info.H = findHomography(src_points, dst_points, matches_info.inliers_mask, CV_RANSAC); - if (abs(determinant(matches_info.H)) < numeric_limits::epsilon()) + if (std::abs(determinant(matches_info.H)) < numeric_limits::epsilon()) return; // Find number of inliers @@ -504,3 +506,5 @@ void BestOf2NearestMatcher::releaseMemory() { impl_->releaseMemory(); } + +} // namespace cv diff --git a/modules/stitching/motion_estimators.cpp b/modules/stitching/src/motion_estimators.cpp similarity index 96% rename from modules/stitching/motion_estimators.cpp rename to modules/stitching/src/motion_estimators.cpp index 4b658663d..eb891288e 100644 --- a/modules/stitching/motion_estimators.cpp +++ b/modules/stitching/src/motion_estimators.cpp @@ -39,17 +39,12 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#include -#include -#include "autocalib.hpp" -#include "motion_estimators.hpp" -#include "util.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; - -////////////////////////////////////////////////////////////////////////////// +namespace cv +{ struct IncDistance { @@ -605,3 +600,5 @@ void findMaxSpanningTree(int num_images, const vector &pairwise_mat centers.push_back(i); CV_Assert(centers.size() > 0 && centers.size() <= 2); } + +} // namespace cv diff --git a/modules/stitching/precomp.cpp b/modules/stitching/src/precomp.cpp similarity index 100% rename from modules/stitching/precomp.cpp rename to modules/stitching/src/precomp.cpp diff --git a/modules/stitching/precomp.hpp b/modules/stitching/src/precomp.hpp similarity index 84% rename from modules/stitching/precomp.hpp rename to modules/stitching/src/precomp.hpp index 16bdf6048..895966939 100644 --- a/modules/stitching/precomp.hpp +++ b/modules/stitching/src/precomp.hpp @@ -51,10 +51,19 @@ #include #include #include +#include +#include "opencv2/stitching/autocalib.hpp" +#include "opencv2/stitching/blenders.hpp" +#include "opencv2/stitching/camera.hpp" +#include "opencv2/stitching/exposure_compensate.hpp" +#include "opencv2/stitching/matchers.hpp" +#include "opencv2/stitching/motion_estimators.hpp" +#include "opencv2/stitching/seam_finders.hpp" +#include "opencv2/stitching/util.hpp" +#include "opencv2/stitching/warpers.hpp" #include "opencv2/core/core.hpp" #include "opencv2/core/internal.hpp" #include "opencv2/imgproc/imgproc.hpp" -#include "opencv2/highgui/highgui.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/calib3d/calib3d.hpp" #include "opencv2/gpu/gpu.hpp" diff --git a/modules/stitching/seam_finders.cpp b/modules/stitching/src/seam_finders.cpp similarity index 97% rename from modules/stitching/seam_finders.cpp rename to modules/stitching/src/seam_finders.cpp index c9d28678a..7535ff030 100644 --- a/modules/stitching/seam_finders.cpp +++ b/modules/stitching/src/seam_finders.cpp @@ -39,12 +39,12 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#include "seam_finders.hpp" -#include "util.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; +namespace cv +{ Ptr SeamFinder::createDefault(int type) { @@ -405,3 +405,5 @@ void GraphCutSeamFinder::find(const vector &src, const vector &corne { impl_->find(src, corners, masks); } + +} // namespace cv diff --git a/modules/stitching/util.cpp b/modules/stitching/src/util.cpp similarity index 95% rename from modules/stitching/util.cpp rename to modules/stitching/src/util.cpp index 902027144..73ba838ea 100644 --- a/modules/stitching/util.cpp +++ b/modules/stitching/src/util.cpp @@ -39,10 +39,12 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#include "util.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; + +namespace cv +{ void DisjointSets::createOneElemSets(int n) { @@ -161,3 +163,5 @@ void selectRandomSubset(int count, int size, vector &subset) } } } + +} // namespace cv diff --git a/modules/stitching/warpers.cpp b/modules/stitching/src/warpers.cpp similarity index 96% rename from modules/stitching/warpers.cpp rename to modules/stitching/src/warpers.cpp index e9d9cd9ce..638f66d38 100644 --- a/modules/stitching/warpers.cpp +++ b/modules/stitching/src/warpers.cpp @@ -39,10 +39,12 @@ // the use of this software, even if advised of the possibility of such damage. // //M*/ -#include "warpers.hpp" +#include "precomp.hpp" using namespace std; -using namespace cv; + +namespace cv +{ Ptr Warper::createByCameraFocal(float focal, int type, bool try_gpu) { @@ -227,3 +229,5 @@ Point CylindricalWarperGpu::warp(const Mat &src, float focal, const Mat &R, Mat return dst_tl; } + +} // namespace cv diff --git a/samples/cpp/CMakeLists.txt b/samples/cpp/CMakeLists.txt index 3fde7b7fd..2a1ca798f 100644 --- a/samples/cpp/CMakeLists.txt +++ b/samples/cpp/CMakeLists.txt @@ -18,6 +18,8 @@ if (BUILD_EXAMPLES) "${CMAKE_SOURCE_DIR}/modules/objdetect/include" "${CMAKE_SOURCE_DIR}/modules/legacy/include" "${CMAKE_SOURCE_DIR}/modules/contrib/include" + "${CMAKE_SOURCE_DIR}/modules/stitching/include" + "${CMAKE_SOURCE_DIR}/modules/gpu/include" ) if(CMAKE_COMPILER_IS_GNUCXX) @@ -35,10 +37,10 @@ if (BUILD_EXAMPLES) PROJECT_LABEL "(EXAMPLE) ${name}") add_dependencies(${the_target} opencv_core opencv_flann opencv_imgproc opencv_highgui opencv_ml opencv_video opencv_objdetect opencv_features2d - opencv_calib3d opencv_legacy opencv_contrib) + opencv_calib3d opencv_legacy opencv_contrib opencv_stitching opencv_gpu) target_link_libraries(${the_target} ${OPENCV_LINKER_LIBS} opencv_core opencv_flann opencv_imgproc opencv_highgui opencv_ml opencv_video opencv_objdetect - opencv_features2d opencv_calib3d opencv_legacy opencv_contrib) + opencv_features2d opencv_calib3d opencv_legacy opencv_contrib opencv_stitching opencv_gpu) if(ENABLE_SOLUTION_FOLDERS) set_target_properties(${the_target} PROPERTIES FOLDER "samples//cpp") diff --git a/modules/stitching/main.cpp b/samples/cpp/stitching.cpp similarity index 92% rename from modules/stitching/main.cpp rename to samples/cpp/stitching.cpp index d46522961..4d2215662 100644 --- a/modules/stitching/main.cpp +++ b/samples/cpp/stitching.cpp @@ -1,624 +1,618 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -// We follow to these papers: -// 1) Construction of panoramic mosaics with global and local alignment. -// Heung-Yeung Shum and Richard Szeliski. 2000. -// 2) Eliminating Ghosting and Exposure Artifacts in Image Mosaics. -// Matthew Uyttendaele, Ashley Eden and Richard Szeliski. 2001. -// 3) Automatic Panoramic Image Stitching using Invariant Features. -// Matthew Brown and David G. Lowe. 2007. - -#include -#include "precomp.hpp" -#include "util.hpp" -#include "warpers.hpp" -#include "blenders.hpp" -#include "seam_finders.hpp" -#include "motion_estimators.hpp" -#include "exposure_compensate.hpp" -#include "camera.hpp" - -using namespace std; -using namespace cv; - -void printUsage() -{ - cout << - "Rotation model images stitcher.\n\n" - "opencv_stitching img1 img2 [...imgN] [flags]\n\n" - "Flags:\n" - " --preview\n" - " Run stitching in the preview mode. Works faster than usual mode,\n" - " but output image will have lower resolution.\n" - " --try_gpu (yes|no)\n" - " Try to use GPU. The default value is 'no'. All default values\n" - " are for CPU mode.\n" - "\nMotion Estimation Flags:\n" - " --work_megapix \n" - " Resolution for image registration step. The default is 0.6 Mpx.\n" - " --match_conf \n" - " Confidence for feature matching step. The default is 0.65.\n" - " --conf_thresh \n" - " Threshold for two images are from the same panorama confidence.\n" - " The default is 1.0.\n" - " --ba (no|ray|focal_ray)\n" - " Bundle adjustment cost function. The default is 'focal_ray'.\n" - " --wave_correct (no|yes)\n" - " Perform wave effect correction. The default is 'yes'.\n" - " --save_graph \n" - " Save matches graph represented in DOT language to file.\n" - " Labels description: Nm is number of matches, Ni is number of inliers,\n" - " C is confidence.\n" - "\nCompositing Flags:\n" - " --warp (plane|cylindrical|spherical)\n" - " Warp surface type. The default is 'spherical'.\n" - " --seam_megapix \n" - " Resolution for seam estimation step. The default is 0.1 Mpx.\n" - " --seam (no|voronoi|gc_color|gc_colorgrad)\n" - " Seam estimation method. The default is 'gc_color'.\n" - " --compose_megapix \n" - " Resolution for compositing step. Use -1 for original resolution.\n" - " The default is -1.\n" - " --expos_comp (no|gain|gain_blocks)\n" - " Exposure compensation method. The default is 'gain_blocks'.\n" - " --blend (no|feather|multiband)\n" - " Blending method. The default is 'multiband'.\n" - " --blend_strength \n" - " Blending strength from [0,100] range. The default is 5.\n" - " --output \n" - " The default is 'result.jpg'.\n"; -} - - -// Default command line args -vector img_names; -bool preview = false; -bool try_gpu = false; -double work_megapix = 0.6; -double seam_megapix = 0.1; -double compose_megapix = -1; -int ba_space = BundleAdjuster::FOCAL_RAY_SPACE; -float conf_thresh = 1.f; -bool wave_correct = true; -bool save_graph = false; -std::string save_graph_to; -int warp_type = Warper::SPHERICAL; -int expos_comp_type = ExposureCompensator::GAIN_BLOCKS; -float match_conf = 0.65f; -int seam_find_type = SeamFinder::GC_COLOR; -int blend_type = Blender::MULTI_BAND; -float blend_strength = 5; -string result_name = "result.jpg"; - -int parseCmdArgs(int argc, char** argv) -{ - if (argc == 1) - { - printUsage(); - return -1; - } - for (int i = 1; i < argc; ++i) - { - if (string(argv[i]) == "--help" || string(argv[i]) == "/?") - { - printUsage(); - return -1; - } - else if (string(argv[i]) == "--preview") - { - preview = true; - } - else if (string(argv[i]) == "--try_gpu") - { - if (string(argv[i + 1]) == "no") - try_gpu = false; - else if (string(argv[i + 1]) == "yes") - try_gpu = true; - else - { - cout << "Bad --try_gpu flag value\n"; - return -1; - } - i++; - } - else if (string(argv[i]) == "--work_megapix") - { - work_megapix = atof(argv[i + 1]); - i++; - } - else if (string(argv[i]) == "--seam_megapix") - { - seam_megapix = atof(argv[i + 1]); - i++; - } - else if (string(argv[i]) == "--compose_megapix") - { - compose_megapix = atof(argv[i + 1]); - i++; - } - else if (string(argv[i]) == "--result") - { - result_name = argv[i + 1]; - i++; - } - else if (string(argv[i]) == "--match_conf") - { - match_conf = static_cast(atof(argv[i + 1])); - i++; - } - else if (string(argv[i]) == "--ba") - { - if (string(argv[i + 1]) == "no") - ba_space = BundleAdjuster::NO; - else if (string(argv[i + 1]) == "ray") - ba_space = BundleAdjuster::RAY_SPACE; - else if (string(argv[i + 1]) == "focal_ray") - ba_space = BundleAdjuster::FOCAL_RAY_SPACE; - else - { - cout << "Bad bundle adjustment space\n"; - return -1; - } - i++; - } - else if (string(argv[i]) == "--conf_thresh") - { - conf_thresh = static_cast(atof(argv[i + 1])); - i++; - } - else if (string(argv[i]) == "--wave_correct") - { - if (string(argv[i + 1]) == "no") - wave_correct = false; - else if (string(argv[i + 1]) == "yes") - wave_correct = true; - else - { - cout << "Bad --wave_correct flag value\n"; - return -1; - } - i++; - } - else if (string(argv[i]) == "--save_graph") - { - save_graph = true; - save_graph_to = argv[i + 1]; - i++; - } - else if (string(argv[i]) == "--warp") - { - if (string(argv[i + 1]) == "plane") - warp_type = Warper::PLANE; - else if (string(argv[i + 1]) == "cylindrical") - warp_type = Warper::CYLINDRICAL; - else if (string(argv[i + 1]) == "spherical") - warp_type = Warper::SPHERICAL; - else - { - cout << "Bad warping method\n"; - return -1; - } - i++; - } - else if (string(argv[i]) == "--expos_comp") - { - if (string(argv[i + 1]) == "no") - expos_comp_type = ExposureCompensator::NO; - else if (string(argv[i + 1]) == "gain") - expos_comp_type = ExposureCompensator::GAIN; - else if (string(argv[i + 1]) == "gain_blocks") - expos_comp_type = ExposureCompensator::GAIN_BLOCKS; - else - { - cout << "Bad exposure compensation method\n"; - return -1; - } - i++; - } - else if (string(argv[i]) == "--seam") - { - if (string(argv[i + 1]) == "no") - seam_find_type = SeamFinder::NO; - else if (string(argv[i + 1]) == "voronoi") - seam_find_type = SeamFinder::VORONOI; - else if (string(argv[i + 1]) == "gc_color") - seam_find_type = SeamFinder::GC_COLOR; - else if (string(argv[i + 1]) == "gc_colorgrad") - seam_find_type = SeamFinder::GC_COLOR_GRAD; - else - { - cout << "Bad seam finding method\n"; - return -1; - } - i++; - } - else if (string(argv[i]) == "--blend") - { - if (string(argv[i + 1]) == "no") - blend_type = Blender::NO; - else if (string(argv[i + 1]) == "feather") - blend_type = Blender::FEATHER; - else if (string(argv[i + 1]) == "multiband") - blend_type = Blender::MULTI_BAND; - else - { - cout << "Bad blending method\n"; - return -1; - } - i++; - } - else if (string(argv[i]) == "--blend_strength") - { - blend_strength = static_cast(atof(argv[i + 1])); - i++; - } - else if (string(argv[i]) == "--output") - { - result_name = argv[i + 1]; - i++; - } - else - img_names.push_back(argv[i]); - } - if (preview) - { - compose_megapix = 0.6; - } - return 0; -} - - -int main(int argc, char* argv[]) -{ - int64 app_start_time = getTickCount(); - cv::setBreakOnError(true); - - int retval = parseCmdArgs(argc, argv); - if (retval) - return retval; - - // Check if have enough images - int num_images = static_cast(img_names.size()); - if (num_images < 2) - { - LOGLN("Need more images"); - return -1; - } - - double work_scale = 1, seam_scale = 1, compose_scale = 1; - bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false; - - LOGLN("Finding features..."); - int64 t = getTickCount(); - - vector features(num_images); - SurfFeaturesFinder finder(try_gpu); - Mat full_img, img; - - vector images(num_images); - vector full_img_sizes(num_images); - double seam_work_aspect = 1; - - for (int i = 0; i < num_images; ++i) - { - full_img = imread(img_names[i]); - full_img_sizes[i] = full_img.size(); - - if (full_img.empty()) - { - LOGLN("Can't open image " << img_names[i]); - return -1; - } - if (work_megapix < 0) - { - img = full_img; - work_scale = 1; - is_work_scale_set = true; - } - else - { - if (!is_work_scale_set) - { - work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area())); - is_work_scale_set = true; - } - resize(full_img, img, Size(), work_scale, work_scale); - } - if (!is_seam_scale_set) - { - seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area())); - seam_work_aspect = seam_scale / work_scale; - is_seam_scale_set = true; - } - - finder(img, features[i]); - features[i].img_idx = i; - LOGLN("Features in image #" << i+1 << ": " << features[i].keypoints.size()); - - resize(full_img, img, Size(), seam_scale, seam_scale); - images[i] = img.clone(); - } - - finder.releaseMemory(); - - full_img.release(); - img.release(); - - LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); - - LOG("Pairwise matching"); - t = getTickCount(); - vector pairwise_matches; - BestOf2NearestMatcher matcher(try_gpu, match_conf); - matcher(features, pairwise_matches); - matcher.releaseMemory(); - LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); - - // Check if we should save matches graph - if (save_graph) - { - LOGLN("Saving matches graph..."); - ofstream f(save_graph_to.c_str()); - f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh); - } - - // Leave only images we are sure are from the same panorama - vector indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh); - vector img_subset; - vector img_names_subset; - vector full_img_sizes_subset; - for (size_t i = 0; i < indices.size(); ++i) - { - img_names_subset.push_back(img_names[indices[i]]); - img_subset.push_back(images[indices[i]]); - full_img_sizes_subset.push_back(full_img_sizes[indices[i]]); - } - - images = img_subset; - img_names = img_names_subset; - full_img_sizes = full_img_sizes_subset; - - // Check if we still have enough images - num_images = static_cast(img_names.size()); - if (num_images < 2) - { - LOGLN("Need more images"); - return -1; - } - - HomographyBasedEstimator estimator; - vector cameras; - estimator(features, pairwise_matches, cameras); - - for (size_t i = 0; i < cameras.size(); ++i) - { - Mat R; - cameras[i].R.convertTo(R, CV_32F); - cameras[i].R = R; - LOGLN("Initial focal length #" << indices[i]+1 << ": " << cameras[i].focal); - } - - BundleAdjuster adjuster(ba_space, conf_thresh); - adjuster(features, pairwise_matches, cameras); - - // Find median focal length - vector focals; - for (size_t i = 0; i < cameras.size(); ++i) - { - LOGLN("Camera #" << indices[i]+1 << " focal length: " << cameras[i].focal); - focals.push_back(cameras[i].focal); - } - nth_element(focals.begin(), focals.begin() + focals.size()/2, focals.end()); - float warped_image_scale = static_cast(focals[focals.size() / 2]); - - if (wave_correct) - { - vector rmats; - for (size_t i = 0; i < cameras.size(); ++i) - rmats.push_back(cameras[i].R); - waveCorrect(rmats); - for (size_t i = 0; i < cameras.size(); ++i) - cameras[i].R = rmats[i]; - } - - LOGLN("Warping images (auxiliary)... "); - t = getTickCount(); - - vector corners(num_images); - vector masks_warped(num_images); - vector images_warped(num_images); - vector sizes(num_images); - vector masks(num_images); - - // Preapre images masks - for (int i = 0; i < num_images; ++i) - { - masks[i].create(images[i].size(), CV_8U); - masks[i].setTo(Scalar::all(255)); - } - - // Warp images and their masks - Ptr warper = Warper::createByCameraFocal(static_cast(warped_image_scale * seam_work_aspect), - warp_type, try_gpu); - for (int i = 0; i < num_images; ++i) - { - corners[i] = warper->warp(images[i], static_cast(cameras[i].focal * seam_work_aspect), - cameras[i].R, images_warped[i]); - sizes[i] = images_warped[i].size(); - warper->warp(masks[i], static_cast(cameras[i].focal * seam_work_aspect), - cameras[i].R, masks_warped[i], INTER_NEAREST, BORDER_CONSTANT); - } - - vector images_warped_f(num_images); - for (int i = 0; i < num_images; ++i) - images_warped[i].convertTo(images_warped_f[i], CV_32F); - - LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); - - Ptr compensator = ExposureCompensator::createDefault(expos_comp_type); - compensator->feed(corners, images_warped, masks_warped); - - Ptr seam_finder = SeamFinder::createDefault(seam_find_type); - seam_finder->find(images_warped_f, corners, masks_warped); - - // Release unused memory - images.clear(); - images_warped.clear(); - images_warped_f.clear(); - masks.clear(); - - LOGLN("Compositing..."); - t = getTickCount(); - - Mat img_warped, img_warped_s; - Mat dilated_mask, seam_mask, mask, mask_warped; - Ptr blender; - double compose_seam_aspect = 1; - double compose_work_aspect = 1; - - for (int img_idx = 0; img_idx < num_images; ++img_idx) - { - LOGLN("Compositing image #" << indices[img_idx]+1); - - // Read image and resize it if necessary - full_img = imread(img_names[img_idx]); - if (!is_compose_scale_set) - { - if (compose_megapix > 0) - compose_scale = min(1.0, sqrt(compose_megapix * 1e6 / full_img.size().area())); - is_compose_scale_set = true; - - // Compute relative scales - compose_seam_aspect = compose_scale / seam_scale; - compose_work_aspect = compose_scale / work_scale; - - // Update warped image scale - warped_image_scale *= static_cast(compose_work_aspect); - warper = Warper::createByCameraFocal(warped_image_scale, warp_type, try_gpu); - - // Update corners and sizes - for (int i = 0; i < num_images; ++i) - { - // Update camera focal - cameras[i].focal *= compose_work_aspect; - - // Update corner and size - Size sz = full_img_sizes[i]; - if (abs(compose_scale - 1) > 1e-1) - { - sz.width = cvRound(full_img_sizes[i].width * compose_scale); - sz.height = cvRound(full_img_sizes[i].height * compose_scale); - } - - Rect roi = warper->warpRoi(sz, static_cast(cameras[i].focal), cameras[i].R); - corners[i] = roi.tl(); - sizes[i] = roi.size(); - } - } - if (abs(compose_scale - 1) > 1e-1) - resize(full_img, img, Size(), compose_scale, compose_scale); - else - img = full_img; - full_img.release(); - Size img_size = img.size(); - - // Warp the current image - warper->warp(img, static_cast(cameras[img_idx].focal), cameras[img_idx].R, - img_warped); - - // Warp the current image mask - mask.create(img_size, CV_8U); - mask.setTo(Scalar::all(255)); - warper->warp(mask, static_cast(cameras[img_idx].focal), cameras[img_idx].R, mask_warped, - INTER_NEAREST, BORDER_CONSTANT); - - // Compensate exposure - compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped); - - img_warped.convertTo(img_warped_s, CV_16S); - img_warped.release(); - img.release(); - mask.release(); - - dilate(masks_warped[img_idx], dilated_mask, Mat()); - resize(dilated_mask, seam_mask, mask_warped.size()); - mask_warped = seam_mask & mask_warped; - - if (blender.empty()) - { - blender = Blender::createDefault(blend_type, try_gpu); - Size dst_sz = resultRoi(corners, sizes).size(); - float blend_width = sqrt(static_cast(dst_sz.area())) * blend_strength / 100.f; - if (blend_width < 1.f) - blender = Blender::createDefault(Blender::NO, try_gpu); - else if (blend_type == Blender::MULTI_BAND) - { - MultiBandBlender* mb = dynamic_cast(static_cast(blender)); - mb->setNumBands(static_cast(ceil(log(blend_width)/log(2.)) - 1.)); - LOGLN("Multi-band blender, number of bands: " << mb->numBands()); - } - else if (blend_type == Blender::FEATHER) - { - FeatherBlender* fb = dynamic_cast(static_cast(blender)); - fb->setSharpness(1.f/blend_width); - LOGLN("Feather blender, sharpness: " << fb->sharpness()); - } - blender->prepare(corners, sizes); - } - - // Blend the current image - blender->feed(img_warped_s, mask_warped, corners[img_idx]); - } - - Mat result, result_mask; - blender->blend(result, result_mask); - - LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); - - imwrite(result_name, result); - - LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec"); - return 0; -} - - +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +// We follow to these papers: +// 1) Construction of panoramic mosaics with global and local alignment. +// Heung-Yeung Shum and Richard Szeliski. 2000. +// 2) Eliminating Ghosting and Exposure Artifacts in Image Mosaics. +// Matthew Uyttendaele, Ashley Eden and Richard Szeliski. 2001. +// 3) Automatic Panoramic Image Stitching using Invariant Features. +// Matthew Brown and David G. Lowe. 2007. + +#include +#include "opencv2/stitching/stitching.hpp" +#include "opencv2/highgui/highgui.hpp" + +using namespace std; +using namespace cv; + +void printUsage() +{ + cout << + "Rotation model images stitcher.\n\n" + "stitching img1 img2 [...imgN] [flags]\n\n" + "Flags:\n" + " --preview\n" + " Run stitching in the preview mode. Works faster than usual mode,\n" + " but output image will have lower resolution.\n" + " --try_gpu (yes|no)\n" + " Try to use GPU. The default value is 'no'. All default values\n" + " are for CPU mode.\n" + "\nMotion Estimation Flags:\n" + " --work_megapix \n" + " Resolution for image registration step. The default is 0.6 Mpx.\n" + " --match_conf \n" + " Confidence for feature matching step. The default is 0.65.\n" + " --conf_thresh \n" + " Threshold for two images are from the same panorama confidence.\n" + " The default is 1.0.\n" + " --ba (no|ray|focal_ray)\n" + " Bundle adjustment cost function. The default is 'focal_ray'.\n" + " --wave_correct (no|yes)\n" + " Perform wave effect correction. The default is 'yes'.\n" + " --save_graph \n" + " Save matches graph represented in DOT language to file.\n" + " Labels description: Nm is number of matches, Ni is number of inliers,\n" + " C is confidence.\n" + "\nCompositing Flags:\n" + " --warp (plane|cylindrical|spherical)\n" + " Warp surface type. The default is 'spherical'.\n" + " --seam_megapix \n" + " Resolution for seam estimation step. The default is 0.1 Mpx.\n" + " --seam (no|voronoi|gc_color|gc_colorgrad)\n" + " Seam estimation method. The default is 'gc_color'.\n" + " --compose_megapix \n" + " Resolution for compositing step. Use -1 for original resolution.\n" + " The default is -1.\n" + " --expos_comp (no|gain|gain_blocks)\n" + " Exposure compensation method. The default is 'gain_blocks'.\n" + " --blend (no|feather|multiband)\n" + " Blending method. The default is 'multiband'.\n" + " --blend_strength \n" + " Blending strength from [0,100] range. The default is 5.\n" + " --output \n" + " The default is 'result.jpg'.\n"; +} + + +// Default command line args +vector img_names; +bool preview = false; +bool try_gpu = false; +double work_megapix = 0.6; +double seam_megapix = 0.1; +double compose_megapix = -1; +int ba_space = BundleAdjuster::FOCAL_RAY_SPACE; +float conf_thresh = 1.f; +bool wave_correct = true; +bool save_graph = false; +std::string save_graph_to; +int warp_type = Warper::SPHERICAL; +int expos_comp_type = ExposureCompensator::GAIN_BLOCKS; +float match_conf = 0.65f; +int seam_find_type = SeamFinder::GC_COLOR; +int blend_type = Blender::MULTI_BAND; +float blend_strength = 5; +string result_name = "result.jpg"; + +int parseCmdArgs(int argc, char** argv) +{ + if (argc == 1) + { + printUsage(); + return -1; + } + for (int i = 1; i < argc; ++i) + { + if (string(argv[i]) == "--help" || string(argv[i]) == "/?") + { + printUsage(); + return -1; + } + else if (string(argv[i]) == "--preview") + { + preview = true; + } + else if (string(argv[i]) == "--try_gpu") + { + if (string(argv[i + 1]) == "no") + try_gpu = false; + else if (string(argv[i + 1]) == "yes") + try_gpu = true; + else + { + cout << "Bad --try_gpu flag value\n"; + return -1; + } + i++; + } + else if (string(argv[i]) == "--work_megapix") + { + work_megapix = atof(argv[i + 1]); + i++; + } + else if (string(argv[i]) == "--seam_megapix") + { + seam_megapix = atof(argv[i + 1]); + i++; + } + else if (string(argv[i]) == "--compose_megapix") + { + compose_megapix = atof(argv[i + 1]); + i++; + } + else if (string(argv[i]) == "--result") + { + result_name = argv[i + 1]; + i++; + } + else if (string(argv[i]) == "--match_conf") + { + match_conf = static_cast(atof(argv[i + 1])); + i++; + } + else if (string(argv[i]) == "--ba") + { + if (string(argv[i + 1]) == "no") + ba_space = BundleAdjuster::NO; + else if (string(argv[i + 1]) == "ray") + ba_space = BundleAdjuster::RAY_SPACE; + else if (string(argv[i + 1]) == "focal_ray") + ba_space = BundleAdjuster::FOCAL_RAY_SPACE; + else + { + cout << "Bad bundle adjustment space\n"; + return -1; + } + i++; + } + else if (string(argv[i]) == "--conf_thresh") + { + conf_thresh = static_cast(atof(argv[i + 1])); + i++; + } + else if (string(argv[i]) == "--wave_correct") + { + if (string(argv[i + 1]) == "no") + wave_correct = false; + else if (string(argv[i + 1]) == "yes") + wave_correct = true; + else + { + cout << "Bad --wave_correct flag value\n"; + return -1; + } + i++; + } + else if (string(argv[i]) == "--save_graph") + { + save_graph = true; + save_graph_to = argv[i + 1]; + i++; + } + else if (string(argv[i]) == "--warp") + { + if (string(argv[i + 1]) == "plane") + warp_type = Warper::PLANE; + else if (string(argv[i + 1]) == "cylindrical") + warp_type = Warper::CYLINDRICAL; + else if (string(argv[i + 1]) == "spherical") + warp_type = Warper::SPHERICAL; + else + { + cout << "Bad warping method\n"; + return -1; + } + i++; + } + else if (string(argv[i]) == "--expos_comp") + { + if (string(argv[i + 1]) == "no") + expos_comp_type = ExposureCompensator::NO; + else if (string(argv[i + 1]) == "gain") + expos_comp_type = ExposureCompensator::GAIN; + else if (string(argv[i + 1]) == "gain_blocks") + expos_comp_type = ExposureCompensator::GAIN_BLOCKS; + else + { + cout << "Bad exposure compensation method\n"; + return -1; + } + i++; + } + else if (string(argv[i]) == "--seam") + { + if (string(argv[i + 1]) == "no") + seam_find_type = SeamFinder::NO; + else if (string(argv[i + 1]) == "voronoi") + seam_find_type = SeamFinder::VORONOI; + else if (string(argv[i + 1]) == "gc_color") + seam_find_type = SeamFinder::GC_COLOR; + else if (string(argv[i + 1]) == "gc_colorgrad") + seam_find_type = SeamFinder::GC_COLOR_GRAD; + else + { + cout << "Bad seam finding method\n"; + return -1; + } + i++; + } + else if (string(argv[i]) == "--blend") + { + if (string(argv[i + 1]) == "no") + blend_type = Blender::NO; + else if (string(argv[i + 1]) == "feather") + blend_type = Blender::FEATHER; + else if (string(argv[i + 1]) == "multiband") + blend_type = Blender::MULTI_BAND; + else + { + cout << "Bad blending method\n"; + return -1; + } + i++; + } + else if (string(argv[i]) == "--blend_strength") + { + blend_strength = static_cast(atof(argv[i + 1])); + i++; + } + else if (string(argv[i]) == "--output") + { + result_name = argv[i + 1]; + i++; + } + else + img_names.push_back(argv[i]); + } + if (preview) + { + compose_megapix = 0.6; + } + return 0; +} + + +int main(int argc, char* argv[]) +{ + int64 app_start_time = getTickCount(); + cv::setBreakOnError(true); + + int retval = parseCmdArgs(argc, argv); + if (retval) + return retval; + + // Check if have enough images + int num_images = static_cast(img_names.size()); + if (num_images < 2) + { + LOGLN("Need more images"); + return -1; + } + + double work_scale = 1, seam_scale = 1, compose_scale = 1; + bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false; + + LOGLN("Finding features..."); + int64 t = getTickCount(); + + vector features(num_images); + SurfFeaturesFinder finder(try_gpu); + Mat full_img, img; + + vector images(num_images); + vector full_img_sizes(num_images); + double seam_work_aspect = 1; + + for (int i = 0; i < num_images; ++i) + { + full_img = imread(img_names[i]); + full_img_sizes[i] = full_img.size(); + + if (full_img.empty()) + { + LOGLN("Can't open image " << img_names[i]); + return -1; + } + if (work_megapix < 0) + { + img = full_img; + work_scale = 1; + is_work_scale_set = true; + } + else + { + if (!is_work_scale_set) + { + work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area())); + is_work_scale_set = true; + } + resize(full_img, img, Size(), work_scale, work_scale); + } + if (!is_seam_scale_set) + { + seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area())); + seam_work_aspect = seam_scale / work_scale; + is_seam_scale_set = true; + } + + finder(img, features[i]); + features[i].img_idx = i; + LOGLN("Features in image #" << i+1 << ": " << features[i].keypoints.size()); + + resize(full_img, img, Size(), seam_scale, seam_scale); + images[i] = img.clone(); + } + + finder.releaseMemory(); + + full_img.release(); + img.release(); + + LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); + + LOG("Pairwise matching"); + t = getTickCount(); + vector pairwise_matches; + BestOf2NearestMatcher matcher(try_gpu, match_conf); + matcher(features, pairwise_matches); + matcher.releaseMemory(); + LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); + + // Check if we should save matches graph + if (save_graph) + { + LOGLN("Saving matches graph..."); + ofstream f(save_graph_to.c_str()); + f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh); + } + + // Leave only images we are sure are from the same panorama + vector indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh); + vector img_subset; + vector img_names_subset; + vector full_img_sizes_subset; + for (size_t i = 0; i < indices.size(); ++i) + { + img_names_subset.push_back(img_names[indices[i]]); + img_subset.push_back(images[indices[i]]); + full_img_sizes_subset.push_back(full_img_sizes[indices[i]]); + } + + images = img_subset; + img_names = img_names_subset; + full_img_sizes = full_img_sizes_subset; + + // Check if we still have enough images + num_images = static_cast(img_names.size()); + if (num_images < 2) + { + LOGLN("Need more images"); + return -1; + } + + HomographyBasedEstimator estimator; + vector cameras; + estimator(features, pairwise_matches, cameras); + + for (size_t i = 0; i < cameras.size(); ++i) + { + Mat R; + cameras[i].R.convertTo(R, CV_32F); + cameras[i].R = R; + LOGLN("Initial focal length #" << indices[i]+1 << ": " << cameras[i].focal); + } + + BundleAdjuster adjuster(ba_space, conf_thresh); + adjuster(features, pairwise_matches, cameras); + + // Find median focal length + vector focals; + for (size_t i = 0; i < cameras.size(); ++i) + { + LOGLN("Camera #" << indices[i]+1 << " focal length: " << cameras[i].focal); + focals.push_back(cameras[i].focal); + } + nth_element(focals.begin(), focals.begin() + focals.size()/2, focals.end()); + float warped_image_scale = static_cast(focals[focals.size() / 2]); + + if (wave_correct) + { + vector rmats; + for (size_t i = 0; i < cameras.size(); ++i) + rmats.push_back(cameras[i].R); + waveCorrect(rmats); + for (size_t i = 0; i < cameras.size(); ++i) + cameras[i].R = rmats[i]; + } + + LOGLN("Warping images (auxiliary)... "); + t = getTickCount(); + + vector corners(num_images); + vector masks_warped(num_images); + vector images_warped(num_images); + vector sizes(num_images); + vector masks(num_images); + + // Preapre images masks + for (int i = 0; i < num_images; ++i) + { + masks[i].create(images[i].size(), CV_8U); + masks[i].setTo(Scalar::all(255)); + } + + // Warp images and their masks + Ptr warper = Warper::createByCameraFocal(static_cast(warped_image_scale * seam_work_aspect), + warp_type, try_gpu); + for (int i = 0; i < num_images; ++i) + { + corners[i] = warper->warp(images[i], static_cast(cameras[i].focal * seam_work_aspect), + cameras[i].R, images_warped[i]); + sizes[i] = images_warped[i].size(); + warper->warp(masks[i], static_cast(cameras[i].focal * seam_work_aspect), + cameras[i].R, masks_warped[i], INTER_NEAREST, BORDER_CONSTANT); + } + + vector images_warped_f(num_images); + for (int i = 0; i < num_images; ++i) + images_warped[i].convertTo(images_warped_f[i], CV_32F); + + LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); + + Ptr compensator = ExposureCompensator::createDefault(expos_comp_type); + compensator->feed(corners, images_warped, masks_warped); + + Ptr seam_finder = SeamFinder::createDefault(seam_find_type); + seam_finder->find(images_warped_f, corners, masks_warped); + + // Release unused memory + images.clear(); + images_warped.clear(); + images_warped_f.clear(); + masks.clear(); + + LOGLN("Compositing..."); + t = getTickCount(); + + Mat img_warped, img_warped_s; + Mat dilated_mask, seam_mask, mask, mask_warped; + Ptr blender; + double compose_seam_aspect = 1; + double compose_work_aspect = 1; + + for (int img_idx = 0; img_idx < num_images; ++img_idx) + { + LOGLN("Compositing image #" << indices[img_idx]+1); + + // Read image and resize it if necessary + full_img = imread(img_names[img_idx]); + if (!is_compose_scale_set) + { + if (compose_megapix > 0) + compose_scale = min(1.0, sqrt(compose_megapix * 1e6 / full_img.size().area())); + is_compose_scale_set = true; + + // Compute relative scales + compose_seam_aspect = compose_scale / seam_scale; + compose_work_aspect = compose_scale / work_scale; + + // Update warped image scale + warped_image_scale *= static_cast(compose_work_aspect); + warper = Warper::createByCameraFocal(warped_image_scale, warp_type, try_gpu); + + // Update corners and sizes + for (int i = 0; i < num_images; ++i) + { + // Update camera focal + cameras[i].focal *= compose_work_aspect; + + // Update corner and size + Size sz = full_img_sizes[i]; + if (abs(compose_scale - 1) > 1e-1) + { + sz.width = cvRound(full_img_sizes[i].width * compose_scale); + sz.height = cvRound(full_img_sizes[i].height * compose_scale); + } + + Rect roi = warper->warpRoi(sz, static_cast(cameras[i].focal), cameras[i].R); + corners[i] = roi.tl(); + sizes[i] = roi.size(); + } + } + if (abs(compose_scale - 1) > 1e-1) + resize(full_img, img, Size(), compose_scale, compose_scale); + else + img = full_img; + full_img.release(); + Size img_size = img.size(); + + // Warp the current image + warper->warp(img, static_cast(cameras[img_idx].focal), cameras[img_idx].R, + img_warped); + + // Warp the current image mask + mask.create(img_size, CV_8U); + mask.setTo(Scalar::all(255)); + warper->warp(mask, static_cast(cameras[img_idx].focal), cameras[img_idx].R, mask_warped, + INTER_NEAREST, BORDER_CONSTANT); + + // Compensate exposure + compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped); + + img_warped.convertTo(img_warped_s, CV_16S); + img_warped.release(); + img.release(); + mask.release(); + + dilate(masks_warped[img_idx], dilated_mask, Mat()); + resize(dilated_mask, seam_mask, mask_warped.size()); + mask_warped = seam_mask & mask_warped; + + if (blender.empty()) + { + blender = Blender::createDefault(blend_type, try_gpu); + Size dst_sz = resultRoi(corners, sizes).size(); + float blend_width = sqrt(static_cast(dst_sz.area())) * blend_strength / 100.f; + if (blend_width < 1.f) + blender = Blender::createDefault(Blender::NO, try_gpu); + else if (blend_type == Blender::MULTI_BAND) + { + MultiBandBlender* mb = dynamic_cast(static_cast(blender)); + mb->setNumBands(static_cast(ceil(log(blend_width)/log(2.)) - 1.)); + LOGLN("Multi-band blender, number of bands: " << mb->numBands()); + } + else if (blend_type == Blender::FEATHER) + { + FeatherBlender* fb = dynamic_cast(static_cast(blender)); + fb->setSharpness(1.f/blend_width); + LOGLN("Feather blender, sharpness: " << fb->sharpness()); + } + blender->prepare(corners, sizes); + } + + // Blend the current image + blender->feed(img_warped_s, mask_warped, corners[img_idx]); + } + + Mat result, result_mask; + blender->blend(result, result_mask); + + LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); + + imwrite(result_name, result); + + LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec"); + return 0; +} + +