Commit 9be4701f authored by Alexey Spizhevoy's avatar Alexey Spizhevoy

turned opencv_stitching application to module and sample

parent 30ecb288
project(stitching) 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)
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)
...@@ -39,19 +39,24 @@ ...@@ -39,19 +39,24 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_AUTOCALIB_HPP__ #ifndef __OPENCV_STITCHING_AUTOCALIB_HPP__
#define __OPENCV_AUTOCALIB_HPP__ #define __OPENCV_STITCHING_AUTOCALIB_HPP__
#include "precomp.hpp" #include "opencv2/core/core.hpp"
#include "matchers.hpp" #include "matchers.hpp"
namespace cv
{
// See "Construction of Panoramic Image Mosaics with Global and Local Alignment" // See "Construction of Panoramic Image Mosaics with Global and Local Alignment"
// by Heung-Yeung Shum and Richard Szeliski. // 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<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches, void estimateFocal(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches,
std::vector<double> &focals); std::vector<double> &focals);
bool calibrateRotatingCamera(const std::vector<cv::Mat> &Hs, cv::Mat &K); bool calibrateRotatingCamera(const std::vector<Mat> &Hs, Mat &K);
} // namespace cv
#endif // __OPENCV_AUTOCALIB_HPP__ #endif // __OPENCV_STITCHING_AUTOCALIB_HPP__
...@@ -39,26 +39,29 @@ ...@@ -39,26 +39,29 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_BLENDERS_HPP__ #ifndef __OPENCV_STITCHING_BLENDERS_HPP__
#define __OPENCV_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 // Simple blender which puts one image over another
class Blender class Blender
{ {
public: public:
enum { NO, FEATHER, MULTI_BAND }; enum { NO, FEATHER, MULTI_BAND };
static cv::Ptr<Blender> createDefault(int type, bool try_gpu = false); static Ptr<Blender> createDefault(int type, bool try_gpu = false);
void prepare(const std::vector<cv::Point> &corners, const std::vector<cv::Size> &sizes); void prepare(const std::vector<Point> &corners, const std::vector<Size> &sizes);
virtual void prepare(cv::Rect dst_roi); virtual void prepare(Rect dst_roi);
virtual void feed(const cv::Mat &img, const cv::Mat &mask, cv::Point tl); virtual void feed(const Mat &img, const Mat &mask, Point tl);
virtual void blend(cv::Mat &dst, cv::Mat &dst_mask); virtual void blend(Mat &dst, Mat &dst_mask);
protected: protected:
cv::Mat dst_, dst_mask_; Mat dst_, dst_mask_;
cv::Rect dst_roi_; Rect dst_roi_;
}; };
...@@ -69,14 +72,14 @@ public: ...@@ -69,14 +72,14 @@ public:
float sharpness() const { return sharpness_; } float sharpness() const { return sharpness_; }
void setSharpness(float val) { sharpness_ = val; } void setSharpness(float val) { sharpness_ = val; }
void prepare(cv::Rect dst_roi); void prepare(Rect dst_roi);
void feed(const cv::Mat &img, const cv::Mat &mask, cv::Point tl); void feed(const Mat &img, const Mat &mask, Point tl);
void blend(cv::Mat &dst, cv::Mat &dst_mask); void blend(Mat &dst, Mat &dst_mask);
private: private:
float sharpness_; float sharpness_;
cv::Mat weight_map_; Mat weight_map_;
cv::Mat dst_weight_map_; Mat dst_weight_map_;
}; };
...@@ -87,15 +90,15 @@ public: ...@@ -87,15 +90,15 @@ public:
int numBands() const { return actual_num_bands_; } int numBands() const { return actual_num_bands_; }
void setNumBands(int val) { actual_num_bands_ = val; } void setNumBands(int val) { actual_num_bands_ = val; }
void prepare(cv::Rect dst_roi); void prepare(Rect dst_roi);
void feed(const cv::Mat &img, const cv::Mat &mask, cv::Point tl); void feed(const Mat &img, const Mat &mask, Point tl);
void blend(cv::Mat &dst, cv::Mat &dst_mask); void blend(Mat &dst, Mat &dst_mask);
private: private:
int actual_num_bands_, num_bands_; int actual_num_bands_, num_bands_;
std::vector<cv::Mat> dst_pyr_laplace_; std::vector<Mat> dst_pyr_laplace_;
std::vector<cv::Mat> dst_band_weights_; std::vector<Mat> dst_band_weights_;
cv::Rect dst_roi_final_; Rect dst_roi_final_;
bool can_use_gpu_; bool can_use_gpu_;
}; };
...@@ -103,15 +106,17 @@ private: ...@@ -103,15 +106,17 @@ private:
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// Auxiliary functions // 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<cv::Mat>& pyr); void createLaplacePyr(const Mat &img, int num_levels, std::vector<Mat>& pyr);
void createLaplacePyrGpu(const cv::Mat &img, int num_levels, std::vector<cv::Mat>& pyr); void createLaplacePyrGpu(const Mat &img, int num_levels, std::vector<Mat>& pyr);
// Restores source image // Restores source image
void restoreImageFromLaplacePyr(std::vector<cv::Mat>& pyr); void restoreImageFromLaplacePyr(std::vector<Mat>& pyr);
} // namespace cv
#endif // __OPENCV_BLENDERS_HPP__ #endif // __OPENCV_STITCHING_BLENDERS_HPP__
...@@ -39,11 +39,13 @@ ...@@ -39,11 +39,13 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_CAMERA_HPP__ #ifndef __OPENCV_STITCHING_CAMERA_HPP__
#define __OPENCV_CAMERA_HPP__ #define __OPENCV_STITCHING_CAMERA_HPP__
#include "precomp.hpp" #include "opencv2/core/core.hpp"
namespace cv
{
struct CameraParams struct CameraParams
{ {
...@@ -52,8 +54,10 @@ struct CameraParams ...@@ -52,8 +54,10 @@ struct CameraParams
const CameraParams& operator =(const CameraParams& other); const CameraParams& operator =(const CameraParams& other);
double focal; // Focal length double focal; // Focal length
cv::Mat R; // Rotation Mat R; // Rotation
cv::Mat t; // Translation Mat t; // Translation
}; };
#endif // #ifndef __OPENCV_CAMERA_HPP__ } // namespace cv
#endif // #ifndef __OPENCV_STITCHING_CAMERA_HPP__
...@@ -38,61 +38,65 @@ ...@@ -38,61 +38,65 @@
// or tort (including negligence or otherwise) arising in any way out of // 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. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_EXPOSURE_COMPENSATE_HPP__ #ifndef __OPENCV_STITCHING_EXPOSURE_COMPENSATE_HPP__
#define __OPENCV_EXPOSURE_COMPENSATE_HPP__ #define __OPENCV_STITCHING_EXPOSURE_COMPENSATE_HPP__
#include "precomp.hpp" #include "opencv2/core/core.hpp"
namespace cv
class ExposureCompensator {
{
public: class ExposureCompensator
enum { NO, GAIN, GAIN_BLOCKS }; {
static cv::Ptr<ExposureCompensator> createDefault(int type); public:
enum { NO, GAIN, GAIN_BLOCKS };
void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images, static Ptr<ExposureCompensator> createDefault(int type);
const std::vector<cv::Mat> &masks);
virtual void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images, void feed(const std::vector<Point> &corners, const std::vector<Mat> &images,
const std::vector<std::pair<cv::Mat,uchar> > &masks) = 0; const std::vector<Mat> &masks);
virtual void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask) = 0; virtual void feed(const std::vector<Point> &corners, const std::vector<Mat> &images,
}; const std::vector<std::pair<Mat,uchar> > &masks) = 0;
virtual void apply(int index, Point corner, Mat &image, const Mat &mask) = 0;
};
class NoExposureCompensator : public ExposureCompensator
{
public: class NoExposureCompensator : public ExposureCompensator
void feed(const std::vector<cv::Point> &/*corners*/, const std::vector<cv::Mat> &/*images*/, {
const std::vector<std::pair<cv::Mat,uchar> > &/*masks*/) {}; public:
void apply(int /*index*/, cv::Point /*corner*/, cv::Mat &/*image*/, const cv::Mat &/*mask*/) {}; void feed(const std::vector<Point> &/*corners*/, const std::vector<Mat> &/*images*/,
}; const std::vector<std::pair<Mat,uchar> > &/*masks*/) {};
void apply(int /*index*/, Point /*corner*/, Mat &/*image*/, const Mat &/*mask*/) {};
};
class GainCompensator : public ExposureCompensator
{
public: class GainCompensator : public ExposureCompensator
void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images, {
const std::vector<std::pair<cv::Mat,uchar> > &masks); public:
void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask); void feed(const std::vector<Point> &corners, const std::vector<Mat> &images,
std::vector<double> gains() const; const std::vector<std::pair<Mat,uchar> > &masks);
void apply(int index, Point corner, Mat &image, const Mat &mask);
private: std::vector<double> gains() const;
cv::Mat_<double> gains_;
}; private:
Mat_<double> gains_;
};
class BlocksGainCompensator : public ExposureCompensator
{
public: class BlocksGainCompensator : public ExposureCompensator
BlocksGainCompensator(int bl_width = 32, int bl_height = 32) {
: bl_width_(bl_width), bl_height_(bl_height) {} public:
void feed(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images, BlocksGainCompensator(int bl_width = 32, int bl_height = 32)
const std::vector<std::pair<cv::Mat,uchar> > &masks); : bl_width_(bl_width), bl_height_(bl_height) {}
void apply(int index, cv::Point corner, cv::Mat &image, const cv::Mat &mask); void feed(const std::vector<Point> &corners, const std::vector<Mat> &images,
const std::vector<std::pair<Mat,uchar> > &masks);
private: void apply(int index, Point corner, Mat &image, const Mat &mask);
int bl_width_, bl_height_;
std::vector<cv::Mat_<float> > gain_maps_; private:
}; int bl_width_, bl_height_;
std::vector<Mat_<float> > gain_maps_;
#endif // __OPENCV_EXPOSURE_COMPENSATE_HPP__ };
\ No newline at end of file
} // namespace cv
#endif // __OPENCV_STITCHING_EXPOSURE_COMPENSATE_HPP__
...@@ -39,10 +39,14 @@ ...@@ -39,10 +39,14 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_MATCHERS_HPP__ #ifndef __OPENCV_STITCHING_MATCHERS_HPP__
#define __OPENCV_MATCHERS_HPP__ #define __OPENCV_STITCHING_MATCHERS_HPP__
#include "precomp.hpp" #include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
namespace cv
{
struct ImageFeatures struct ImageFeatures
{ {
...@@ -136,4 +140,6 @@ protected: ...@@ -136,4 +140,6 @@ protected:
cv::Ptr<FeaturesMatcher> impl_; cv::Ptr<FeaturesMatcher> impl_;
}; };
#endif // __OPENCV_MATCHERS_HPP__ } // namespace cv
#endif // __OPENCV_STITCHING_MATCHERS_HPP__
...@@ -39,14 +39,17 @@ ...@@ -39,14 +39,17 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_MOTION_ESTIMATORS_HPP__ #ifndef __OPENCV_STITCHING_MOTION_ESTIMATORS_HPP__
#define __OPENCV_MOTION_ESTIMATORS_HPP__ #define __OPENCV_STITCHING_MOTION_ESTIMATORS_HPP__
#include "precomp.hpp" #include "opencv2/core/core.hpp"
#include "matchers.hpp" #include "matchers.hpp"
#include "util.hpp" #include "util.hpp"
#include "camera.hpp" #include "camera.hpp"
namespace cv
{
class Estimator class Estimator
{ {
public: public:
...@@ -88,24 +91,24 @@ private: ...@@ -88,24 +91,24 @@ private:
void estimate(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches, void estimate(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches,
std::vector<CameraParams> &cameras); std::vector<CameraParams> &cameras);
void calcError(cv::Mat &err); void calcError(Mat &err);
void calcJacobian(); void calcJacobian();
int num_images_; int num_images_;
int total_num_matches_; int total_num_matches_;
const ImageFeatures *features_; const ImageFeatures *features_;
const MatchesInfo *pairwise_matches_; const MatchesInfo *pairwise_matches_;
cv::Mat cameras_; Mat cameras_;
std::vector<std::pair<int,int> > edges_; std::vector<std::pair<int,int> > edges_;
int cost_space_; int cost_space_;
float conf_thresh_; float conf_thresh_;
cv::Mat err_, err1_, err2_; Mat err_, err1_, err2_;
cv::Mat J_; Mat J_;
}; };
void waveCorrect(std::vector<cv::Mat> &rmats); void waveCorrect(std::vector<Mat> &rmats);
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
...@@ -121,4 +124,6 @@ std::vector<int> leaveBiggestComponent(std::vector<ImageFeatures> &features, std ...@@ -121,4 +124,6 @@ std::vector<int> leaveBiggestComponent(std::vector<ImageFeatures> &features, std
void findMaxSpanningTree(int num_images, const std::vector<MatchesInfo> &pairwise_matches, void findMaxSpanningTree(int num_images, const std::vector<MatchesInfo> &pairwise_matches,
Graph &span_tree, std::vector<int> &centers); Graph &span_tree, std::vector<int> &centers);
#endif // __OPENCV_MOTION_ESTIMATORS_HPP__ } // namespace cv
#endif // __OPENCV_STITCHING_MOTION_ESTIMATORS_HPP__
...@@ -38,66 +38,71 @@ ...@@ -38,66 +38,71 @@
// or tort (including negligence or otherwise) arising in any way out of // 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. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_SEAM_FINDERS_HPP__ #ifndef __OPENCV_STITCHING_SEAM_FINDERS_HPP__
#define __OPENCV_SEAM_FINDERS_HPP__ #define __OPENCV_STITCHING_SEAM_FINDERS_HPP__
#include "precomp.hpp" #include "opencv2/core/core.hpp"
class SeamFinder namespace cv
{ {
public:
enum { NO, VORONOI, GC_COLOR, GC_COLOR_GRAD }; class SeamFinder
static cv::Ptr<SeamFinder> createDefault(int type); {
public:
virtual ~SeamFinder() {} enum { NO, VORONOI, GC_COLOR, GC_COLOR_GRAD };
virtual void find(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, static Ptr<SeamFinder> createDefault(int type);
std::vector<cv::Mat> &masks) = 0;
}; virtual ~SeamFinder() {}
virtual void find(const std::vector<Mat> &src, const std::vector<Point> &corners,
std::vector<Mat> &masks) = 0;
class NoSeamFinder : public SeamFinder };
{
public:
void find(const std::vector<cv::Mat>&, const std::vector<cv::Point>&, std::vector<cv::Mat>&) {} class NoSeamFinder : public SeamFinder
}; {
public:
void find(const std::vector<Mat>&, const std::vector<Point>&, std::vector<Mat>&) {}
class PairwiseSeamFinder : public SeamFinder };
{
public:
virtual void find(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, class PairwiseSeamFinder : public SeamFinder
std::vector<cv::Mat> &masks); {
public:
protected: virtual void find(const std::vector<Mat> &src, const std::vector<Point> &corners,
virtual void findInPair(size_t first, size_t second, cv::Rect roi) = 0; std::vector<Mat> &masks);
std::vector<cv::Mat> images_; protected:
std::vector<cv::Point> corners_; virtual void findInPair(size_t first, size_t second, Rect roi) = 0;
std::vector<cv::Mat> masks_;
}; std::vector<Mat> images_;
std::vector<Point> corners_;
std::vector<Mat> masks_;
class VoronoiSeamFinder : public PairwiseSeamFinder };
{
private:
void findInPair(size_t first, size_t second, cv::Rect roi); 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 }; class GraphCutSeamFinder : public SeamFinder
GraphCutSeamFinder(int cost_type = COST_COLOR_GRAD, float terminal_cost = 10000.f, {
float bad_region_penalty = 1000.f); public:
enum { COST_COLOR, COST_COLOR_GRAD };
void find(const std::vector<cv::Mat> &src, const std::vector<cv::Point> &corners, GraphCutSeamFinder(int cost_type = COST_COLOR_GRAD, float terminal_cost = 10000.f,
std::vector<cv::Mat> &masks); float bad_region_penalty = 1000.f);
private: void find(const std::vector<Mat> &src, const std::vector<Point> &corners,
class Impl; std::vector<Mat> &masks);
cv::Ptr<Impl> impl_;
}; private:
class Impl;
#endif // __OPENCV_SEAM_FINDERS_HPP__ Ptr<Impl> impl_;
};
} // namespace cv
#endif // __OPENCV_STITCHING_SEAM_FINDERS_HPP__
/*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__
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
#define __OPENCV_STITCHING_UTIL_HPP__ #define __OPENCV_STITCHING_UTIL_HPP__
#include <list> #include <list>
#include "precomp.hpp" #include "opencv2/core/core.hpp"
#define ENABLE_LOG 1 #define ENABLE_LOG 1
...@@ -56,6 +56,8 @@ ...@@ -56,6 +56,8 @@
#define LOGLN(msg) LOG(msg << std::endl) #define LOGLN(msg) LOG(msg << std::endl)
namespace cv
{
class DisjointSets class DisjointSets
{ {
...@@ -104,14 +106,16 @@ private: ...@@ -104,14 +106,16 @@ private:
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// Auxiliary functions // Auxiliary functions
bool overlapRoi(cv::Point tl1, cv::Point tl2, cv::Size sz1, cv::Size sz2, cv::Rect &roi); bool overlapRoi(Point tl1, Point tl2, Size sz1, Size sz2, Rect &roi);
cv::Rect resultRoi(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images); Rect resultRoi(const std::vector<Point> &corners, const std::vector<Mat> &images);
cv::Rect resultRoi(const std::vector<cv::Point> &corners, const std::vector<cv::Size> &sizes); Rect resultRoi(const std::vector<Point> &corners, const std::vector<Size> &sizes);
cv::Point resultTl(const std::vector<cv::Point> &corners); Point resultTl(const std::vector<Point> &corners);
// Returns random 'count' element subset of the {0,1,...,size-1} set // Returns random 'count' element subset of the {0,1,...,size-1} set
void selectRandomSubset(int count, int size, std::vector<int> &subset); void selectRandomSubset(int count, int size, std::vector<int> &subset);
} // namespace cv
#include "util_inl.hpp" #include "util_inl.hpp"
#endif // __OPENCV_STITCHING_UTIL_HPP__ #endif // __OPENCV_STITCHING_UTIL_HPP__
...@@ -38,82 +38,88 @@ ...@@ -38,82 +38,88 @@
// or tort (including negligence or otherwise) arising in any way out of // 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. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_STITCHING_UTIL_INL_HPP__ #ifndef __OPENCV_STITCHING_UTIL_INL_HPP__
#define __OPENCV_STITCHING_UTIL_INL_HPP__ #define __OPENCV_STITCHING_UTIL_INL_HPP__
#include <queue> #include <queue>
#include "util.hpp" // Make your IDE see declarations #include "opencv2/core/core.hpp"
#include "util.hpp" // Make your IDE see declarations
template <typename B>
B Graph::forEach(B body) const namespace cv
{ {
for (int i = 0; i < numVertices(); ++i)
{ template <typename B>
std::list<GraphEdge>::const_iterator edge = edges_[i].begin(); B Graph::forEach(B body) const
for (; edge != edges_[i].end(); ++edge) {
body(*edge); for (int i = 0; i < numVertices(); ++i)
} {
return body; std::list<GraphEdge>::const_iterator edge = edges_[i].begin();
} for (; edge != edges_[i].end(); ++edge)
body(*edge);
}
template <typename B> return body;
B Graph::walkBreadthFirst(int from, B body) const }
{
std::vector<bool> was(numVertices(), false);
std::queue<int> vertices; template <typename B>
B Graph::walkBreadthFirst(int from, B body) const
was[from] = true; {
vertices.push(from); std::vector<bool> was(numVertices(), false);
std::queue<int> vertices;
while (!vertices.empty())
{ was[from] = true;
int vertex = vertices.front(); vertices.push(from);
vertices.pop();
while (!vertices.empty())
std::list<GraphEdge>::const_iterator edge = edges_[vertex].begin(); {
for (; edge != edges_[vertex].end(); ++edge) int vertex = vertices.front();
{ vertices.pop();
if (!was[edge->to])
{ std::list<GraphEdge>::const_iterator edge = edges_[vertex].begin();
body(*edge); for (; edge != edges_[vertex].end(); ++edge)
was[edge->to] = true; {
vertices.push(edge->to); if (!was[edge->to])
} {
} body(*edge);
} was[edge->to] = true;
vertices.push(edge->to);
return body; }
} }
}
////////////////////////////////////////////////////////////////////////////// return body;
// Some auxiliary math functions }
static inline
float normL2(const cv::Point3f& a) //////////////////////////////////////////////////////////////////////////////
{ // Some auxiliary math functions
return a.x * a.x + a.y * a.y + a.z * a.z;
} static inline
float normL2(const Point3f& a)
{
static inline return a.x * a.x + a.y * a.y + a.z * a.z;
float normL2(const cv::Point3f& a, const cv::Point3f& b) }
{
return normL2(a - b);
} static inline
float normL2(const Point3f& a, const Point3f& b)
{
static inline return normL2(a - b);
double normL2sq(const cv::Mat &r) }
{
return r.dot(r);
} static inline
double normL2sq(const Mat &r)
{
static inline int sqr(int x) { return x * x; } return r.dot(r);
static inline float sqr(float x) { return x * x; } }
static inline double sqr(double x) { return x * x; }
#endif // __OPENCV_STITCHING_UTIL_INL_HPP__ 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__
...@@ -39,29 +39,34 @@ ...@@ -39,29 +39,34 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_WARPERS_HPP__ #ifndef __OPENCV_STITCHING_WARPERS_HPP__
#define __OPENCV_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 class Warper
{ {
public: public:
enum { PLANE, CYLINDRICAL, SPHERICAL }; enum { PLANE, CYLINDRICAL, SPHERICAL };
static cv::Ptr<Warper> createByCameraFocal(float focal, int type, bool try_gpu = false); static Ptr<Warper> createByCameraFocal(float focal, int type, bool try_gpu = false);
virtual ~Warper() {} virtual ~Warper() {}
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 = cv::INTER_LINEAR, int border_mode = cv::BORDER_REFLECT) = 0; int interp_mode = INTER_LINEAR, int border_mode = BORDER_REFLECT) = 0;
virtual cv::Rect warpRoi(const cv::Size &sz, float focal, const cv::Mat &R) = 0; virtual Rect warpRoi(const Size &sz, float focal, const Mat &R) = 0;
}; };
struct ProjectorBase struct ProjectorBase
{ {
void setTransformation(const cv::Mat& R); void setTransformation(const Mat& R);
cv::Size size; Size size;
float focal; float focal;
float r[9]; float r[9];
float rinv[9]; float rinv[9];
...@@ -73,20 +78,20 @@ template <class P> ...@@ -73,20 +78,20 @@ template <class P>
class WarperBase : public Warper class WarperBase : public Warper
{ {
public: 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); 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: protected:
// Detects ROI of the destination image. It's correct for any projection. // 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. // Detects ROI of the destination image by walking over image border.
// Correctness for any projection isn't guaranteed. // 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_; P projector_;
}; };
...@@ -110,7 +115,7 @@ public: ...@@ -110,7 +115,7 @@ public:
} }
protected: 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 ...@@ -118,11 +123,11 @@ class PlaneWarperGpu : public PlaneWarper
{ {
public: public:
PlaneWarperGpu(float plane_dist = 1.f, float scale = 1.f) : PlaneWarper(plane_dist, scale) {} 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); int interp_mode, int border_mode);
private: 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: ...@@ -141,7 +146,7 @@ public:
SphericalWarper(float scale = 300.f) { projector_.scale = scale; } SphericalWarper(float scale = 300.f) { projector_.scale = scale; }
protected: 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 ...@@ -149,11 +154,11 @@ class SphericalWarperGpu : public SphericalWarper
{ {
public: public:
SphericalWarperGpu(float scale = 300.f) : SphericalWarper(scale) {} 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); int interp_mode, int border_mode);
private: 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: ...@@ -171,7 +176,7 @@ public:
CylindricalWarper(float scale = 300.f) { projector_.scale = scale; } CylindricalWarper(float scale = 300.f) { projector_.scale = scale; }
protected: protected:
void detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br) void detectResultRoi(Point &dst_tl, Point &dst_br)
{ {
WarperBase<CylindricalProjector>::detectResultRoiByBorder(dst_tl, dst_br); WarperBase<CylindricalProjector>::detectResultRoiByBorder(dst_tl, dst_br);
} }
...@@ -182,13 +187,15 @@ class CylindricalWarperGpu : public CylindricalWarper ...@@ -182,13 +187,15 @@ class CylindricalWarperGpu : public CylindricalWarper
{ {
public: public:
CylindricalWarperGpu(float scale = 300.f) : CylindricalWarper(scale) {} 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); int interp_mode, int border_mode);
private: 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" #include "warpers_inl.hpp"
#endif // __OPENCV_WARPERS_HPP__ #endif // __OPENCV_STITCHING_WARPERS_HPP__
...@@ -39,13 +39,17 @@ ...@@ -39,13 +39,17 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#ifndef __OPENCV_WARPERS_INL_HPP__ #ifndef __OPENCV_STITCHING_WARPERS_INL_HPP__
#define __OPENCV_WARPERS_INL_HPP__ #define __OPENCV_STITCHING_WARPERS_INL_HPP__
#include "opencv2/core/core.hpp"
#include "warpers.hpp" // Make your IDE see declarations #include "warpers.hpp" // Make your IDE see declarations
namespace cv
{
template <class P> template <class P>
cv::Point WarperBase<P>::warp(const cv::Mat &src, float focal, const cv::Mat &R, cv::Mat &dst, Point WarperBase<P>::warp(const Mat &src, float focal, const Mat &R, Mat &dst,
int interp_mode, int border_mode) int interp_mode, int border_mode)
{ {
src_size_ = src.size(); src_size_ = src.size();
...@@ -54,11 +58,11 @@ cv::Point WarperBase<P>::warp(const cv::Mat &src, float focal, const cv::Mat &R, ...@@ -54,11 +58,11 @@ cv::Point WarperBase<P>::warp(const cv::Mat &src, float focal, const cv::Mat &R,
projector_.focal = focal; projector_.focal = focal;
projector_.setTransformation(R); projector_.setTransformation(R);
cv::Point dst_tl, dst_br; Point dst_tl, dst_br;
detectResultRoi(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); 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 ymap(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F);
float x, y; float x, y;
for (int v = dst_tl.y; v <= dst_br.y; ++v) for (int v = dst_tl.y; v <= dst_br.y; ++v)
...@@ -79,7 +83,7 @@ cv::Point WarperBase<P>::warp(const cv::Mat &src, float focal, const cv::Mat &R, ...@@ -79,7 +83,7 @@ cv::Point WarperBase<P>::warp(const cv::Mat &src, float focal, const cv::Mat &R,
template <class P> template <class P>
cv::Rect WarperBase<P>::warpRoi(const cv::Size &sz, float focal, const cv::Mat &R) Rect WarperBase<P>::warpRoi(const Size &sz, float focal, const Mat &R)
{ {
src_size_ = sz; src_size_ = sz;
...@@ -87,15 +91,15 @@ cv::Rect WarperBase<P>::warpRoi(const cv::Size &sz, float focal, const cv::Mat & ...@@ -87,15 +91,15 @@ cv::Rect WarperBase<P>::warpRoi(const cv::Size &sz, float focal, const cv::Mat &
projector_.focal = focal; projector_.focal = focal;
projector_.setTransformation(R); projector_.setTransformation(R);
cv::Point dst_tl, dst_br; Point dst_tl, dst_br;
detectResultRoi(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 <class P> template <class P>
void WarperBase<P>::detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br) void WarperBase<P>::detectResultRoi(Point &dst_tl, Point &dst_br)
{ {
float tl_uf = std::numeric_limits<float>::max(); float tl_uf = std::numeric_limits<float>::max();
float tl_vf = std::numeric_limits<float>::max(); float tl_vf = std::numeric_limits<float>::max();
...@@ -121,7 +125,7 @@ void WarperBase<P>::detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br) ...@@ -121,7 +125,7 @@ void WarperBase<P>::detectResultRoi(cv::Point &dst_tl, cv::Point &dst_br)
template <class P> template <class P>
void WarperBase<P>::detectResultRoiByBorder(cv::Point &dst_tl, cv::Point &dst_br) void WarperBase<P>::detectResultRoiByBorder(Point &dst_tl, Point &dst_br)
{ {
float tl_uf = std::numeric_limits<float>::max(); float tl_uf = std::numeric_limits<float>::max();
float tl_vf = std::numeric_limits<float>::max(); float tl_vf = std::numeric_limits<float>::max();
...@@ -252,4 +256,6 @@ void CylindricalProjector::mapBackward(float u, float v, float &x, float &y) ...@@ -252,4 +256,6 @@ void CylindricalProjector::mapBackward(float u, float v, float &x, float &y)
y = focal * y / z + size.height * 0.5f; y = focal * y / z + size.height * 0.5f;
} }
#endif // __OPENCV_WARPERS_INL_HPP__ } // namespace cv
#endif // __OPENCV_STITCHING_WARPERS_INL_HPP__
...@@ -39,11 +39,12 @@ ...@@ -39,11 +39,12 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#include "autocalib.hpp" #include "precomp.hpp"
#include "util.hpp"
using namespace std; using namespace std;
using namespace cv;
namespace cv
{
void focalsFromHomography(const 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)
{ {
...@@ -59,8 +60,8 @@ void focalsFromHomography(const Mat& H, double &f0, double &f1, bool &f0_ok, boo ...@@ -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]); d2 = (h[7] - h[6]) * (h[7] + h[6]);
v1 = -(h[0] * h[1] + h[3] * h[4]) / d1; 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; 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 < v2) std::swap(v1, v2);
if (v1 > 0 && v2 > 0) f1 = sqrt(abs(d1) > abs(d2) ? 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 if (v1 > 0) f1 = sqrt(v1);
else f1_ok = false; else f1_ok = false;
...@@ -69,8 +70,8 @@ void focalsFromHomography(const Mat& H, double &f0, double &f1, bool &f0_ok, boo ...@@ -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]; d2 = h[0] * h[0] + h[1] * h[1] - h[3] * h[3] - h[4] * h[4];
v1 = -h[2] * h[5] / d1; v1 = -h[2] * h[5] / d1;
v2 = (h[5] * h[5] - h[2] * h[2]) / d2; v2 = (h[5] * h[5] - h[2] * h[2]) / d2;
if (v1 < v2) swap(v1, v2); if (v1 < v2) std::swap(v1, v2);
if (v1 > 0 && v2 > 0) f0 = sqrt(abs(d1) > abs(d2) ? 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 if (v1 > 0) f0 = sqrt(v1);
else f0_ok = false; else f0_ok = false;
} }
...@@ -182,3 +183,5 @@ bool calibrateRotatingCamera(const vector<Mat> &Hs, Mat &K) ...@@ -182,3 +183,5 @@ bool calibrateRotatingCamera(const vector<Mat> &Hs, Mat &K)
K = W.t(); K = W.t();
return true; return true;
} }
} // namespace cv
...@@ -39,11 +39,12 @@ ...@@ -39,11 +39,12 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#include "blenders.hpp" #include "precomp.hpp"
#include "util.hpp"
using namespace std; using namespace std;
using namespace cv;
namespace cv
{
static const float WEIGHT_EPS = 1e-5f; static const float WEIGHT_EPS = 1e-5f;
...@@ -147,7 +148,7 @@ void FeatherBlender::feed(const Mat &img, const Mat &mask, Point tl) ...@@ -147,7 +148,7 @@ void FeatherBlender::feed(const Mat &img, const Mat &mask, Point tl)
void FeatherBlender::blend(Mat &dst, Mat &dst_mask) 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; dst_mask_ = dst_weight_map_ > WEIGHT_EPS;
Blender::blend(dst, dst_mask); Blender::blend(dst, dst_mask);
} }
...@@ -281,7 +282,7 @@ void MultiBandBlender::feed(const Mat &img, const Mat &mask, Point tl) ...@@ -281,7 +282,7 @@ void MultiBandBlender::feed(const Mat &img, const Mat &mask, Point tl)
void MultiBandBlender::blend(Mat &dst, Mat &dst_mask) void MultiBandBlender::blend(Mat &dst, Mat &dst_mask)
{ {
for (int i = 0; i <= num_bands_; ++i) 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_); restoreImageFromLaplacePyr(dst_pyr_laplace_);
...@@ -299,7 +300,7 @@ void MultiBandBlender::blend(Mat &dst, Mat &dst_mask) ...@@ -299,7 +300,7 @@ void MultiBandBlender::blend(Mat &dst, Mat &dst_mask)
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// Auxiliary functions // Auxiliary functions
void normalize(const Mat& weight, Mat& src) void normalizeUsingWeightMap(const Mat& weight, Mat& src)
{ {
CV_Assert(weight.type() == CV_32F); CV_Assert(weight.type() == CV_32F);
CV_Assert(src.type() == CV_16SC3); CV_Assert(src.type() == CV_16SC3);
...@@ -374,4 +375,4 @@ void restoreImageFromLaplacePyr(vector<Mat> &pyr) ...@@ -374,4 +375,4 @@ void restoreImageFromLaplacePyr(vector<Mat> &pyr)
} }
} }
} // namespace cv
#include <fstream> #include "precomp.hpp"
#include "camera.hpp"
using namespace std; 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)) {} 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) ...@@ -16,3 +16,5 @@ const CameraParams& CameraParams::operator =(const CameraParams &other)
t = other.t.clone(); t = other.t.clone();
return *this; return *this;
} }
} // namespace cv
...@@ -39,14 +39,13 @@ ...@@ -39,14 +39,13 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#include "precomp.hpp"
#include "exposure_compensate.hpp"
#include "util.hpp"
using namespace std; using namespace std;
using namespace cv;
using namespace cv::gpu; using namespace cv::gpu;
namespace cv
{
Ptr<ExposureCompensator> ExposureCompensator::createDefault(int type) Ptr<ExposureCompensator> ExposureCompensator::createDefault(int type)
{ {
...@@ -243,3 +242,5 @@ void BlocksGainCompensator::apply(int index, Point /*corner*/, Mat &image, const ...@@ -243,3 +242,5 @@ void BlocksGainCompensator::apply(int index, Point /*corner*/, Mat &image, const
} }
} }
} }
} // namespace cv
...@@ -39,17 +39,13 @@ ...@@ -39,17 +39,13 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#include <algorithm> #include "precomp.hpp"
#include <functional>
#include "matchers.hpp"
#include "util.hpp"
using namespace std; using namespace std;
using namespace cv;
using namespace cv::gpu; using namespace cv::gpu;
namespace cv
////////////////////////////////////////////////////////////////////////////// {
void FeaturesFinder::operator ()(const Mat &image, ImageFeatures &features) void FeaturesFinder::operator ()(const Mat &image, ImageFeatures &features)
{ {
...@@ -58,10 +54,13 @@ void FeaturesFinder::operator ()(const Mat &image, ImageFeatures &features) ...@@ -58,10 +54,13 @@ void FeaturesFinder::operator ()(const Mat &image, ImageFeatures &features)
//features.img = image.clone(); //features.img = image.clone();
} }
////////////////////////////////////////////////////////////////////////////// } // namespace cv
namespace namespace
{ {
using namespace cv;
class CpuSurfFeaturesFinder : public FeaturesFinder class CpuSurfFeaturesFinder : public FeaturesFinder
{ {
public: public:
...@@ -153,9 +152,12 @@ namespace ...@@ -153,9 +152,12 @@ namespace
keypoints_.release(); keypoints_.release();
descriptors_.release(); descriptors_.release();
} }
} // anonymous namespace } // namespace
namespace cv
{
SurfFeaturesFinder::SurfFeaturesFinder(bool try_use_gpu, double hess_thresh, int num_octaves, int num_layers, SurfFeaturesFinder::SurfFeaturesFinder(bool try_use_gpu, double hess_thresh, int num_octaves, int num_layers,
int num_octaves_descr, int num_layers_descr) int num_octaves_descr, int num_layers_descr)
{ {
...@@ -240,8 +242,8 @@ struct MatchPairsBody ...@@ -240,8 +242,8 @@ struct MatchPairsBody
pairwise_matches[dual_pair_idx].H = pairwise_matches[pair_idx].H.inv(); 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) for (size_t j = 0; j < pairwise_matches[dual_pair_idx].matches.size(); ++j)
swap(pairwise_matches[dual_pair_idx].matches[j].queryIdx, std::swap(pairwise_matches[dual_pair_idx].matches[j].queryIdx,
pairwise_matches[dual_pair_idx].matches[j].trainIdx); pairwise_matches[dual_pair_idx].matches[j].trainIdx);
LOG("."); LOG(".");
} }
} }
...@@ -457,7 +459,7 @@ void BestOf2NearestMatcher::match(const ImageFeatures &features1, const ImageFea ...@@ -457,7 +459,7 @@ void BestOf2NearestMatcher::match(const ImageFeatures &features1, const ImageFea
// Find pair-wise motion // Find pair-wise motion
matches_info.H = findHomography(src_points, dst_points, matches_info.inliers_mask, CV_RANSAC); matches_info.H = findHomography(src_points, dst_points, matches_info.inliers_mask, CV_RANSAC);
if (abs(determinant(matches_info.H)) < numeric_limits<double>::epsilon()) if (std::abs(determinant(matches_info.H)) < numeric_limits<double>::epsilon())
return; return;
// Find number of inliers // Find number of inliers
...@@ -504,3 +506,5 @@ void BestOf2NearestMatcher::releaseMemory() ...@@ -504,3 +506,5 @@ void BestOf2NearestMatcher::releaseMemory()
{ {
impl_->releaseMemory(); impl_->releaseMemory();
} }
} // namespace cv
...@@ -39,17 +39,12 @@ ...@@ -39,17 +39,12 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#include <algorithm> #include "precomp.hpp"
#include <sstream>
#include "autocalib.hpp"
#include "motion_estimators.hpp"
#include "util.hpp"
using namespace std; using namespace std;
using namespace cv;
namespace cv
////////////////////////////////////////////////////////////////////////////// {
struct IncDistance struct IncDistance
{ {
...@@ -605,3 +600,5 @@ void findMaxSpanningTree(int num_images, const vector<MatchesInfo> &pairwise_mat ...@@ -605,3 +600,5 @@ void findMaxSpanningTree(int num_images, const vector<MatchesInfo> &pairwise_mat
centers.push_back(i); centers.push_back(i);
CV_Assert(centers.size() > 0 && centers.size() <= 2); CV_Assert(centers.size() > 0 && centers.size() <= 2);
} }
} // namespace cv
...@@ -51,10 +51,19 @@ ...@@ -51,10 +51,19 @@
#include <utility> #include <utility>
#include <set> #include <set>
#include <functional> #include <functional>
#include <sstream>
#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/core.hpp"
#include "opencv2/core/internal.hpp" #include "opencv2/core/internal.hpp"
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/features2d/features2d.hpp" #include "opencv2/features2d/features2d.hpp"
#include "opencv2/calib3d/calib3d.hpp" #include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/gpu/gpu.hpp" #include "opencv2/gpu/gpu.hpp"
......
...@@ -39,12 +39,12 @@ ...@@ -39,12 +39,12 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#include "seam_finders.hpp" #include "precomp.hpp"
#include "util.hpp"
using namespace std; using namespace std;
using namespace cv;
namespace cv
{
Ptr<SeamFinder> SeamFinder::createDefault(int type) Ptr<SeamFinder> SeamFinder::createDefault(int type)
{ {
...@@ -405,3 +405,5 @@ void GraphCutSeamFinder::find(const vector<Mat> &src, const vector<Point> &corne ...@@ -405,3 +405,5 @@ void GraphCutSeamFinder::find(const vector<Mat> &src, const vector<Point> &corne
{ {
impl_->find(src, corners, masks); impl_->find(src, corners, masks);
} }
} // namespace cv
...@@ -39,10 +39,12 @@ ...@@ -39,10 +39,12 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#include "util.hpp" #include "precomp.hpp"
using namespace std; using namespace std;
using namespace cv;
namespace cv
{
void DisjointSets::createOneElemSets(int n) void DisjointSets::createOneElemSets(int n)
{ {
...@@ -161,3 +163,5 @@ void selectRandomSubset(int count, int size, vector<int> &subset) ...@@ -161,3 +163,5 @@ void selectRandomSubset(int count, int size, vector<int> &subset)
} }
} }
} }
} // namespace cv
...@@ -39,10 +39,12 @@ ...@@ -39,10 +39,12 @@
// the use of this software, even if advised of the possibility of such damage. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
#include "warpers.hpp" #include "precomp.hpp"
using namespace std; using namespace std;
using namespace cv;
namespace cv
{
Ptr<Warper> Warper::createByCameraFocal(float focal, int type, bool try_gpu) Ptr<Warper> 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 ...@@ -227,3 +229,5 @@ Point CylindricalWarperGpu::warp(const Mat &src, float focal, const Mat &R, Mat
return dst_tl; return dst_tl;
} }
} // namespace cv
...@@ -18,6 +18,8 @@ if (BUILD_EXAMPLES) ...@@ -18,6 +18,8 @@ if (BUILD_EXAMPLES)
"${CMAKE_SOURCE_DIR}/modules/objdetect/include" "${CMAKE_SOURCE_DIR}/modules/objdetect/include"
"${CMAKE_SOURCE_DIR}/modules/legacy/include" "${CMAKE_SOURCE_DIR}/modules/legacy/include"
"${CMAKE_SOURCE_DIR}/modules/contrib/include" "${CMAKE_SOURCE_DIR}/modules/contrib/include"
"${CMAKE_SOURCE_DIR}/modules/stitching/include"
"${CMAKE_SOURCE_DIR}/modules/gpu/include"
) )
if(CMAKE_COMPILER_IS_GNUCXX) if(CMAKE_COMPILER_IS_GNUCXX)
...@@ -35,10 +37,10 @@ if (BUILD_EXAMPLES) ...@@ -35,10 +37,10 @@ if (BUILD_EXAMPLES)
PROJECT_LABEL "(EXAMPLE) ${name}") PROJECT_LABEL "(EXAMPLE) ${name}")
add_dependencies(${the_target} opencv_core opencv_flann opencv_imgproc opencv_highgui add_dependencies(${the_target} opencv_core opencv_flann opencv_imgproc opencv_highgui
opencv_ml opencv_video opencv_objdetect opencv_features2d 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 target_link_libraries(${the_target} ${OPENCV_LINKER_LIBS} opencv_core
opencv_flann opencv_imgproc opencv_highgui opencv_ml opencv_video opencv_objdetect 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) if(ENABLE_SOLUTION_FOLDERS)
set_target_properties(${the_target} PROPERTIES FOLDER "samples//cpp") set_target_properties(${the_target} PROPERTIES FOLDER "samples//cpp")
......
/*M/////////////////////////////////////////////////////////////////////////////////////// /*M///////////////////////////////////////////////////////////////////////////////////////
// //
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
// //
// By downloading, copying, installing or using the software you agree to this license. // 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, // If you do not agree to this license, do not download, install,
// copy or use the software. // copy or use the software.
// //
// //
// License Agreement // License Agreement
// For Open Source Computer Vision Library // For Open Source Computer Vision Library
// //
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. // Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners. // Third party copyrights are property of their respective owners.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met: // are permitted provided that the following conditions are met:
// //
// * Redistribution's of source code must retain the above copyright notice, // * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer. // this list of conditions and the following disclaimer.
// //
// * Redistribution's in binary form must reproduce the above copyright notice, // * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation // this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution. // and/or other materials provided with the distribution.
// //
// * The name of the copyright holders may not be used to endorse or promote products // * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission. // derived from this software without specific prior written permission.
// //
// This software is provided by the copyright holders and contributors "as is" and // 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 // any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed. // 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, // In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages // indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services; // (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused // loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability, // and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of // 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. // the use of this software, even if advised of the possibility of such damage.
// //
//M*/ //M*/
// We follow to these papers: // We follow to these papers:
// 1) Construction of panoramic mosaics with global and local alignment. // 1) Construction of panoramic mosaics with global and local alignment.
// Heung-Yeung Shum and Richard Szeliski. 2000. // Heung-Yeung Shum and Richard Szeliski. 2000.
// 2) Eliminating Ghosting and Exposure Artifacts in Image Mosaics. // 2) Eliminating Ghosting and Exposure Artifacts in Image Mosaics.
// Matthew Uyttendaele, Ashley Eden and Richard Szeliski. 2001. // Matthew Uyttendaele, Ashley Eden and Richard Szeliski. 2001.
// 3) Automatic Panoramic Image Stitching using Invariant Features. // 3) Automatic Panoramic Image Stitching using Invariant Features.
// Matthew Brown and David G. Lowe. 2007. // Matthew Brown and David G. Lowe. 2007.
#include <fstream> #include <fstream>
#include "precomp.hpp" #include "opencv2/stitching/stitching.hpp"
#include "util.hpp" #include "opencv2/highgui/highgui.hpp"
#include "warpers.hpp"
#include "blenders.hpp" using namespace std;
#include "seam_finders.hpp" using namespace cv;
#include "motion_estimators.hpp"
#include "exposure_compensate.hpp" void printUsage()
#include "camera.hpp" {
cout <<
using namespace std; "Rotation model images stitcher.\n\n"
using namespace cv; "stitching img1 img2 [...imgN] [flags]\n\n"
"Flags:\n"
void printUsage() " --preview\n"
{ " Run stitching in the preview mode. Works faster than usual mode,\n"
cout << " but output image will have lower resolution.\n"
"Rotation model images stitcher.\n\n" " --try_gpu (yes|no)\n"
"opencv_stitching img1 img2 [...imgN] [flags]\n\n" " Try to use GPU. The default value is 'no'. All default values\n"
"Flags:\n" " are for CPU mode.\n"
" --preview\n" "\nMotion Estimation Flags:\n"
" Run stitching in the preview mode. Works faster than usual mode,\n" " --work_megapix <float>\n"
" but output image will have lower resolution.\n" " Resolution for image registration step. The default is 0.6 Mpx.\n"
" --try_gpu (yes|no)\n" " --match_conf <float>\n"
" Try to use GPU. The default value is 'no'. All default values\n" " Confidence for feature matching step. The default is 0.65.\n"
" are for CPU mode.\n" " --conf_thresh <float>\n"
"\nMotion Estimation Flags:\n" " Threshold for two images are from the same panorama confidence.\n"
" --work_megapix <float>\n" " The default is 1.0.\n"
" Resolution for image registration step. The default is 0.6 Mpx.\n" " --ba (no|ray|focal_ray)\n"
" --match_conf <float>\n" " Bundle adjustment cost function. The default is 'focal_ray'.\n"
" Confidence for feature matching step. The default is 0.65.\n" " --wave_correct (no|yes)\n"
" --conf_thresh <float>\n" " Perform wave effect correction. The default is 'yes'.\n"
" Threshold for two images are from the same panorama confidence.\n" " --save_graph <file_name>\n"
" The default is 1.0.\n" " Save matches graph represented in DOT language to <file_name> file.\n"
" --ba (no|ray|focal_ray)\n" " Labels description: Nm is number of matches, Ni is number of inliers,\n"
" Bundle adjustment cost function. The default is 'focal_ray'.\n" " C is confidence.\n"
" --wave_correct (no|yes)\n" "\nCompositing Flags:\n"
" Perform wave effect correction. The default is 'yes'.\n" " --warp (plane|cylindrical|spherical)\n"
" --save_graph <file_name>\n" " Warp surface type. The default is 'spherical'.\n"
" Save matches graph represented in DOT language to <file_name> file.\n" " --seam_megapix <float>\n"
" Labels description: Nm is number of matches, Ni is number of inliers,\n" " Resolution for seam estimation step. The default is 0.1 Mpx.\n"
" C is confidence.\n" " --seam (no|voronoi|gc_color|gc_colorgrad)\n"
"\nCompositing Flags:\n" " Seam estimation method. The default is 'gc_color'.\n"
" --warp (plane|cylindrical|spherical)\n" " --compose_megapix <float>\n"
" Warp surface type. The default is 'spherical'.\n" " Resolution for compositing step. Use -1 for original resolution.\n"
" --seam_megapix <float>\n" " The default is -1.\n"
" Resolution for seam estimation step. The default is 0.1 Mpx.\n" " --expos_comp (no|gain|gain_blocks)\n"
" --seam (no|voronoi|gc_color|gc_colorgrad)\n" " Exposure compensation method. The default is 'gain_blocks'.\n"
" Seam estimation method. The default is 'gc_color'.\n" " --blend (no|feather|multiband)\n"
" --compose_megapix <float>\n" " Blending method. The default is 'multiband'.\n"
" Resolution for compositing step. Use -1 for original resolution.\n" " --blend_strength <float>\n"
" The default is -1.\n" " Blending strength from [0,100] range. The default is 5.\n"
" --expos_comp (no|gain|gain_blocks)\n" " --output <result_img>\n"
" Exposure compensation method. The default is 'gain_blocks'.\n" " The default is 'result.jpg'.\n";
" --blend (no|feather|multiband)\n" }
" Blending method. The default is 'multiband'.\n"
" --blend_strength <float>\n"
" Blending strength from [0,100] range. The default is 5.\n" // Default command line args
" --output <result_img>\n" vector<string> img_names;
" The default is 'result.jpg'.\n"; bool preview = false;
} bool try_gpu = false;
double work_megapix = 0.6;
double seam_megapix = 0.1;
// Default command line args double compose_megapix = -1;
vector<string> img_names; int ba_space = BundleAdjuster::FOCAL_RAY_SPACE;
bool preview = false; float conf_thresh = 1.f;
bool try_gpu = false; bool wave_correct = true;
double work_megapix = 0.6; bool save_graph = false;
double seam_megapix = 0.1; std::string save_graph_to;
double compose_megapix = -1; int warp_type = Warper::SPHERICAL;
int ba_space = BundleAdjuster::FOCAL_RAY_SPACE; int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
float conf_thresh = 1.f; float match_conf = 0.65f;
bool wave_correct = true; int seam_find_type = SeamFinder::GC_COLOR;
bool save_graph = false; int blend_type = Blender::MULTI_BAND;
std::string save_graph_to; float blend_strength = 5;
int warp_type = Warper::SPHERICAL; string result_name = "result.jpg";
int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
float match_conf = 0.65f; int parseCmdArgs(int argc, char** argv)
int seam_find_type = SeamFinder::GC_COLOR; {
int blend_type = Blender::MULTI_BAND; if (argc == 1)
float blend_strength = 5; {
string result_name = "result.jpg"; printUsage();
return -1;
int parseCmdArgs(int argc, char** argv) }
{ for (int i = 1; i < argc; ++i)
if (argc == 1) {
{ if (string(argv[i]) == "--help" || string(argv[i]) == "/?")
printUsage(); {
return -1; printUsage();
} return -1;
for (int i = 1; i < argc; ++i) }
{ else if (string(argv[i]) == "--preview")
if (string(argv[i]) == "--help" || string(argv[i]) == "/?") {
{ preview = true;
printUsage(); }
return -1; else if (string(argv[i]) == "--try_gpu")
} {
else if (string(argv[i]) == "--preview") if (string(argv[i + 1]) == "no")
{ try_gpu = false;
preview = true; else if (string(argv[i + 1]) == "yes")
} try_gpu = true;
else if (string(argv[i]) == "--try_gpu") else
{ {
if (string(argv[i + 1]) == "no") cout << "Bad --try_gpu flag value\n";
try_gpu = false; return -1;
else if (string(argv[i + 1]) == "yes") }
try_gpu = true; i++;
else }
{ else if (string(argv[i]) == "--work_megapix")
cout << "Bad --try_gpu flag value\n"; {
return -1; work_megapix = atof(argv[i + 1]);
} i++;
i++; }
} else if (string(argv[i]) == "--seam_megapix")
else if (string(argv[i]) == "--work_megapix") {
{ seam_megapix = atof(argv[i + 1]);
work_megapix = atof(argv[i + 1]); i++;
i++; }
} else if (string(argv[i]) == "--compose_megapix")
else if (string(argv[i]) == "--seam_megapix") {
{ compose_megapix = atof(argv[i + 1]);
seam_megapix = atof(argv[i + 1]); i++;
i++; }
} else if (string(argv[i]) == "--result")
else if (string(argv[i]) == "--compose_megapix") {
{ result_name = argv[i + 1];
compose_megapix = atof(argv[i + 1]); i++;
i++; }
} else if (string(argv[i]) == "--match_conf")
else if (string(argv[i]) == "--result") {
{ match_conf = static_cast<float>(atof(argv[i + 1]));
result_name = argv[i + 1]; i++;
i++; }
} else if (string(argv[i]) == "--ba")
else if (string(argv[i]) == "--match_conf") {
{ if (string(argv[i + 1]) == "no")
match_conf = static_cast<float>(atof(argv[i + 1])); ba_space = BundleAdjuster::NO;
i++; else if (string(argv[i + 1]) == "ray")
} ba_space = BundleAdjuster::RAY_SPACE;
else if (string(argv[i]) == "--ba") else if (string(argv[i + 1]) == "focal_ray")
{ ba_space = BundleAdjuster::FOCAL_RAY_SPACE;
if (string(argv[i + 1]) == "no") else
ba_space = BundleAdjuster::NO; {
else if (string(argv[i + 1]) == "ray") cout << "Bad bundle adjustment space\n";
ba_space = BundleAdjuster::RAY_SPACE; return -1;
else if (string(argv[i + 1]) == "focal_ray") }
ba_space = BundleAdjuster::FOCAL_RAY_SPACE; i++;
else }
{ else if (string(argv[i]) == "--conf_thresh")
cout << "Bad bundle adjustment space\n"; {
return -1; conf_thresh = static_cast<float>(atof(argv[i + 1]));
} i++;
i++; }
} else if (string(argv[i]) == "--wave_correct")
else if (string(argv[i]) == "--conf_thresh") {
{ if (string(argv[i + 1]) == "no")
conf_thresh = static_cast<float>(atof(argv[i + 1])); wave_correct = false;
i++; else if (string(argv[i + 1]) == "yes")
} wave_correct = true;
else if (string(argv[i]) == "--wave_correct") else
{ {
if (string(argv[i + 1]) == "no") cout << "Bad --wave_correct flag value\n";
wave_correct = false; return -1;
else if (string(argv[i + 1]) == "yes") }
wave_correct = true; i++;
else }
{ else if (string(argv[i]) == "--save_graph")
cout << "Bad --wave_correct flag value\n"; {
return -1; save_graph = true;
} save_graph_to = argv[i + 1];
i++; i++;
} }
else if (string(argv[i]) == "--save_graph") else if (string(argv[i]) == "--warp")
{ {
save_graph = true; if (string(argv[i + 1]) == "plane")
save_graph_to = argv[i + 1]; warp_type = Warper::PLANE;
i++; else if (string(argv[i + 1]) == "cylindrical")
} warp_type = Warper::CYLINDRICAL;
else if (string(argv[i]) == "--warp") else if (string(argv[i + 1]) == "spherical")
{ warp_type = Warper::SPHERICAL;
if (string(argv[i + 1]) == "plane") else
warp_type = Warper::PLANE; {
else if (string(argv[i + 1]) == "cylindrical") cout << "Bad warping method\n";
warp_type = Warper::CYLINDRICAL; return -1;
else if (string(argv[i + 1]) == "spherical") }
warp_type = Warper::SPHERICAL; i++;
else }
{ else if (string(argv[i]) == "--expos_comp")
cout << "Bad warping method\n"; {
return -1; if (string(argv[i + 1]) == "no")
} expos_comp_type = ExposureCompensator::NO;
i++; else if (string(argv[i + 1]) == "gain")
} expos_comp_type = ExposureCompensator::GAIN;
else if (string(argv[i]) == "--expos_comp") else if (string(argv[i + 1]) == "gain_blocks")
{ expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
if (string(argv[i + 1]) == "no") else
expos_comp_type = ExposureCompensator::NO; {
else if (string(argv[i + 1]) == "gain") cout << "Bad exposure compensation method\n";
expos_comp_type = ExposureCompensator::GAIN; return -1;
else if (string(argv[i + 1]) == "gain_blocks") }
expos_comp_type = ExposureCompensator::GAIN_BLOCKS; i++;
else }
{ else if (string(argv[i]) == "--seam")
cout << "Bad exposure compensation method\n"; {
return -1; if (string(argv[i + 1]) == "no")
} seam_find_type = SeamFinder::NO;
i++; else if (string(argv[i + 1]) == "voronoi")
} seam_find_type = SeamFinder::VORONOI;
else if (string(argv[i]) == "--seam") else if (string(argv[i + 1]) == "gc_color")
{ seam_find_type = SeamFinder::GC_COLOR;
if (string(argv[i + 1]) == "no") else if (string(argv[i + 1]) == "gc_colorgrad")
seam_find_type = SeamFinder::NO; seam_find_type = SeamFinder::GC_COLOR_GRAD;
else if (string(argv[i + 1]) == "voronoi") else
seam_find_type = SeamFinder::VORONOI; {
else if (string(argv[i + 1]) == "gc_color") cout << "Bad seam finding method\n";
seam_find_type = SeamFinder::GC_COLOR; return -1;
else if (string(argv[i + 1]) == "gc_colorgrad") }
seam_find_type = SeamFinder::GC_COLOR_GRAD; i++;
else }
{ else if (string(argv[i]) == "--blend")
cout << "Bad seam finding method\n"; {
return -1; if (string(argv[i + 1]) == "no")
} blend_type = Blender::NO;
i++; else if (string(argv[i + 1]) == "feather")
} blend_type = Blender::FEATHER;
else if (string(argv[i]) == "--blend") else if (string(argv[i + 1]) == "multiband")
{ blend_type = Blender::MULTI_BAND;
if (string(argv[i + 1]) == "no") else
blend_type = Blender::NO; {
else if (string(argv[i + 1]) == "feather") cout << "Bad blending method\n";
blend_type = Blender::FEATHER; return -1;
else if (string(argv[i + 1]) == "multiband") }
blend_type = Blender::MULTI_BAND; i++;
else }
{ else if (string(argv[i]) == "--blend_strength")
cout << "Bad blending method\n"; {
return -1; blend_strength = static_cast<float>(atof(argv[i + 1]));
} i++;
i++; }
} else if (string(argv[i]) == "--output")
else if (string(argv[i]) == "--blend_strength") {
{ result_name = argv[i + 1];
blend_strength = static_cast<float>(atof(argv[i + 1])); i++;
i++; }
} else
else if (string(argv[i]) == "--output") img_names.push_back(argv[i]);
{ }
result_name = argv[i + 1]; if (preview)
i++; {
} compose_megapix = 0.6;
else }
img_names.push_back(argv[i]); return 0;
} }
if (preview)
{
compose_megapix = 0.6; int main(int argc, char* argv[])
} {
return 0; int64 app_start_time = getTickCount();
} cv::setBreakOnError(true);
int retval = parseCmdArgs(argc, argv);
int main(int argc, char* argv[]) if (retval)
{ return retval;
int64 app_start_time = getTickCount();
cv::setBreakOnError(true); // Check if have enough images
int num_images = static_cast<int>(img_names.size());
int retval = parseCmdArgs(argc, argv); if (num_images < 2)
if (retval) {
return retval; LOGLN("Need more images");
return -1;
// Check if have enough images }
int num_images = static_cast<int>(img_names.size());
if (num_images < 2) 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("Need more images");
return -1; LOGLN("Finding features...");
} int64 t = getTickCount();
double work_scale = 1, seam_scale = 1, compose_scale = 1; vector<ImageFeatures> features(num_images);
bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false; SurfFeaturesFinder finder(try_gpu);
Mat full_img, img;
LOGLN("Finding features...");
int64 t = getTickCount(); vector<Mat> images(num_images);
vector<Size> full_img_sizes(num_images);
vector<ImageFeatures> features(num_images); double seam_work_aspect = 1;
SurfFeaturesFinder finder(try_gpu);
Mat full_img, img; for (int i = 0; i < num_images; ++i)
{
vector<Mat> images(num_images); full_img = imread(img_names[i]);
vector<Size> full_img_sizes(num_images); full_img_sizes[i] = full_img.size();
double seam_work_aspect = 1;
if (full_img.empty())
for (int i = 0; i < num_images; ++i) {
{ LOGLN("Can't open image " << img_names[i]);
full_img = imread(img_names[i]); return -1;
full_img_sizes[i] = full_img.size(); }
if (work_megapix < 0)
if (full_img.empty()) {
{ img = full_img;
LOGLN("Can't open image " << img_names[i]); work_scale = 1;
return -1; is_work_scale_set = true;
} }
if (work_megapix < 0) else
{ {
img = full_img; if (!is_work_scale_set)
work_scale = 1; {
is_work_scale_set = true; work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area()));
} is_work_scale_set = true;
else }
{ resize(full_img, img, Size(), work_scale, work_scale);
if (!is_work_scale_set) }
{ if (!is_seam_scale_set)
work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area())); {
is_work_scale_set = true; seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area()));
} seam_work_aspect = seam_scale / work_scale;
resize(full_img, img, Size(), work_scale, work_scale); is_seam_scale_set = true;
} }
if (!is_seam_scale_set)
{ finder(img, features[i]);
seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area())); features[i].img_idx = i;
seam_work_aspect = seam_scale / work_scale; LOGLN("Features in image #" << i+1 << ": " << features[i].keypoints.size());
is_seam_scale_set = true;
} resize(full_img, img, Size(), seam_scale, seam_scale);
images[i] = img.clone();
finder(img, features[i]); }
features[i].img_idx = i;
LOGLN("Features in image #" << i+1 << ": " << features[i].keypoints.size()); finder.releaseMemory();
resize(full_img, img, Size(), seam_scale, seam_scale); full_img.release();
images[i] = img.clone(); img.release();
}
LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
finder.releaseMemory();
LOG("Pairwise matching");
full_img.release(); t = getTickCount();
img.release(); vector<MatchesInfo> pairwise_matches;
BestOf2NearestMatcher matcher(try_gpu, match_conf);
LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); matcher(features, pairwise_matches);
matcher.releaseMemory();
LOG("Pairwise matching"); LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
t = getTickCount();
vector<MatchesInfo> pairwise_matches; // Check if we should save matches graph
BestOf2NearestMatcher matcher(try_gpu, match_conf); if (save_graph)
matcher(features, pairwise_matches); {
matcher.releaseMemory(); LOGLN("Saving matches graph...");
LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); ofstream f(save_graph_to.c_str());
f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh);
// Check if we should save matches graph }
if (save_graph)
{ // Leave only images we are sure are from the same panorama
LOGLN("Saving matches graph..."); vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh);
ofstream f(save_graph_to.c_str()); vector<Mat> img_subset;
f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh); vector<string> img_names_subset;
} vector<Size> full_img_sizes_subset;
for (size_t i = 0; i < indices.size(); ++i)
// Leave only images we are sure are from the same panorama {
vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh); img_names_subset.push_back(img_names[indices[i]]);
vector<Mat> img_subset; img_subset.push_back(images[indices[i]]);
vector<string> img_names_subset; full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);
vector<Size> full_img_sizes_subset; }
for (size_t i = 0; i < indices.size(); ++i)
{ images = img_subset;
img_names_subset.push_back(img_names[indices[i]]); img_names = img_names_subset;
img_subset.push_back(images[indices[i]]); full_img_sizes = full_img_sizes_subset;
full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);
} // Check if we still have enough images
num_images = static_cast<int>(img_names.size());
images = img_subset; if (num_images < 2)
img_names = img_names_subset; {
full_img_sizes = full_img_sizes_subset; LOGLN("Need more images");
return -1;
// Check if we still have enough images }
num_images = static_cast<int>(img_names.size());
if (num_images < 2) HomographyBasedEstimator estimator;
{ vector<CameraParams> cameras;
LOGLN("Need more images"); estimator(features, pairwise_matches, cameras);
return -1;
} for (size_t i = 0; i < cameras.size(); ++i)
{
HomographyBasedEstimator estimator; Mat R;
vector<CameraParams> cameras; cameras[i].R.convertTo(R, CV_32F);
estimator(features, pairwise_matches, cameras); cameras[i].R = R;
LOGLN("Initial focal length #" << indices[i]+1 << ": " << cameras[i].focal);
for (size_t i = 0; i < cameras.size(); ++i) }
{
Mat R; BundleAdjuster adjuster(ba_space, conf_thresh);
cameras[i].R.convertTo(R, CV_32F); adjuster(features, pairwise_matches, cameras);
cameras[i].R = R;
LOGLN("Initial focal length #" << indices[i]+1 << ": " << cameras[i].focal); // Find median focal length
} vector<double> focals;
for (size_t i = 0; i < cameras.size(); ++i)
BundleAdjuster adjuster(ba_space, conf_thresh); {
adjuster(features, pairwise_matches, cameras); LOGLN("Camera #" << indices[i]+1 << " focal length: " << cameras[i].focal);
focals.push_back(cameras[i].focal);
// Find median focal length }
vector<double> focals; nth_element(focals.begin(), focals.begin() + focals.size()/2, focals.end());
for (size_t i = 0; i < cameras.size(); ++i) float warped_image_scale = static_cast<float>(focals[focals.size() / 2]);
{
LOGLN("Camera #" << indices[i]+1 << " focal length: " << cameras[i].focal); if (wave_correct)
focals.push_back(cameras[i].focal); {
} vector<Mat> rmats;
nth_element(focals.begin(), focals.begin() + focals.size()/2, focals.end()); for (size_t i = 0; i < cameras.size(); ++i)
float warped_image_scale = static_cast<float>(focals[focals.size() / 2]); rmats.push_back(cameras[i].R);
waveCorrect(rmats);
if (wave_correct) for (size_t i = 0; i < cameras.size(); ++i)
{ cameras[i].R = rmats[i];
vector<Mat> rmats; }
for (size_t i = 0; i < cameras.size(); ++i)
rmats.push_back(cameras[i].R); LOGLN("Warping images (auxiliary)... ");
waveCorrect(rmats); t = getTickCount();
for (size_t i = 0; i < cameras.size(); ++i)
cameras[i].R = rmats[i]; vector<Point> corners(num_images);
} vector<Mat> masks_warped(num_images);
vector<Mat> images_warped(num_images);
LOGLN("Warping images (auxiliary)... "); vector<Size> sizes(num_images);
t = getTickCount(); vector<Mat> masks(num_images);
vector<Point> corners(num_images); // Preapre images masks
vector<Mat> masks_warped(num_images); for (int i = 0; i < num_images; ++i)
vector<Mat> images_warped(num_images); {
vector<Size> sizes(num_images); masks[i].create(images[i].size(), CV_8U);
vector<Mat> masks(num_images); masks[i].setTo(Scalar::all(255));
}
// Preapre images masks
for (int i = 0; i < num_images; ++i) // Warp images and their masks
{ Ptr<Warper> warper = Warper::createByCameraFocal(static_cast<float>(warped_image_scale * seam_work_aspect),
masks[i].create(images[i].size(), CV_8U); warp_type, try_gpu);
masks[i].setTo(Scalar::all(255)); for (int i = 0; i < num_images; ++i)
} {
corners[i] = warper->warp(images[i], static_cast<float>(cameras[i].focal * seam_work_aspect),
// Warp images and their masks cameras[i].R, images_warped[i]);
Ptr<Warper> warper = Warper::createByCameraFocal(static_cast<float>(warped_image_scale * seam_work_aspect), sizes[i] = images_warped[i].size();
warp_type, try_gpu); warper->warp(masks[i], static_cast<float>(cameras[i].focal * seam_work_aspect),
for (int i = 0; i < num_images; ++i) cameras[i].R, masks_warped[i], INTER_NEAREST, BORDER_CONSTANT);
{ }
corners[i] = warper->warp(images[i], static_cast<float>(cameras[i].focal * seam_work_aspect),
cameras[i].R, images_warped[i]); vector<Mat> images_warped_f(num_images);
sizes[i] = images_warped[i].size(); for (int i = 0; i < num_images; ++i)
warper->warp(masks[i], static_cast<float>(cameras[i].focal * seam_work_aspect), images_warped[i].convertTo(images_warped_f[i], CV_32F);
cameras[i].R, masks_warped[i], INTER_NEAREST, BORDER_CONSTANT);
} LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
vector<Mat> images_warped_f(num_images); Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type);
for (int i = 0; i < num_images; ++i) compensator->feed(corners, images_warped, masks_warped);
images_warped[i].convertTo(images_warped_f[i], CV_32F);
Ptr<SeamFinder> seam_finder = SeamFinder::createDefault(seam_find_type);
LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); seam_finder->find(images_warped_f, corners, masks_warped);
Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type); // Release unused memory
compensator->feed(corners, images_warped, masks_warped); images.clear();
images_warped.clear();
Ptr<SeamFinder> seam_finder = SeamFinder::createDefault(seam_find_type); images_warped_f.clear();
seam_finder->find(images_warped_f, corners, masks_warped); masks.clear();
// Release unused memory LOGLN("Compositing...");
images.clear(); t = getTickCount();
images_warped.clear();
images_warped_f.clear(); Mat img_warped, img_warped_s;
masks.clear(); Mat dilated_mask, seam_mask, mask, mask_warped;
Ptr<Blender> blender;
LOGLN("Compositing..."); double compose_seam_aspect = 1;
t = getTickCount(); double compose_work_aspect = 1;
Mat img_warped, img_warped_s; for (int img_idx = 0; img_idx < num_images; ++img_idx)
Mat dilated_mask, seam_mask, mask, mask_warped; {
Ptr<Blender> blender; LOGLN("Compositing image #" << indices[img_idx]+1);
double compose_seam_aspect = 1;
double compose_work_aspect = 1; // Read image and resize it if necessary
full_img = imread(img_names[img_idx]);
for (int img_idx = 0; img_idx < num_images; ++img_idx) if (!is_compose_scale_set)
{ {
LOGLN("Compositing image #" << indices[img_idx]+1); if (compose_megapix > 0)
compose_scale = min(1.0, sqrt(compose_megapix * 1e6 / full_img.size().area()));
// Read image and resize it if necessary is_compose_scale_set = true;
full_img = imread(img_names[img_idx]);
if (!is_compose_scale_set) // Compute relative scales
{ compose_seam_aspect = compose_scale / seam_scale;
if (compose_megapix > 0) compose_work_aspect = compose_scale / work_scale;
compose_scale = min(1.0, sqrt(compose_megapix * 1e6 / full_img.size().area()));
is_compose_scale_set = true; // Update warped image scale
warped_image_scale *= static_cast<float>(compose_work_aspect);
// Compute relative scales warper = Warper::createByCameraFocal(warped_image_scale, warp_type, try_gpu);
compose_seam_aspect = compose_scale / seam_scale;
compose_work_aspect = compose_scale / work_scale; // Update corners and sizes
for (int i = 0; i < num_images; ++i)
// Update warped image scale {
warped_image_scale *= static_cast<float>(compose_work_aspect); // Update camera focal
warper = Warper::createByCameraFocal(warped_image_scale, warp_type, try_gpu); cameras[i].focal *= compose_work_aspect;
// Update corners and sizes // Update corner and size
for (int i = 0; i < num_images; ++i) Size sz = full_img_sizes[i];
{ if (abs(compose_scale - 1) > 1e-1)
// Update camera focal {
cameras[i].focal *= compose_work_aspect; sz.width = cvRound(full_img_sizes[i].width * compose_scale);
sz.height = cvRound(full_img_sizes[i].height * compose_scale);
// Update corner and size }
Size sz = full_img_sizes[i];
if (abs(compose_scale - 1) > 1e-1) Rect roi = warper->warpRoi(sz, static_cast<float>(cameras[i].focal), cameras[i].R);
{ corners[i] = roi.tl();
sz.width = cvRound(full_img_sizes[i].width * compose_scale); sizes[i] = roi.size();
sz.height = cvRound(full_img_sizes[i].height * compose_scale); }
} }
if (abs(compose_scale - 1) > 1e-1)
Rect roi = warper->warpRoi(sz, static_cast<float>(cameras[i].focal), cameras[i].R); resize(full_img, img, Size(), compose_scale, compose_scale);
corners[i] = roi.tl(); else
sizes[i] = roi.size(); img = full_img;
} full_img.release();
} Size img_size = img.size();
if (abs(compose_scale - 1) > 1e-1)
resize(full_img, img, Size(), compose_scale, compose_scale); // Warp the current image
else warper->warp(img, static_cast<float>(cameras[img_idx].focal), cameras[img_idx].R,
img = full_img; img_warped);
full_img.release();
Size img_size = img.size(); // Warp the current image mask
mask.create(img_size, CV_8U);
// Warp the current image mask.setTo(Scalar::all(255));
warper->warp(img, static_cast<float>(cameras[img_idx].focal), cameras[img_idx].R, warper->warp(mask, static_cast<float>(cameras[img_idx].focal), cameras[img_idx].R, mask_warped,
img_warped); INTER_NEAREST, BORDER_CONSTANT);
// Warp the current image mask // Compensate exposure
mask.create(img_size, CV_8U); compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped);
mask.setTo(Scalar::all(255));
warper->warp(mask, static_cast<float>(cameras[img_idx].focal), cameras[img_idx].R, mask_warped, img_warped.convertTo(img_warped_s, CV_16S);
INTER_NEAREST, BORDER_CONSTANT); img_warped.release();
img.release();
// Compensate exposure mask.release();
compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped);
dilate(masks_warped[img_idx], dilated_mask, Mat());
img_warped.convertTo(img_warped_s, CV_16S); resize(dilated_mask, seam_mask, mask_warped.size());
img_warped.release(); mask_warped = seam_mask & mask_warped;
img.release();
mask.release(); if (blender.empty())
{
dilate(masks_warped[img_idx], dilated_mask, Mat()); blender = Blender::createDefault(blend_type, try_gpu);
resize(dilated_mask, seam_mask, mask_warped.size()); Size dst_sz = resultRoi(corners, sizes).size();
mask_warped = seam_mask & mask_warped; float blend_width = sqrt(static_cast<float>(dst_sz.area())) * blend_strength / 100.f;
if (blend_width < 1.f)
if (blender.empty()) blender = Blender::createDefault(Blender::NO, try_gpu);
{ else if (blend_type == Blender::MULTI_BAND)
blender = Blender::createDefault(blend_type, try_gpu); {
Size dst_sz = resultRoi(corners, sizes).size(); MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender));
float blend_width = sqrt(static_cast<float>(dst_sz.area())) * blend_strength / 100.f; mb->setNumBands(static_cast<int>(ceil(log(blend_width)/log(2.)) - 1.));
if (blend_width < 1.f) LOGLN("Multi-band blender, number of bands: " << mb->numBands());
blender = Blender::createDefault(Blender::NO, try_gpu); }
else if (blend_type == Blender::MULTI_BAND) else if (blend_type == Blender::FEATHER)
{ {
MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender)); FeatherBlender* fb = dynamic_cast<FeatherBlender*>(static_cast<Blender*>(blender));
mb->setNumBands(static_cast<int>(ceil(log(blend_width)/log(2.)) - 1.)); fb->setSharpness(1.f/blend_width);
LOGLN("Multi-band blender, number of bands: " << mb->numBands()); LOGLN("Feather blender, sharpness: " << fb->sharpness());
} }
else if (blend_type == Blender::FEATHER) blender->prepare(corners, sizes);
{ }
FeatherBlender* fb = dynamic_cast<FeatherBlender*>(static_cast<Blender*>(blender));
fb->setSharpness(1.f/blend_width); // Blend the current image
LOGLN("Feather blender, sharpness: " << fb->sharpness()); blender->feed(img_warped_s, mask_warped, corners[img_idx]);
} }
blender->prepare(corners, sizes);
} Mat result, result_mask;
blender->blend(result, result_mask);
// Blend the current image
blender->feed(img_warped_s, mask_warped, corners[img_idx]); LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
}
imwrite(result_name, result);
Mat result, result_mask;
blender->blend(result, result_mask); LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec");
return 0;
LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec"); }
imwrite(result_name, result);
LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec");
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment