// 3) Automatic Panoramic Image Stitching using Invariant Features. \r
// Matthew Brown and David G. Lowe. 2007.\r
\r
+#include <fstream>\r
#include "precomp.hpp"\r
#include "util.hpp"\r
#include "warpers.hpp"\r
" Bundle adjustment cost function. The default is 'focal_ray'.\n"\r
" --wave_correct (no|yes)\n"\r
" Perform wave effect correction. The default is 'yes'.\n"\r
+ " --save_graph <file_name>\n"\r
+ " Save matches graph represented in DOT language to <file_name> file.\n"\r
+ " Labels description: Nm is number of matches, Ni is number of inliers,\n"\r
+ " C is confidence.\n"\r
"\nCompositing Flags:\n"\r
" --warp (plane|cylindrical|spherical)\n" \r
" Warp surface type. The default is 'spherical'.\n"\r
int ba_space = BundleAdjuster::FOCAL_RAY_SPACE;\r
float conf_thresh = 1.f;\r
bool wave_correct = true;\r
+bool save_graph = false;\r
+std::string save_graph_to;\r
int warp_type = Warper::SPHERICAL;\r
int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;\r
float match_conf = 0.65f;\r
}\r
i++;\r
}\r
+ else if (string(argv[i]) == "--save_graph")\r
+ {\r
+ save_graph = true;\r
+ save_graph_to = argv[i + 1];\r
+ i++;\r
+ }\r
else if (string(argv[i]) == "--warp")\r
{\r
if (string(argv[i + 1]) == "plane")\r
matcher.releaseMemory();\r
LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");\r
\r
+ // Check if we should save matches graph\r
+ if (save_graph)\r
+ {\r
+ LOGLN("Saving matches graph...");\r
+ ofstream f(save_graph_to.c_str());\r
+ f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh);\r
+ }\r
+\r
// Leave only images we are sure are from the same panorama\r
vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh);\r
vector<Mat> img_subset;\r
// or tort (including negligence or otherwise) arising in any way out of\r
// the use of this software, even if advised of the possibility of such damage.\r
//\r
-//M*/
-#ifndef __OPENCV_MATCHERS_HPP__
-#define __OPENCV_MATCHERS_HPP__
-
-#include "precomp.hpp"
-
-struct ImageFeatures
-{
- int img_idx;
- cv::Size img_size;
- std::vector<cv::KeyPoint> keypoints;
- cv::Mat descriptors;
-};
-
-
-class FeaturesFinder
-{
-public:
- virtual ~FeaturesFinder() {}
- void operator ()(const cv::Mat &image, ImageFeatures &features);
-
- virtual void releaseMemory() {}
-
-protected:
- virtual void find(const cv::Mat &image, ImageFeatures &features) = 0;
-};
-
-
-class SurfFeaturesFinder : public FeaturesFinder
-{
-public:
- SurfFeaturesFinder(bool try_use_gpu = true, double hess_thresh = 300.0,
- int num_octaves = 3, int num_layers = 4,
- int num_octaves_descr = 4, int num_layers_descr = 2);
-
- void releaseMemory();
-
-protected:
- void find(const cv::Mat &image, ImageFeatures &features);
-
- cv::Ptr<FeaturesFinder> impl_;
-};
-
-
-struct MatchesInfo
-{
- MatchesInfo();
- MatchesInfo(const MatchesInfo &other);
- const MatchesInfo& operator =(const MatchesInfo &other);
-
- int src_img_idx, dst_img_idx; // Images indices (optional)
- std::vector<cv::DMatch> matches;
- std::vector<uchar> inliers_mask; // Geometrically consistent matches mask
- int num_inliers; // Number of geometrically consistent matches
- cv::Mat H; // Estimated homography
- double confidence; // Confidence two images are from the same panorama
-};
-
-
-class FeaturesMatcher
-{
-public:
- virtual ~FeaturesMatcher() {}
-
- void operator ()(const ImageFeatures &features1, const ImageFeatures &features2,
- MatchesInfo& matches_info) { match(features1, features2, matches_info); }
- void operator ()(const std::vector<ImageFeatures> &features, std::vector<MatchesInfo> &pairwise_matches);
-
- bool isThreadSafe() const { return is_thread_safe_; }
-
- virtual void releaseMemory() {}
-
-protected:
- FeaturesMatcher(bool is_thread_safe = false) : is_thread_safe_(is_thread_safe) {}
-
- virtual void match(const ImageFeatures &features1, const ImageFeatures &features2,
- MatchesInfo& matches_info) = 0;
-
- bool is_thread_safe_;
-};
-
-
-class BestOf2NearestMatcher : public FeaturesMatcher
-{
-public:
- BestOf2NearestMatcher(bool try_use_gpu = true, float match_conf = 0.55f, int num_matches_thresh1 = 6,
- int num_matches_thresh2 = 6);
-
- void releaseMemory();
-
-protected:
- void match(const ImageFeatures &features1, const ImageFeatures &features2, MatchesInfo &matches_info);
-
- int num_matches_thresh1_;
- int num_matches_thresh2_;
- cv::Ptr<FeaturesMatcher> impl_;
-};
-
-#endif // __OPENCV_MATCHERS_HPP__
\ No newline at end of file
+//M*/\r
+#ifndef __OPENCV_MATCHERS_HPP__\r
+#define __OPENCV_MATCHERS_HPP__\r
+\r
+#include "precomp.hpp"\r
+\r
+struct ImageFeatures\r
+{\r
+ int img_idx;\r
+ cv::Size img_size;\r
+ std::vector<cv::KeyPoint> keypoints;\r
+ cv::Mat descriptors;\r
+};\r
+\r
+\r
+class FeaturesFinder\r
+{\r
+public:\r
+ virtual ~FeaturesFinder() {}\r
+ void operator ()(const cv::Mat &image, ImageFeatures &features);\r
+\r
+ virtual void releaseMemory() {}\r
+\r
+protected:\r
+ virtual void find(const cv::Mat &image, ImageFeatures &features) = 0;\r
+};\r
+\r
+\r
+class SurfFeaturesFinder : public FeaturesFinder\r
+{\r
+public:\r
+ SurfFeaturesFinder(bool try_use_gpu = true, double hess_thresh = 300.0, \r
+ int num_octaves = 3, int num_layers = 4, \r
+ int num_octaves_descr = 4, int num_layers_descr = 2);\r
+\r
+ void releaseMemory();\r
+\r
+protected:\r
+ void find(const cv::Mat &image, ImageFeatures &features);\r
+\r
+ cv::Ptr<FeaturesFinder> impl_;\r
+};\r
+\r
+\r
+struct MatchesInfo\r
+{\r
+ MatchesInfo();\r
+ MatchesInfo(const MatchesInfo &other);\r
+ const MatchesInfo& operator =(const MatchesInfo &other);\r
+\r
+ int src_img_idx, dst_img_idx; // Images indices (optional)\r
+ std::vector<cv::DMatch> matches;\r
+ std::vector<uchar> inliers_mask; // Geometrically consistent matches mask\r
+ int num_inliers; // Number of geometrically consistent matches\r
+ cv::Mat H; // Estimated homography\r
+ double confidence; // Confidence two images are from the same panorama\r
+};\r
+\r
+\r
+class FeaturesMatcher\r
+{\r
+public:\r
+ virtual ~FeaturesMatcher() {}\r
+\r
+ void operator ()(const ImageFeatures &features1, const ImageFeatures &features2, \r
+ MatchesInfo& matches_info) { match(features1, features2, matches_info); }\r
+ void operator ()(const std::vector<ImageFeatures> &features, std::vector<MatchesInfo> &pairwise_matches);\r
+\r
+ bool isThreadSafe() const { return is_thread_safe_; }\r
+\r
+ virtual void releaseMemory() {}\r
+\r
+protected:\r
+ FeaturesMatcher(bool is_thread_safe = false) : is_thread_safe_(is_thread_safe) {}\r
+\r
+ virtual void match(const ImageFeatures &features1, const ImageFeatures &features2, \r
+ MatchesInfo& matches_info) = 0;\r
+\r
+ bool is_thread_safe_;\r
+};\r
+\r
+\r
+class BestOf2NearestMatcher : public FeaturesMatcher\r
+{\r
+public:\r
+ BestOf2NearestMatcher(bool try_use_gpu = true, float match_conf = 0.55f, int num_matches_thresh1 = 6, \r
+ int num_matches_thresh2 = 6);\r
+\r
+ void releaseMemory();\r
+\r
+protected:\r
+ void match(const ImageFeatures &features1, const ImageFeatures &features2, MatchesInfo &matches_info);\r
+\r
+ int num_matches_thresh1_;\r
+ int num_matches_thresh2_;\r
+ cv::Ptr<FeaturesMatcher> impl_;\r
+};\r
+\r
+#endif // __OPENCV_MATCHERS_HPP__\r
//\r
//M*/\r
#include <algorithm>\r
+#include <sstream>\r
#include "autocalib.hpp"\r
#include "motion_estimators.hpp"\r
#include "util.hpp"\r
\r
//////////////////////////////////////////////////////////////////////////////\r
\r
+string matchesGraphAsString(vector<string> &pathes, vector<MatchesInfo> &pairwise_matches,\r
+ float conf_threshold)\r
+{\r
+ stringstream str;\r
+ str << "graph matches_graph{\n";\r
+\r
+ set<int> added_imgs;\r
+\r
+ // Add matches\r
+ for (size_t i = 0; i < pairwise_matches.size(); ++i)\r
+ {\r
+ if (pairwise_matches[i].src_img_idx < pairwise_matches[i].dst_img_idx &&\r
+ pairwise_matches[i].confidence > conf_threshold)\r
+ {\r
+ string name_src = pathes[pairwise_matches[i].src_img_idx];\r
+ size_t prefix_len = name_src.find_last_of("/\\");\r
+ if (prefix_len != string::npos) prefix_len++; else prefix_len = 0;\r
+ name_src = name_src.substr(prefix_len, name_src.size() - prefix_len);\r
+\r
+ string name_dst = pathes[pairwise_matches[i].dst_img_idx];\r
+ prefix_len = name_dst.find_last_of("/\\");\r
+ if (prefix_len != string::npos) prefix_len++; else prefix_len = 0;\r
+ name_dst = name_dst.substr(prefix_len, name_dst.size() - prefix_len);\r
+\r
+ added_imgs.insert(pairwise_matches[i].src_img_idx);\r
+ added_imgs.insert(pairwise_matches[i].dst_img_idx);\r
+\r
+ str << "\"" << name_src << "\" -- \"" << name_dst << "\""\r
+ << "[label=\"Nm=" << pairwise_matches[i].matches.size()\r
+ << ", Ni=" << pairwise_matches[i].num_inliers\r
+ << ", C=" << pairwise_matches[i].confidence << "\"];\n";\r
+ }\r
+ }\r
+\r
+ // Add unmatched images\r
+ for (size_t i = 0; i < pairwise_matches.size(); ++i)\r
+ {\r
+ if (pairwise_matches[i].src_img_idx < pairwise_matches[i].dst_img_idx)\r
+ {\r
+ if (added_imgs.find(pairwise_matches[i].src_img_idx) == added_imgs.end())\r
+ {\r
+ added_imgs.insert(pairwise_matches[i].src_img_idx);\r
+ string name = pathes[pairwise_matches[i].src_img_idx];\r
+ size_t prefix_len = name.find_last_of("/\\");\r
+ if (prefix_len != string::npos) prefix_len++; else prefix_len = 0;\r
+ name = name.substr(prefix_len, name.size() - prefix_len);\r
+ str << "\"" << name << "\";\n";\r
+ }\r
+ if (added_imgs.find(pairwise_matches[i].dst_img_idx) == added_imgs.end())\r
+ {\r
+ added_imgs.insert(pairwise_matches[i].dst_img_idx);\r
+ string name = pathes[pairwise_matches[i].dst_img_idx];\r
+ size_t prefix_len = name.find_last_of("/\\");\r
+ if (prefix_len != string::npos) prefix_len++; else prefix_len = 0;\r
+ name = name.substr(prefix_len, name.size() - prefix_len);\r
+ str << "\"" << name << "\";\n";\r
+ }\r
+ }\r
+ }\r
+\r
+ str << "}";\r
+ return str.str();\r
+}\r
+\r
vector<int> leaveBiggestComponent(vector<ImageFeatures> &features, vector<MatchesInfo> &pairwise_matches, \r
float conf_threshold)\r
{\r
// or tort (including negligence or otherwise) arising in any way out of\r
// the use of this software, even if advised of the possibility of such damage.\r
//\r
-//M*/
-#ifndef __OPENCV_MOTION_ESTIMATORS_HPP__
-#define __OPENCV_MOTION_ESTIMATORS_HPP__
-
-#include "precomp.hpp"
-#include "matchers.hpp"
-#include "util.hpp"
-
-struct CameraParams
-{
- CameraParams();
- CameraParams(const CameraParams& other);
- const CameraParams& operator =(const CameraParams& other);
-
- double focal; // Focal length
- cv::Mat R; // Rotation
- cv::Mat t; // Translation
-};
-
-
-class Estimator
-{
-public:
- void operator ()(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches,
- std::vector<CameraParams> &cameras)
- {
- estimate(features, pairwise_matches, cameras);
- }
-
-protected:
- virtual void estimate(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches,
- std::vector<CameraParams> &cameras) = 0;
-};
-
-
-class HomographyBasedEstimator : public Estimator
-{
-public:
- HomographyBasedEstimator() : is_focals_estimated_(false) {}
- bool isFocalsEstimated() const { return is_focals_estimated_; }
-
-private:
- void estimate(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches,
- std::vector<CameraParams> &cameras);
-
- bool is_focals_estimated_;
-};
-
-
-class BundleAdjuster : public Estimator
-{
-public:
- enum { RAY_SPACE, FOCAL_RAY_SPACE };
-
- BundleAdjuster(int cost_space = FOCAL_RAY_SPACE, float conf_thresh = 1.f)
- : cost_space_(cost_space), conf_thresh_(conf_thresh) {}
-
-private:
- void estimate(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches,
- std::vector<CameraParams> &cameras);
-
- void calcError(cv::Mat &err);
- void calcJacobian();
-
- int num_images_;
- int total_num_matches_;
- const ImageFeatures *features_;
- const MatchesInfo *pairwise_matches_;
- cv::Mat cameras_;
- std::vector<std::pair<int,int> > edges_;
-
- int cost_space_;
- float conf_thresh_;
- cv::Mat err_, err1_, err2_;
- cv::Mat J_;
-};
-
-
-void waveCorrect(std::vector<cv::Mat> &rmats);
-
-
-//////////////////////////////////////////////////////////////////////////////
-// Auxiliary functions
-
-std::vector<int> leaveBiggestComponent(std::vector<ImageFeatures> &features, std::vector<MatchesInfo> &pairwise_matches,
- float conf_threshold);
-
-void findMaxSpanningTree(int num_images, const std::vector<MatchesInfo> &pairwise_matches,
- Graph &span_tree, std::vector<int> ¢ers);
-
-#endif // __OPENCV_MOTION_ESTIMATORS_HPP__
+//M*/\r
+#ifndef __OPENCV_MOTION_ESTIMATORS_HPP__\r
+#define __OPENCV_MOTION_ESTIMATORS_HPP__\r
+\r
+#include "precomp.hpp"\r
+#include "matchers.hpp"\r
+#include "util.hpp"\r
+\r
+struct CameraParams\r
+{\r
+ CameraParams();\r
+ CameraParams(const CameraParams& other);\r
+ const CameraParams& operator =(const CameraParams& other);\r
+\r
+ double focal; // Focal length\r
+ cv::Mat R; // Rotation\r
+ cv::Mat t; // Translation\r
+};\r
+\r
+\r
+class Estimator\r
+{\r
+public:\r
+ void operator ()(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches, \r
+ std::vector<CameraParams> &cameras)\r
+ {\r
+ estimate(features, pairwise_matches, cameras);\r
+ }\r
+\r
+protected:\r
+ virtual void estimate(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches, \r
+ std::vector<CameraParams> &cameras) = 0;\r
+};\r
+\r
+\r
+class HomographyBasedEstimator : public Estimator\r
+{\r
+public:\r
+ HomographyBasedEstimator() : is_focals_estimated_(false) {}\r
+ bool isFocalsEstimated() const { return is_focals_estimated_; }\r
+\r
+private: \r
+ void estimate(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches, \r
+ std::vector<CameraParams> &cameras);\r
+\r
+ bool is_focals_estimated_;\r
+};\r
+\r
+\r
+class BundleAdjuster : public Estimator\r
+{\r
+public:\r
+ enum { RAY_SPACE, FOCAL_RAY_SPACE };\r
+\r
+ BundleAdjuster(int cost_space = FOCAL_RAY_SPACE, float conf_thresh = 1.f) \r
+ : cost_space_(cost_space), conf_thresh_(conf_thresh) {}\r
+\r
+private:\r
+ void estimate(const std::vector<ImageFeatures> &features, const std::vector<MatchesInfo> &pairwise_matches, \r
+ std::vector<CameraParams> &cameras);\r
+\r
+ void calcError(cv::Mat &err);\r
+ void calcJacobian();\r
+\r
+ int num_images_;\r
+ int total_num_matches_;\r
+ const ImageFeatures *features_;\r
+ const MatchesInfo *pairwise_matches_;\r
+ cv::Mat cameras_;\r
+ std::vector<std::pair<int,int> > edges_;\r
+\r
+ int cost_space_;\r
+ float conf_thresh_;\r
+ cv::Mat err_, err1_, err2_;\r
+ cv::Mat J_;\r
+};\r
+\r
+\r
+void waveCorrect(std::vector<cv::Mat> &rmats);\r
+\r
+\r
+//////////////////////////////////////////////////////////////////////////////\r
+// Auxiliary functions\r
+\r
+// Returns matches graph representation in DOT language\r
+std::string matchesGraphAsString(std::vector<std::string> &pathes, std::vector<MatchesInfo> &pairwise_matches,\r
+ float conf_threshold);\r
+\r
+std::vector<int> leaveBiggestComponent(std::vector<ImageFeatures> &features, std::vector<MatchesInfo> &pairwise_matches, \r
+ float conf_threshold);\r
+\r
+void findMaxSpanningTree(int num_images, const std::vector<MatchesInfo> &pairwise_matches, \r
+ Graph &span_tree, std::vector<int> ¢ers);\r
+\r
+#endif // __OPENCV_MOTION_ESTIMATORS_HPP__\r
cv::Rect resultRoi(const std::vector<cv::Point> &corners, const std::vector<cv::Mat> &images);\r
cv::Rect resultRoi(const std::vector<cv::Point> &corners, const std::vector<cv::Size> &sizes);\r
cv::Point resultTl(const std::vector<cv::Point> &corners);\r
-void selectRandomSubset(int count, int size, std::vector<int> &subset);\r
\r
+// Returns random 'count' element subset of the {0,1,...,size-1} set\r
+void selectRandomSubset(int count, int size, std::vector<int> &subset);\r
\r
#include "util_inl.hpp"\r
\r