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