stitching_detailed.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. #include <iostream>
  2. #include <fstream>
  3. #include <string>
  4. #include "opencv2/opencv_modules.hpp"
  5. #include <opencv2/core/utility.hpp>
  6. #include "opencv2/imgcodecs.hpp"
  7. #include "opencv2/highgui.hpp"
  8. #include "opencv2/stitching/detail/autocalib.hpp"
  9. #include "opencv2/stitching/detail/blenders.hpp"
  10. #include "opencv2/stitching/detail/timelapsers.hpp"
  11. #include "opencv2/stitching/detail/camera.hpp"
  12. #include "opencv2/stitching/detail/exposure_compensate.hpp"
  13. #include "opencv2/stitching/detail/matchers.hpp"
  14. #include "opencv2/stitching/detail/motion_estimators.hpp"
  15. #include "opencv2/stitching/detail/seam_finders.hpp"
  16. #include "opencv2/stitching/detail/warpers.hpp"
  17. #include "opencv2/stitching/warpers.hpp"
  18. #ifdef HAVE_OPENCV_XFEATURES2D
  19. #include "opencv2/xfeatures2d.hpp"
  20. #include "opencv2/xfeatures2d/nonfree.hpp"
  21. #endif
  22. #define ENABLE_LOG 1
  23. #define LOG(msg) std::cout << msg
  24. #define LOGLN(msg) std::cout << msg << std::endl
  25. using namespace std;
  26. using namespace cv;
  27. using namespace cv::detail;
  28. static void printUsage(char** argv)
  29. {
  30. cout <<
  31. "Rotation model images stitcher.\n\n"
  32. << argv[0] << " img1 img2 [...imgN] [flags]\n\n"
  33. "Flags:\n"
  34. " --preview\n"
  35. " Run stitching in the preview mode. Works faster than usual mode,\n"
  36. " but output image will have lower resolution.\n"
  37. " --try_cuda (yes|no)\n"
  38. " Try to use CUDA. The default value is 'no'. All default values\n"
  39. " are for CPU mode.\n"
  40. "\nMotion Estimation Flags:\n"
  41. " --work_megapix <float>\n"
  42. " Resolution for image registration step. The default is 0.6 Mpx.\n"
  43. " --features (surf|orb|sift|akaze)\n"
  44. " Type of features used for images matching.\n"
  45. " The default is surf if available, orb otherwise.\n"
  46. " --matcher (homography|affine)\n"
  47. " Matcher used for pairwise image matching.\n"
  48. " --estimator (homography|affine)\n"
  49. " Type of estimator used for transformation estimation.\n"
  50. " --match_conf <float>\n"
  51. " Confidence for feature matching step. The default is 0.65 for surf and 0.3 for orb.\n"
  52. " --conf_thresh <float>\n"
  53. " Threshold for two images are from the same panorama confidence.\n"
  54. " The default is 1.0.\n"
  55. " --ba (no|reproj|ray|affine)\n"
  56. " Bundle adjustment cost function. The default is ray.\n"
  57. " --ba_refine_mask (mask)\n"
  58. " Set refinement mask for bundle adjustment. It looks like 'x_xxx',\n"
  59. " where 'x' means refine respective parameter and '_' means don't\n"
  60. " refine one, and has the following format:\n"
  61. " <fx><skew><ppx><aspect><ppy>. The default mask is 'xxxxx'. If bundle\n"
  62. " adjustment doesn't support estimation of selected parameter then\n"
  63. " the respective flag is ignored.\n"
  64. " --wave_correct (no|horiz|vert)\n"
  65. " Perform wave effect correction. The default is 'horiz'.\n"
  66. " --save_graph <file_name>\n"
  67. " Save matches graph represented in DOT language to <file_name> file.\n"
  68. " Labels description: Nm is number of matches, Ni is number of inliers,\n"
  69. " C is confidence.\n"
  70. "\nCompositing Flags:\n"
  71. " --warp (affine|plane|cylindrical|spherical|fisheye|stereographic|compressedPlaneA2B1|compressedPlaneA1.5B1|compressedPlanePortraitA2B1|compressedPlanePortraitA1.5B1|paniniA2B1|paniniA1.5B1|paniniPortraitA2B1|paniniPortraitA1.5B1|mercator|transverseMercator)\n"
  72. " Warp surface type. The default is 'spherical'.\n"
  73. " --seam_megapix <float>\n"
  74. " Resolution for seam estimation step. The default is 0.1 Mpx.\n"
  75. " --seam (no|voronoi|gc_color|gc_colorgrad)\n"
  76. " Seam estimation method. The default is 'gc_color'.\n"
  77. " --compose_megapix <float>\n"
  78. " Resolution for compositing step. Use -1 for original resolution.\n"
  79. " The default is -1.\n"
  80. " --expos_comp (no|gain|gain_blocks|channels|channels_blocks)\n"
  81. " Exposure compensation method. The default is 'gain_blocks'.\n"
  82. " --expos_comp_nr_feeds <int>\n"
  83. " Number of exposure compensation feed. The default is 1.\n"
  84. " --expos_comp_nr_filtering <int>\n"
  85. " Number of filtering iterations of the exposure compensation gains.\n"
  86. " Only used when using a block exposure compensation method.\n"
  87. " The default is 2.\n"
  88. " --expos_comp_block_size <int>\n"
  89. " BLock size in pixels used by the exposure compensator.\n"
  90. " Only used when using a block exposure compensation method.\n"
  91. " The default is 32.\n"
  92. " --blend (no|feather|multiband)\n"
  93. " Blending method. The default is 'multiband'.\n"
  94. " --blend_strength <float>\n"
  95. " Blending strength from [0,100] range. The default is 5.\n"
  96. " --output <result_img>\n"
  97. " The default is 'result.jpg'.\n"
  98. " --timelapse (as_is|crop) \n"
  99. " Output warped images separately as frames of a time lapse movie, with 'fixed_' prepended to input file names.\n"
  100. " --rangewidth <int>\n"
  101. " uses range_width to limit number of images to match with.\n";
  102. }
  103. // Default command line args
  104. vector<String> img_names;
  105. bool preview = false;
  106. bool try_cuda = false;
  107. double work_megapix = 0.6;
  108. double seam_megapix = 0.1;
  109. double compose_megapix = -1;
  110. float conf_thresh = 1.f;
  111. #ifdef HAVE_OPENCV_XFEATURES2D
  112. string features_type = "surf";
  113. float match_conf = 0.65f;
  114. #else
  115. string features_type = "orb";
  116. float match_conf = 0.3f;
  117. #endif
  118. string matcher_type = "homography";
  119. string estimator_type = "homography";
  120. string ba_cost_func = "ray";
  121. string ba_refine_mask = "xxxxx";
  122. bool do_wave_correct = true;
  123. WaveCorrectKind wave_correct = detail::WAVE_CORRECT_HORIZ;
  124. bool save_graph = false;
  125. std::string save_graph_to;
  126. string warp_type = "spherical";
  127. int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
  128. int expos_comp_nr_feeds = 1;
  129. int expos_comp_nr_filtering = 2;
  130. int expos_comp_block_size = 32;
  131. string seam_find_type = "gc_color";
  132. int blend_type = Blender::MULTI_BAND;
  133. int timelapse_type = Timelapser::AS_IS;
  134. float blend_strength = 5;
  135. string result_name = "result.jpg";
  136. bool timelapse = false;
  137. int range_width = -1;
  138. static int parseCmdArgs(int argc, char** argv)
  139. {
  140. if (argc == 1)
  141. {
  142. printUsage(argv);
  143. return -1;
  144. }
  145. for (int i = 1; i < argc; ++i)
  146. {
  147. if (string(argv[i]) == "--help" || string(argv[i]) == "/?")
  148. {
  149. printUsage(argv);
  150. return -1;
  151. }
  152. else if (string(argv[i]) == "--preview")
  153. {
  154. preview = true;
  155. }
  156. else if (string(argv[i]) == "--try_cuda")
  157. {
  158. if (string(argv[i + 1]) == "no")
  159. try_cuda = false;
  160. else if (string(argv[i + 1]) == "yes")
  161. try_cuda = true;
  162. else
  163. {
  164. cout << "Bad --try_cuda flag value\n";
  165. return -1;
  166. }
  167. i++;
  168. }
  169. else if (string(argv[i]) == "--work_megapix")
  170. {
  171. work_megapix = atof(argv[i + 1]);
  172. i++;
  173. }
  174. else if (string(argv[i]) == "--seam_megapix")
  175. {
  176. seam_megapix = atof(argv[i + 1]);
  177. i++;
  178. }
  179. else if (string(argv[i]) == "--compose_megapix")
  180. {
  181. compose_megapix = atof(argv[i + 1]);
  182. i++;
  183. }
  184. else if (string(argv[i]) == "--result")
  185. {
  186. result_name = argv[i + 1];
  187. i++;
  188. }
  189. else if (string(argv[i]) == "--features")
  190. {
  191. features_type = argv[i + 1];
  192. if (string(features_type) == "orb")
  193. match_conf = 0.3f;
  194. i++;
  195. }
  196. else if (string(argv[i]) == "--matcher")
  197. {
  198. if (string(argv[i + 1]) == "homography" || string(argv[i + 1]) == "affine")
  199. matcher_type = argv[i + 1];
  200. else
  201. {
  202. cout << "Bad --matcher flag value\n";
  203. return -1;
  204. }
  205. i++;
  206. }
  207. else if (string(argv[i]) == "--estimator")
  208. {
  209. if (string(argv[i + 1]) == "homography" || string(argv[i + 1]) == "affine")
  210. estimator_type = argv[i + 1];
  211. else
  212. {
  213. cout << "Bad --estimator flag value\n";
  214. return -1;
  215. }
  216. i++;
  217. }
  218. else if (string(argv[i]) == "--match_conf")
  219. {
  220. match_conf = static_cast<float>(atof(argv[i + 1]));
  221. i++;
  222. }
  223. else if (string(argv[i]) == "--conf_thresh")
  224. {
  225. conf_thresh = static_cast<float>(atof(argv[i + 1]));
  226. i++;
  227. }
  228. else if (string(argv[i]) == "--ba")
  229. {
  230. ba_cost_func = argv[i + 1];
  231. i++;
  232. }
  233. else if (string(argv[i]) == "--ba_refine_mask")
  234. {
  235. ba_refine_mask = argv[i + 1];
  236. if (ba_refine_mask.size() != 5)
  237. {
  238. cout << "Incorrect refinement mask length.\n";
  239. return -1;
  240. }
  241. i++;
  242. }
  243. else if (string(argv[i]) == "--wave_correct")
  244. {
  245. if (string(argv[i + 1]) == "no")
  246. do_wave_correct = false;
  247. else if (string(argv[i + 1]) == "horiz")
  248. {
  249. do_wave_correct = true;
  250. wave_correct = detail::WAVE_CORRECT_HORIZ;
  251. }
  252. else if (string(argv[i + 1]) == "vert")
  253. {
  254. do_wave_correct = true;
  255. wave_correct = detail::WAVE_CORRECT_VERT;
  256. }
  257. else
  258. {
  259. cout << "Bad --wave_correct flag value\n";
  260. return -1;
  261. }
  262. i++;
  263. }
  264. else if (string(argv[i]) == "--save_graph")
  265. {
  266. save_graph = true;
  267. save_graph_to = argv[i + 1];
  268. i++;
  269. }
  270. else if (string(argv[i]) == "--warp")
  271. {
  272. warp_type = string(argv[i + 1]);
  273. i++;
  274. }
  275. else if (string(argv[i]) == "--expos_comp")
  276. {
  277. if (string(argv[i + 1]) == "no")
  278. expos_comp_type = ExposureCompensator::NO;
  279. else if (string(argv[i + 1]) == "gain")
  280. expos_comp_type = ExposureCompensator::GAIN;
  281. else if (string(argv[i + 1]) == "gain_blocks")
  282. expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
  283. else if (string(argv[i + 1]) == "channels")
  284. expos_comp_type = ExposureCompensator::CHANNELS;
  285. else if (string(argv[i + 1]) == "channels_blocks")
  286. expos_comp_type = ExposureCompensator::CHANNELS_BLOCKS;
  287. else
  288. {
  289. cout << "Bad exposure compensation method\n";
  290. return -1;
  291. }
  292. i++;
  293. }
  294. else if (string(argv[i]) == "--expos_comp_nr_feeds")
  295. {
  296. expos_comp_nr_feeds = atoi(argv[i + 1]);
  297. i++;
  298. }
  299. else if (string(argv[i]) == "--expos_comp_nr_filtering")
  300. {
  301. expos_comp_nr_filtering = atoi(argv[i + 1]);
  302. i++;
  303. }
  304. else if (string(argv[i]) == "--expos_comp_block_size")
  305. {
  306. expos_comp_block_size = atoi(argv[i + 1]);
  307. i++;
  308. }
  309. else if (string(argv[i]) == "--seam")
  310. {
  311. if (string(argv[i + 1]) == "no" ||
  312. string(argv[i + 1]) == "voronoi" ||
  313. string(argv[i + 1]) == "gc_color" ||
  314. string(argv[i + 1]) == "gc_colorgrad" ||
  315. string(argv[i + 1]) == "dp_color" ||
  316. string(argv[i + 1]) == "dp_colorgrad")
  317. seam_find_type = argv[i + 1];
  318. else
  319. {
  320. cout << "Bad seam finding method\n";
  321. return -1;
  322. }
  323. i++;
  324. }
  325. else if (string(argv[i]) == "--blend")
  326. {
  327. if (string(argv[i + 1]) == "no")
  328. blend_type = Blender::NO;
  329. else if (string(argv[i + 1]) == "feather")
  330. blend_type = Blender::FEATHER;
  331. else if (string(argv[i + 1]) == "multiband")
  332. blend_type = Blender::MULTI_BAND;
  333. else
  334. {
  335. cout << "Bad blending method\n";
  336. return -1;
  337. }
  338. i++;
  339. }
  340. else if (string(argv[i]) == "--timelapse")
  341. {
  342. timelapse = true;
  343. if (string(argv[i + 1]) == "as_is")
  344. timelapse_type = Timelapser::AS_IS;
  345. else if (string(argv[i + 1]) == "crop")
  346. timelapse_type = Timelapser::CROP;
  347. else
  348. {
  349. cout << "Bad timelapse method\n";
  350. return -1;
  351. }
  352. i++;
  353. }
  354. else if (string(argv[i]) == "--rangewidth")
  355. {
  356. range_width = atoi(argv[i + 1]);
  357. i++;
  358. }
  359. else if (string(argv[i]) == "--blend_strength")
  360. {
  361. blend_strength = static_cast<float>(atof(argv[i + 1]));
  362. i++;
  363. }
  364. else if (string(argv[i]) == "--output")
  365. {
  366. result_name = argv[i + 1];
  367. i++;
  368. }
  369. else
  370. img_names.push_back(argv[i]);
  371. }
  372. if (preview)
  373. {
  374. compose_megapix = 0.6;
  375. }
  376. return 0;
  377. }
  378. int main(int argc, char* argv[])
  379. {
  380. #if ENABLE_LOG
  381. int64 app_start_time = getTickCount();
  382. #endif
  383. #if 0
  384. cv::setBreakOnError(true);
  385. #endif
  386. int retval = parseCmdArgs(argc, argv);
  387. if (retval)
  388. return retval;
  389. // Check if have enough images
  390. int num_images = static_cast<int>(img_names.size());
  391. if (num_images < 2)
  392. {
  393. LOGLN("Need more images");
  394. return -1;
  395. }
  396. double work_scale = 1, seam_scale = 1, compose_scale = 1;
  397. bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false;
  398. LOGLN("Finding features...");
  399. #if ENABLE_LOG
  400. int64 t = getTickCount();
  401. #endif
  402. Ptr<Feature2D> finder;
  403. if (features_type == "orb")
  404. {
  405. finder = ORB::create();
  406. }
  407. else if (features_type == "akaze")
  408. {
  409. finder = AKAZE::create();
  410. }
  411. #ifdef HAVE_OPENCV_XFEATURES2D
  412. else if (features_type == "surf")
  413. {
  414. finder = xfeatures2d::SURF::create();
  415. }
  416. #endif
  417. else if (features_type == "sift")
  418. {
  419. finder = SIFT::create();
  420. }
  421. else
  422. {
  423. cout << "Unknown 2D features type: '" << features_type << "'.\n";
  424. return -1;
  425. }
  426. Mat full_img, img;
  427. vector<ImageFeatures> features(num_images);
  428. vector<Mat> images(num_images);
  429. vector<Size> full_img_sizes(num_images);
  430. double seam_work_aspect = 1;
  431. for (int i = 0; i < num_images; ++i)
  432. {
  433. full_img = imread(samples::findFile(img_names[i]));
  434. full_img_sizes[i] = full_img.size();
  435. if (full_img.empty())
  436. {
  437. LOGLN("Can't open image " << img_names[i]);
  438. return -1;
  439. }
  440. if (work_megapix < 0)
  441. {
  442. img = full_img;
  443. work_scale = 1;
  444. is_work_scale_set = true;
  445. }
  446. else
  447. {
  448. if (!is_work_scale_set)
  449. {
  450. work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area()));
  451. is_work_scale_set = true;
  452. }
  453. resize(full_img, img, Size(), work_scale, work_scale, INTER_LINEAR_EXACT);
  454. }
  455. if (!is_seam_scale_set)
  456. {
  457. seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area()));
  458. seam_work_aspect = seam_scale / work_scale;
  459. is_seam_scale_set = true;
  460. }
  461. computeImageFeatures(finder, img, features[i]);
  462. features[i].img_idx = i;
  463. LOGLN("Features in image #" << i+1 << ": " << features[i].keypoints.size());
  464. resize(full_img, img, Size(), seam_scale, seam_scale, INTER_LINEAR_EXACT);
  465. images[i] = img.clone();
  466. }
  467. full_img.release();
  468. img.release();
  469. LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
  470. LOG("Pairwise matching");
  471. #if ENABLE_LOG
  472. t = getTickCount();
  473. #endif
  474. vector<MatchesInfo> pairwise_matches;
  475. Ptr<FeaturesMatcher> matcher;
  476. if (matcher_type == "affine")
  477. matcher = makePtr<AffineBestOf2NearestMatcher>(false, try_cuda, match_conf);
  478. else if (range_width==-1)
  479. matcher = makePtr<BestOf2NearestMatcher>(try_cuda, match_conf);
  480. else
  481. matcher = makePtr<BestOf2NearestRangeMatcher>(range_width, try_cuda, match_conf);
  482. (*matcher)(features, pairwise_matches);
  483. matcher->collectGarbage();
  484. LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
  485. // Check if we should save matches graph
  486. if (save_graph)
  487. {
  488. LOGLN("Saving matches graph...");
  489. ofstream f(save_graph_to.c_str());
  490. f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh);
  491. }
  492. // Leave only images we are sure are from the same panorama
  493. vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh);
  494. vector<Mat> img_subset;
  495. vector<String> img_names_subset;
  496. vector<Size> full_img_sizes_subset;
  497. for (size_t i = 0; i < indices.size(); ++i)
  498. {
  499. img_names_subset.push_back(img_names[indices[i]]);
  500. img_subset.push_back(images[indices[i]]);
  501. full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);
  502. }
  503. images = img_subset;
  504. img_names = img_names_subset;
  505. full_img_sizes = full_img_sizes_subset;
  506. // Check if we still have enough images
  507. num_images = static_cast<int>(img_names.size());
  508. if (num_images < 2)
  509. {
  510. LOGLN("Need more images");
  511. return -1;
  512. }
  513. Ptr<Estimator> estimator;
  514. if (estimator_type == "affine")
  515. estimator = makePtr<AffineBasedEstimator>();
  516. else
  517. estimator = makePtr<HomographyBasedEstimator>();
  518. vector<CameraParams> cameras;
  519. if (!(*estimator)(features, pairwise_matches, cameras))
  520. {
  521. cout << "Homography estimation failed.\n";
  522. return -1;
  523. }
  524. for (size_t i = 0; i < cameras.size(); ++i)
  525. {
  526. Mat R;
  527. cameras[i].R.convertTo(R, CV_32F);
  528. cameras[i].R = R;
  529. LOGLN("Initial camera intrinsics #" << indices[i]+1 << ":\nK:\n" << cameras[i].K() << "\nR:\n" << cameras[i].R);
  530. }
  531. Ptr<detail::BundleAdjusterBase> adjuster;
  532. if (ba_cost_func == "reproj") adjuster = makePtr<detail::BundleAdjusterReproj>();
  533. else if (ba_cost_func == "ray") adjuster = makePtr<detail::BundleAdjusterRay>();
  534. else if (ba_cost_func == "affine") adjuster = makePtr<detail::BundleAdjusterAffinePartial>();
  535. else if (ba_cost_func == "no") adjuster = makePtr<NoBundleAdjuster>();
  536. else
  537. {
  538. cout << "Unknown bundle adjustment cost function: '" << ba_cost_func << "'.\n";
  539. return -1;
  540. }
  541. adjuster->setConfThresh(conf_thresh);
  542. Mat_<uchar> refine_mask = Mat::zeros(3, 3, CV_8U);
  543. if (ba_refine_mask[0] == 'x') refine_mask(0,0) = 1;
  544. if (ba_refine_mask[1] == 'x') refine_mask(0,1) = 1;
  545. if (ba_refine_mask[2] == 'x') refine_mask(0,2) = 1;
  546. if (ba_refine_mask[3] == 'x') refine_mask(1,1) = 1;
  547. if (ba_refine_mask[4] == 'x') refine_mask(1,2) = 1;
  548. adjuster->setRefinementMask(refine_mask);
  549. if (!(*adjuster)(features, pairwise_matches, cameras))
  550. {
  551. cout << "Camera parameters adjusting failed.\n";
  552. return -1;
  553. }
  554. // Find median focal length
  555. vector<double> focals;
  556. for (size_t i = 0; i < cameras.size(); ++i)
  557. {
  558. LOGLN("Camera #" << indices[i]+1 << ":\nK:\n" << cameras[i].K() << "\nR:\n" << cameras[i].R);
  559. focals.push_back(cameras[i].focal);
  560. }
  561. sort(focals.begin(), focals.end());
  562. float warped_image_scale;
  563. if (focals.size() % 2 == 1)
  564. warped_image_scale = static_cast<float>(focals[focals.size() / 2]);
  565. else
  566. warped_image_scale = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;
  567. if (do_wave_correct)
  568. {
  569. vector<Mat> rmats;
  570. for (size_t i = 0; i < cameras.size(); ++i)
  571. rmats.push_back(cameras[i].R.clone());
  572. waveCorrect(rmats, wave_correct);
  573. for (size_t i = 0; i < cameras.size(); ++i)
  574. cameras[i].R = rmats[i];
  575. }
  576. LOGLN("Warping images (auxiliary)... ");
  577. #if ENABLE_LOG
  578. t = getTickCount();
  579. #endif
  580. vector<Point> corners(num_images);
  581. vector<UMat> masks_warped(num_images);
  582. vector<UMat> images_warped(num_images);
  583. vector<Size> sizes(num_images);
  584. vector<UMat> masks(num_images);
  585. // Prepare images masks
  586. for (int i = 0; i < num_images; ++i)
  587. {
  588. masks[i].create(images[i].size(), CV_8U);
  589. masks[i].setTo(Scalar::all(255));
  590. }
  591. // Warp images and their masks
  592. Ptr<WarperCreator> warper_creator;
  593. #ifdef HAVE_OPENCV_CUDAWARPING
  594. if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
  595. {
  596. if (warp_type == "plane")
  597. warper_creator = makePtr<cv::PlaneWarperGpu>();
  598. else if (warp_type == "cylindrical")
  599. warper_creator = makePtr<cv::CylindricalWarperGpu>();
  600. else if (warp_type == "spherical")
  601. warper_creator = makePtr<cv::SphericalWarperGpu>();
  602. }
  603. else
  604. #endif
  605. {
  606. if (warp_type == "plane")
  607. warper_creator = makePtr<cv::PlaneWarper>();
  608. else if (warp_type == "affine")
  609. warper_creator = makePtr<cv::AffineWarper>();
  610. else if (warp_type == "cylindrical")
  611. warper_creator = makePtr<cv::CylindricalWarper>();
  612. else if (warp_type == "spherical")
  613. warper_creator = makePtr<cv::SphericalWarper>();
  614. else if (warp_type == "fisheye")
  615. warper_creator = makePtr<cv::FisheyeWarper>();
  616. else if (warp_type == "stereographic")
  617. warper_creator = makePtr<cv::StereographicWarper>();
  618. else if (warp_type == "compressedPlaneA2B1")
  619. warper_creator = makePtr<cv::CompressedRectilinearWarper>(2.0f, 1.0f);
  620. else if (warp_type == "compressedPlaneA1.5B1")
  621. warper_creator = makePtr<cv::CompressedRectilinearWarper>(1.5f, 1.0f);
  622. else if (warp_type == "compressedPlanePortraitA2B1")
  623. warper_creator = makePtr<cv::CompressedRectilinearPortraitWarper>(2.0f, 1.0f);
  624. else if (warp_type == "compressedPlanePortraitA1.5B1")
  625. warper_creator = makePtr<cv::CompressedRectilinearPortraitWarper>(1.5f, 1.0f);
  626. else if (warp_type == "paniniA2B1")
  627. warper_creator = makePtr<cv::PaniniWarper>(2.0f, 1.0f);
  628. else if (warp_type == "paniniA1.5B1")
  629. warper_creator = makePtr<cv::PaniniWarper>(1.5f, 1.0f);
  630. else if (warp_type == "paniniPortraitA2B1")
  631. warper_creator = makePtr<cv::PaniniPortraitWarper>(2.0f, 1.0f);
  632. else if (warp_type == "paniniPortraitA1.5B1")
  633. warper_creator = makePtr<cv::PaniniPortraitWarper>(1.5f, 1.0f);
  634. else if (warp_type == "mercator")
  635. warper_creator = makePtr<cv::MercatorWarper>();
  636. else if (warp_type == "transverseMercator")
  637. warper_creator = makePtr<cv::TransverseMercatorWarper>();
  638. }
  639. if (!warper_creator)
  640. {
  641. cout << "Can't create the following warper '" << warp_type << "'\n";
  642. return 1;
  643. }
  644. Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale * seam_work_aspect));
  645. for (int i = 0; i < num_images; ++i)
  646. {
  647. Mat_<float> K;
  648. cameras[i].K().convertTo(K, CV_32F);
  649. float swa = (float)seam_work_aspect;
  650. K(0,0) *= swa; K(0,2) *= swa;
  651. K(1,1) *= swa; K(1,2) *= swa;
  652. corners[i] = warper->warp(images[i], K, cameras[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);
  653. sizes[i] = images_warped[i].size();
  654. warper->warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
  655. }
  656. vector<UMat> images_warped_f(num_images);
  657. for (int i = 0; i < num_images; ++i)
  658. images_warped[i].convertTo(images_warped_f[i], CV_32F);
  659. LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
  660. LOGLN("Compensating exposure...");
  661. #if ENABLE_LOG
  662. t = getTickCount();
  663. #endif
  664. Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type);
  665. if (dynamic_cast<GainCompensator*>(compensator.get()))
  666. {
  667. GainCompensator* gcompensator = dynamic_cast<GainCompensator*>(compensator.get());
  668. gcompensator->setNrFeeds(expos_comp_nr_feeds);
  669. }
  670. if (dynamic_cast<ChannelsCompensator*>(compensator.get()))
  671. {
  672. ChannelsCompensator* ccompensator = dynamic_cast<ChannelsCompensator*>(compensator.get());
  673. ccompensator->setNrFeeds(expos_comp_nr_feeds);
  674. }
  675. if (dynamic_cast<BlocksCompensator*>(compensator.get()))
  676. {
  677. BlocksCompensator* bcompensator = dynamic_cast<BlocksCompensator*>(compensator.get());
  678. bcompensator->setNrFeeds(expos_comp_nr_feeds);
  679. bcompensator->setNrGainsFilteringIterations(expos_comp_nr_filtering);
  680. bcompensator->setBlockSize(expos_comp_block_size, expos_comp_block_size);
  681. }
  682. compensator->feed(corners, images_warped, masks_warped);
  683. LOGLN("Compensating exposure, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
  684. LOGLN("Finding seams...");
  685. #if ENABLE_LOG
  686. t = getTickCount();
  687. #endif
  688. Ptr<SeamFinder> seam_finder;
  689. if (seam_find_type == "no")
  690. seam_finder = makePtr<detail::NoSeamFinder>();
  691. else if (seam_find_type == "voronoi")
  692. seam_finder = makePtr<detail::VoronoiSeamFinder>();
  693. else if (seam_find_type == "gc_color")
  694. {
  695. #ifdef HAVE_OPENCV_CUDALEGACY
  696. if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
  697. seam_finder = makePtr<detail::GraphCutSeamFinderGpu>(GraphCutSeamFinderBase::COST_COLOR);
  698. else
  699. #endif
  700. seam_finder = makePtr<detail::GraphCutSeamFinder>(GraphCutSeamFinderBase::COST_COLOR);
  701. }
  702. else if (seam_find_type == "gc_colorgrad")
  703. {
  704. #ifdef HAVE_OPENCV_CUDALEGACY
  705. if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
  706. seam_finder = makePtr<detail::GraphCutSeamFinderGpu>(GraphCutSeamFinderBase::COST_COLOR_GRAD);
  707. else
  708. #endif
  709. seam_finder = makePtr<detail::GraphCutSeamFinder>(GraphCutSeamFinderBase::COST_COLOR_GRAD);
  710. }
  711. else if (seam_find_type == "dp_color")
  712. seam_finder = makePtr<detail::DpSeamFinder>(DpSeamFinder::COLOR);
  713. else if (seam_find_type == "dp_colorgrad")
  714. seam_finder = makePtr<detail::DpSeamFinder>(DpSeamFinder::COLOR_GRAD);
  715. if (!seam_finder)
  716. {
  717. cout << "Can't create the following seam finder '" << seam_find_type << "'\n";
  718. return 1;
  719. }
  720. seam_finder->find(images_warped_f, corners, masks_warped);
  721. LOGLN("Finding seams, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
  722. // Release unused memory
  723. images.clear();
  724. images_warped.clear();
  725. images_warped_f.clear();
  726. masks.clear();
  727. LOGLN("Compositing...");
  728. #if ENABLE_LOG
  729. t = getTickCount();
  730. #endif
  731. Mat img_warped, img_warped_s;
  732. Mat dilated_mask, seam_mask, mask, mask_warped;
  733. Ptr<Blender> blender;
  734. Ptr<Timelapser> timelapser;
  735. //double compose_seam_aspect = 1;
  736. double compose_work_aspect = 1;
  737. for (int img_idx = 0; img_idx < num_images; ++img_idx)
  738. {
  739. LOGLN("Compositing image #" << indices[img_idx]+1);
  740. // Read image and resize it if necessary
  741. full_img = imread(samples::findFile(img_names[img_idx]));
  742. if (!is_compose_scale_set)
  743. {
  744. if (compose_megapix > 0)
  745. compose_scale = min(1.0, sqrt(compose_megapix * 1e6 / full_img.size().area()));
  746. is_compose_scale_set = true;
  747. // Compute relative scales
  748. //compose_seam_aspect = compose_scale / seam_scale;
  749. compose_work_aspect = compose_scale / work_scale;
  750. // Update warped image scale
  751. warped_image_scale *= static_cast<float>(compose_work_aspect);
  752. warper = warper_creator->create(warped_image_scale);
  753. // Update corners and sizes
  754. for (int i = 0; i < num_images; ++i)
  755. {
  756. // Update intrinsics
  757. cameras[i].focal *= compose_work_aspect;
  758. cameras[i].ppx *= compose_work_aspect;
  759. cameras[i].ppy *= compose_work_aspect;
  760. // Update corner and size
  761. Size sz = full_img_sizes[i];
  762. if (std::abs(compose_scale - 1) > 1e-1)
  763. {
  764. sz.width = cvRound(full_img_sizes[i].width * compose_scale);
  765. sz.height = cvRound(full_img_sizes[i].height * compose_scale);
  766. }
  767. Mat K;
  768. cameras[i].K().convertTo(K, CV_32F);
  769. Rect roi = warper->warpRoi(sz, K, cameras[i].R);
  770. corners[i] = roi.tl();
  771. sizes[i] = roi.size();
  772. }
  773. }
  774. if (abs(compose_scale - 1) > 1e-1)
  775. resize(full_img, img, Size(), compose_scale, compose_scale, INTER_LINEAR_EXACT);
  776. else
  777. img = full_img;
  778. full_img.release();
  779. Size img_size = img.size();
  780. Mat K;
  781. cameras[img_idx].K().convertTo(K, CV_32F);
  782. // Warp the current image
  783. warper->warp(img, K, cameras[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);
  784. // Warp the current image mask
  785. mask.create(img_size, CV_8U);
  786. mask.setTo(Scalar::all(255));
  787. warper->warp(mask, K, cameras[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);
  788. // Compensate exposure
  789. compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped);
  790. img_warped.convertTo(img_warped_s, CV_16S);
  791. img_warped.release();
  792. img.release();
  793. mask.release();
  794. dilate(masks_warped[img_idx], dilated_mask, Mat());
  795. resize(dilated_mask, seam_mask, mask_warped.size(), 0, 0, INTER_LINEAR_EXACT);
  796. mask_warped = seam_mask & mask_warped;
  797. if (!blender && !timelapse)
  798. {
  799. blender = Blender::createDefault(blend_type, try_cuda);
  800. Size dst_sz = resultRoi(corners, sizes).size();
  801. float blend_width = sqrt(static_cast<float>(dst_sz.area())) * blend_strength / 100.f;
  802. if (blend_width < 1.f)
  803. blender = Blender::createDefault(Blender::NO, try_cuda);
  804. else if (blend_type == Blender::MULTI_BAND)
  805. {
  806. MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(blender.get());
  807. mb->setNumBands(static_cast<int>(ceil(log(blend_width)/log(2.)) - 1.));
  808. LOGLN("Multi-band blender, number of bands: " << mb->numBands());
  809. }
  810. else if (blend_type == Blender::FEATHER)
  811. {
  812. FeatherBlender* fb = dynamic_cast<FeatherBlender*>(blender.get());
  813. fb->setSharpness(1.f/blend_width);
  814. LOGLN("Feather blender, sharpness: " << fb->sharpness());
  815. }
  816. blender->prepare(corners, sizes);
  817. }
  818. else if (!timelapser && timelapse)
  819. {
  820. timelapser = Timelapser::createDefault(timelapse_type);
  821. timelapser->initialize(corners, sizes);
  822. }
  823. // Blend the current image
  824. if (timelapse)
  825. {
  826. timelapser->process(img_warped_s, Mat::ones(img_warped_s.size(), CV_8UC1), corners[img_idx]);
  827. String fixedFileName;
  828. size_t pos_s = String(img_names[img_idx]).find_last_of("/\\");
  829. if (pos_s == String::npos)
  830. {
  831. fixedFileName = "fixed_" + img_names[img_idx];
  832. }
  833. else
  834. {
  835. fixedFileName = "fixed_" + String(img_names[img_idx]).substr(pos_s + 1, String(img_names[img_idx]).length() - pos_s);
  836. }
  837. imwrite(fixedFileName, timelapser->getDst());
  838. }
  839. else
  840. {
  841. blender->feed(img_warped_s, mask_warped, corners[img_idx]);
  842. }
  843. }
  844. if (!timelapse)
  845. {
  846. Mat result, result_mask;
  847. blender->blend(result, result_mask);
  848. LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
  849. imwrite(result_name, result);
  850. }
  851. LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec");
  852. return 0;
  853. }