1 /*M///////////////////////////////////////////////////////////////////////////////////////
3 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
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.
11 // For Open Source Computer Vision Library
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.
17 // Redistribution and use in source and binary forms, with or without modification,
18 // are permitted provided that the following conditions are met:
20 // * Redistribution's of source code must retain the above copyright notice,
21 // this list of conditions and the following disclaimer.
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.
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.
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.
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"
65 using namespace cv::detail;
67 static void printUsage()
70 "Rotation model images stitcher.\n\n"
71 "stitching_detailed img1 img2 [...imgN] [flags]\n\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";
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;
154 static int parseCmdArgs(int argc, char** argv)
161 for (int i = 1; i < argc; ++i)
163 if (string(argv[i]) == "--help" || string(argv[i]) == "/?")
168 else if (string(argv[i]) == "--preview")
172 else if (string(argv[i]) == "--try_cuda")
174 if (string(argv[i + 1]) == "no")
176 else if (string(argv[i + 1]) == "yes")
180 cout << "Bad --try_cuda flag value\n";
185 else if (string(argv[i]) == "--work_megapix")
187 work_megapix = atof(argv[i + 1]);
190 else if (string(argv[i]) == "--seam_megapix")
192 seam_megapix = atof(argv[i + 1]);
195 else if (string(argv[i]) == "--compose_megapix")
197 compose_megapix = atof(argv[i + 1]);
200 else if (string(argv[i]) == "--result")
202 result_name = argv[i + 1];
205 else if (string(argv[i]) == "--features")
207 features_type = argv[i + 1];
208 if (features_type == "orb")
212 else if (string(argv[i]) == "--match_conf")
214 match_conf = static_cast<float>(atof(argv[i + 1]));
217 else if (string(argv[i]) == "--conf_thresh")
219 conf_thresh = static_cast<float>(atof(argv[i + 1]));
222 else if (string(argv[i]) == "--ba")
224 ba_cost_func = argv[i + 1];
227 else if (string(argv[i]) == "--ba_refine_mask")
229 ba_refine_mask = argv[i + 1];
230 if (ba_refine_mask.size() != 5)
232 cout << "Incorrect refinement mask length.\n";
237 else if (string(argv[i]) == "--wave_correct")
239 if (string(argv[i + 1]) == "no")
240 do_wave_correct = false;
241 else if (string(argv[i + 1]) == "horiz")
243 do_wave_correct = true;
244 wave_correct = detail::WAVE_CORRECT_HORIZ;
246 else if (string(argv[i + 1]) == "vert")
248 do_wave_correct = true;
249 wave_correct = detail::WAVE_CORRECT_VERT;
253 cout << "Bad --wave_correct flag value\n";
258 else if (string(argv[i]) == "--save_graph")
261 save_graph_to = argv[i + 1];
264 else if (string(argv[i]) == "--warp")
266 warp_type = string(argv[i + 1]);
269 else if (string(argv[i]) == "--expos_comp")
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;
279 cout << "Bad exposure compensation method\n";
284 else if (string(argv[i]) == "--seam")
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];
295 cout << "Bad seam finding method\n";
300 else if (string(argv[i]) == "--blend")
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;
310 cout << "Bad blending method\n";
315 else if (string(argv[i]) == "--timelapse")
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;
325 cout << "Bad timelapse method\n";
330 timelapse_range = atoi(argv[i + 1]);
333 else if (string(argv[i]) == "--blend_strength")
335 blend_strength = static_cast<float>(atof(argv[i + 1]));
338 else if (string(argv[i]) == "--output")
340 result_name = argv[i + 1];
344 img_names.push_back(argv[i]);
348 compose_megapix = 0.6;
354 int main(int argc, char* argv[])
357 int64 app_start_time = getTickCount();
361 cv::setBreakOnError(true);
364 int retval = parseCmdArgs(argc, argv);
368 // Check if have enough images
369 int num_images = static_cast<int>(img_names.size());
372 LOGLN("Need more images");
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;
379 LOGLN("Finding features...");
381 int64 t = getTickCount();
384 Ptr<FeaturesFinder> finder;
385 if (features_type == "surf")
387 #ifdef HAVE_OPENCV_NONFREE
388 if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
389 finder = makePtr<SurfFeaturesFinderGpu>();
392 finder = makePtr<SurfFeaturesFinder>();
394 else if (features_type == "orb")
396 finder = makePtr<OrbFeaturesFinder>();
400 cout << "Unknown 2D features type: '" << features_type << "'.\n";
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;
410 for (int i = 0; i < num_images; ++i)
412 full_img = imread(img_names[i]);
413 full_img_sizes[i] = full_img.size();
415 if (full_img.empty())
417 LOGLN("Can't open image " << img_names[i]);
420 if (work_megapix < 0)
424 is_work_scale_set = true;
428 if (!is_work_scale_set)
430 work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area()));
431 is_work_scale_set = true;
433 resize(full_img, img, Size(), work_scale, work_scale);
435 if (!is_seam_scale_set)
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;
442 (*finder)(img, features[i]);
443 features[i].img_idx = i;
444 LOGLN("Features in image #" << i+1 << ": " << features[i].keypoints.size());
446 resize(full_img, img, Size(), seam_scale, seam_scale);
447 images[i] = img.clone();
450 finder->collectGarbage();
454 LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
456 LOG("Pairwise matching");
460 vector<MatchesInfo> pairwise_matches;
463 BestOf2NearestMatcher matcher(try_cuda, match_conf);
464 matcher(features, pairwise_matches);
465 matcher.collectGarbage();
469 BestOf2NearestRangeMatcher matcher(timelapse_range, try_cuda, match_conf);
470 matcher(features, pairwise_matches);
471 matcher.collectGarbage();
474 LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
476 // Check if we should save matches graph
479 LOGLN("Saving matches graph...");
480 ofstream f(save_graph_to.c_str());
481 f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh);
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)
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]]);
497 img_names = img_names_subset;
498 full_img_sizes = full_img_sizes_subset;
500 // Check if we still have enough images
501 num_images = static_cast<int>(img_names.size());
504 LOGLN("Need more images");
508 HomographyBasedEstimator estimator;
509 vector<CameraParams> cameras;
510 if (!estimator(features, pairwise_matches, cameras))
512 cout << "Homography estimation failed.\n";
516 for (size_t i = 0; i < cameras.size(); ++i)
519 cameras[i].R.convertTo(R, CV_32F);
521 LOGLN("Initial intrinsics #" << indices[i]+1 << ":\n" << cameras[i].K());
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>();
529 cout << "Unknown bundle adjustment cost function: '" << ba_cost_func << "'.\n";
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))
542 cout << "Camera parameters adjusting failed.\n";
546 // Find median focal length
548 vector<double> focals;
549 for (size_t i = 0; i < cameras.size(); ++i)
551 LOGLN("Camera #" << indices[i]+1 << ":\n" << cameras[i].K());
552 focals.push_back(cameras[i].focal);
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]);
560 warped_image_scale = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;
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];
572 LOGLN("Warping images (auxiliary)... ");
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);
583 // Preapre images masks
584 for (int i = 0; i < num_images; ++i)
586 masks[i].create(images[i].size(), CV_8U);
587 masks[i].setTo(Scalar::all(255));
590 // Warp images and their masks
592 Ptr<WarperCreator> warper_creator;
593 #ifdef HAVE_OPENCV_CUDAWARPING
594 if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
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>();
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>();
640 cout << "Can't create the following warper '" << warp_type << "'\n";
644 Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale * seam_work_aspect));
646 for (int i = 0; i < num_images; ++i)
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;
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();
657 warper->warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
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);
664 LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
666 Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type);
667 compensator->feed(corners, images_warped, masks_warped);
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")
676 #ifdef HAVE_OPENCV_CUDA
677 if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
678 seam_finder = makePtr<detail::GraphCutSeamFinderGpu>(GraphCutSeamFinderBase::COST_COLOR);
681 seam_finder = makePtr<detail::GraphCutSeamFinder>(GraphCutSeamFinderBase::COST_COLOR);
683 else if (seam_find_type == "gc_colorgrad")
685 #ifdef HAVE_OPENCV_CUDA
686 if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
687 seam_finder = makePtr<detail::GraphCutSeamFinderGpu>(GraphCutSeamFinderBase::COST_COLOR_GRAD);
690 seam_finder = makePtr<detail::GraphCutSeamFinder>(GraphCutSeamFinderBase::COST_COLOR_GRAD);
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);
698 cout << "Can't create the following seam finder '" << seam_find_type << "'\n";
702 seam_finder->find(images_warped_f, corners, masks_warped);
704 // Release unused memory
706 images_warped.clear();
707 images_warped_f.clear();
710 LOGLN("Compositing...");
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;
722 for (int img_idx = 0; img_idx < num_images; ++img_idx)
724 LOGLN("Compositing image #" << indices[img_idx]+1);
726 // Read image and resize it if necessary
727 full_img = imread(img_names[img_idx]);
728 if (!is_compose_scale_set)
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;
734 // Compute relative scales
735 //compose_seam_aspect = compose_scale / seam_scale;
736 compose_work_aspect = compose_scale / work_scale;
738 // Update warped image scale
739 warped_image_scale *= static_cast<float>(compose_work_aspect);
740 warper = warper_creator->create(warped_image_scale);
742 // Update corners and sizes
743 for (int i = 0; i < num_images; ++i)
746 cameras[i].focal *= compose_work_aspect;
747 cameras[i].ppx *= compose_work_aspect;
748 cameras[i].ppy *= compose_work_aspect;
750 // Update corner and size
751 Size sz = full_img_sizes[i];
752 if (std::abs(compose_scale - 1) > 1e-1)
754 sz.width = cvRound(full_img_sizes[i].width * compose_scale);
755 sz.height = cvRound(full_img_sizes[i].height * compose_scale);
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();
765 if (abs(compose_scale - 1) > 1e-1)
766 resize(full_img, img, Size(), compose_scale, compose_scale);
770 Size img_size = img.size();
773 cameras[img_idx].K().convertTo(K, CV_32F);
775 // Warp the current image
776 warper->warp(img, K, cameras[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);
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);
783 // Compensate exposure
784 compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped);
786 img_warped.convertTo(img_warped_s, CV_16S);
787 img_warped.release();
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;
795 if (!blender && !timelapse)
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)
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());
808 else if (blend_type == Blender::FEATHER)
810 FeatherBlender* fb = dynamic_cast<FeatherBlender*>(blender.get());
811 fb->setSharpness(1.f/blend_width);
812 LOGLN("Feather blender, sharpness: " << fb->sharpness());
814 blender->prepare(corners, sizes);
816 else if (!timelapser)
818 CV_Assert(timelapse);
819 timelapser = Timelapser::createDefault(timelapse_type);
820 timelapser->initialize(corners, sizes);
823 // Blend the current image
826 timelapser->process(img_warped_s, Mat::ones(img_warped_s.size(), CV_8UC1), corners[img_idx]);
828 imwrite("fixed_" + img_names[img_idx], timelapser->getDst());
832 blender->feed(img_warped_s, mask_warped, corners[img_idx]);
838 Mat result, result_mask;
839 blender->blend(result, result_mask);
841 LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
843 imwrite(result_name, result);
846 LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec");