Home | History | Annotate | Download | only in cpp
      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 
     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 
    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 
    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