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