stitching_detailed.cpp 29.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                          License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of the copyright holders may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
40
//
41 42 43
//
//M*/

44
#include <iostream>
45
#include <fstream>
46
#include <string>
A
Andrey Kamaev 已提交
47
#include "opencv2/opencv_modules.hpp"
48
#include <opencv2/core/utility.hpp>
49
#include "opencv2/imgcodecs.hpp"
50
#include "opencv2/highgui.hpp"
51 52
#include "opencv2/stitching/detail/autocalib.hpp"
#include "opencv2/stitching/detail/blenders.hpp"
P
Petr Glotov 已提交
53
#include "opencv2/stitching/detail/timelapsers.hpp"
54 55 56 57 58 59
#include "opencv2/stitching/detail/camera.hpp"
#include "opencv2/stitching/detail/exposure_compensate.hpp"
#include "opencv2/stitching/detail/matchers.hpp"
#include "opencv2/stitching/detail/motion_estimators.hpp"
#include "opencv2/stitching/detail/seam_finders.hpp"
#include "opencv2/stitching/detail/warpers.hpp"
60
#include "opencv2/stitching/warpers.hpp"
61

62 63 64 65
#define ENABLE_LOG 1
#define LOG(msg) std::cout << msg
#define LOGLN(msg) std::cout << msg << std::endl

66 67 68 69
using namespace std;
using namespace cv;
using namespace cv::detail;

70
static void printUsage()
71 72 73 74 75 76 77 78
{
    cout <<
        "Rotation model images stitcher.\n\n"
        "stitching_detailed img1 img2 [...imgN] [flags]\n\n"
        "Flags:\n"
        "  --preview\n"
        "      Run stitching in the preview mode. Works faster than usual mode,\n"
        "      but output image will have lower resolution.\n"
I
Ilya Lavrenov 已提交
79 80 81
        "  --try_cuda (yes|no)\n"
        "      Try to use CUDA. The default value is 'no'. All default values\n"
        "      are for CPU mode.\n"
82 83 84
        "\nMotion Estimation Flags:\n"
        "  --work_megapix <float>\n"
        "      Resolution for image registration step. The default is 0.6 Mpx.\n"
85 86
        "  --features (surf|orb)\n"
        "      Type of features used for images matching. The default is surf.\n"
87
        "  --match_conf <float>\n"
88
        "      Confidence for feature matching step. The default is 0.65 for surf and 0.3 for orb.\n"
89 90 91
        "  --conf_thresh <float>\n"
        "      Threshold for two images are from the same panorama confidence.\n"
        "      The default is 1.0.\n"
92 93
        "  --ba (reproj|ray)\n"
        "      Bundle adjustment cost function. The default is ray.\n"
94 95 96 97 98 99 100
        "  --ba_refine_mask (mask)\n"
        "      Set refinement mask for bundle adjustment. It looks like 'x_xxx',\n"
        "      where 'x' means refine respective parameter and '_' means don't\n"
        "      refine one, and has the following format:\n"
        "      <fx><skew><ppx><aspect><ppy>. The default mask is 'xxxxx'. If bundle\n"
        "      adjustment doesn't support estimation of selected parameter then\n"
        "      the respective flag is ignored.\n"
101 102
        "  --wave_correct (no|horiz|vert)\n"
        "      Perform wave effect correction. The default is 'horiz'.\n"
103 104 105 106 107
        "  --save_graph <file_name>\n"
        "      Save matches graph represented in DOT language to <file_name> file.\n"
        "      Labels description: Nm is number of matches, Ni is number of inliers,\n"
        "      C is confidence.\n"
        "\nCompositing Flags:\n"
I
Ivan Korolev 已提交
108
        "  --warp (plane|cylindrical|spherical|fisheye|stereographic|compressedPlaneA2B1|compressedPlaneA1.5B1|compressedPlanePortraitA2B1|compressedPlanePortraitA1.5B1|paniniA2B1|paniniA1.5B1|paniniPortraitA2B1|paniniPortraitA1.5B1|mercator|transverseMercator)\n"
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
        "      Warp surface type. The default is 'spherical'.\n"
        "  --seam_megapix <float>\n"
        "      Resolution for seam estimation step. The default is 0.1 Mpx.\n"
        "  --seam (no|voronoi|gc_color|gc_colorgrad)\n"
        "      Seam estimation method. The default is 'gc_color'.\n"
        "  --compose_megapix <float>\n"
        "      Resolution for compositing step. Use -1 for original resolution.\n"
        "      The default is -1.\n"
        "  --expos_comp (no|gain|gain_blocks)\n"
        "      Exposure compensation method. The default is 'gain_blocks'.\n"
        "  --blend (no|feather|multiband)\n"
        "      Blending method. The default is 'multiband'.\n"
        "  --blend_strength <float>\n"
        "      Blending strength from [0,100] range. The default is 5.\n"
        "  --output <result_img>\n"
P
Petr Glotov 已提交
124
        "      The default is 'result.jpg'.\n"
125 126 127 128
        "  --timelapse (as_is|crop) \n"
        "      Output warped images separately as frames of a time lapse movie, with 'fixed_' prepended to input file names.\n"
        "  --rangewidth <int>\n"
        "      uses range_width to limit number of images to match with.\n";
129 130 131 132
}


// Default command line args
A
Andrey Kamaev 已提交
133
vector<String> img_names;
134
bool preview = false;
I
Ilya Lavrenov 已提交
135
bool try_cuda = false;
136 137 138 139
double work_megapix = 0.6;
double seam_megapix = 0.1;
double compose_megapix = -1;
float conf_thresh = 1.f;
140
string features_type = "surf";
141
string ba_cost_func = "ray";
142
string ba_refine_mask = "xxxxx";
143 144
bool do_wave_correct = true;
WaveCorrectKind wave_correct = detail::WAVE_CORRECT_HORIZ;
145 146
bool save_graph = false;
std::string save_graph_to;
147
string warp_type = "spherical";
148
int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
149
float match_conf = 0.3f;
150
string seam_find_type = "gc_color";
151
int blend_type = Blender::MULTI_BAND;
P
Petr Glotov 已提交
152
int timelapse_type = Timelapser::AS_IS;
153 154
float blend_strength = 5;
string result_name = "result.jpg";
P
Petr Glotov 已提交
155
bool timelapse = false;
156
int range_width = -1;
P
Petr Glotov 已提交
157

158

159
static int parseCmdArgs(int argc, char** argv)
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
{
    if (argc == 1)
    {
        printUsage();
        return -1;
    }
    for (int i = 1; i < argc; ++i)
    {
        if (string(argv[i]) == "--help" || string(argv[i]) == "/?")
        {
            printUsage();
            return -1;
        }
        else if (string(argv[i]) == "--preview")
        {
            preview = true;
        }
I
Ilya Lavrenov 已提交
177
        else if (string(argv[i]) == "--try_cuda")
178 179
        {
            if (string(argv[i + 1]) == "no")
I
Ilya Lavrenov 已提交
180
                try_cuda = false;
181
            else if (string(argv[i + 1]) == "yes")
I
Ilya Lavrenov 已提交
182
                try_cuda = true;
183 184
            else
            {
I
Ilya Lavrenov 已提交
185 186 187 188 189
                cout << "Bad --try_cuda flag value\n";
                return -1;
            }
            i++;
        }
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        else if (string(argv[i]) == "--work_megapix")
        {
            work_megapix = atof(argv[i + 1]);
            i++;
        }
        else if (string(argv[i]) == "--seam_megapix")
        {
            seam_megapix = atof(argv[i + 1]);
            i++;
        }
        else if (string(argv[i]) == "--compose_megapix")
        {
            compose_megapix = atof(argv[i + 1]);
            i++;
        }
        else if (string(argv[i]) == "--result")
        {
            result_name = argv[i + 1];
            i++;
        }
210 211
        else if (string(argv[i]) == "--features")
        {
212 213
            features_type = argv[i + 1];
            if (features_type == "orb")
214 215 216
                match_conf = 0.3f;
            i++;
        }
217 218 219 220 221 222 223 224 225 226
        else if (string(argv[i]) == "--match_conf")
        {
            match_conf = static_cast<float>(atof(argv[i + 1]));
            i++;
        }
        else if (string(argv[i]) == "--conf_thresh")
        {
            conf_thresh = static_cast<float>(atof(argv[i + 1]));
            i++;
        }
227 228 229 230 231
        else if (string(argv[i]) == "--ba")
        {
            ba_cost_func = argv[i + 1];
            i++;
        }
232 233 234 235 236 237 238 239 240 241
        else if (string(argv[i]) == "--ba_refine_mask")
        {
            ba_refine_mask = argv[i + 1];
            if (ba_refine_mask.size() != 5)
            {
                cout << "Incorrect refinement mask length.\n";
                return -1;
            }
            i++;
        }
242 243 244
        else if (string(argv[i]) == "--wave_correct")
        {
            if (string(argv[i + 1]) == "no")
245 246 247 248 249 250 251 252 253 254 255
                do_wave_correct = false;
            else if (string(argv[i + 1]) == "horiz")
            {
                do_wave_correct = true;
                wave_correct = detail::WAVE_CORRECT_HORIZ;
            }
            else if (string(argv[i + 1]) == "vert")
            {
                do_wave_correct = true;
                wave_correct = detail::WAVE_CORRECT_VERT;
            }
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
            else
            {
                cout << "Bad --wave_correct flag value\n";
                return -1;
            }
            i++;
        }
        else if (string(argv[i]) == "--save_graph")
        {
            save_graph = true;
            save_graph_to = argv[i + 1];
            i++;
        }
        else if (string(argv[i]) == "--warp")
        {
271
            warp_type = string(argv[i + 1]);
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
            i++;
        }
        else if (string(argv[i]) == "--expos_comp")
        {
            if (string(argv[i + 1]) == "no")
                expos_comp_type = ExposureCompensator::NO;
            else if (string(argv[i + 1]) == "gain")
                expos_comp_type = ExposureCompensator::GAIN;
            else if (string(argv[i + 1]) == "gain_blocks")
                expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
            else
            {
                cout << "Bad exposure compensation method\n";
                return -1;
            }
            i++;
        }
        else if (string(argv[i]) == "--seam")
        {
291 292 293
            if (string(argv[i + 1]) == "no" ||
                string(argv[i + 1]) == "voronoi" ||
                string(argv[i + 1]) == "gc_color" ||
294 295 296
                string(argv[i + 1]) == "gc_colorgrad" ||
                string(argv[i + 1]) == "dp_color" ||
                string(argv[i + 1]) == "dp_colorgrad")
297
                seam_find_type = argv[i + 1];
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
            else
            {
                cout << "Bad seam finding method\n";
                return -1;
            }
            i++;
        }
        else if (string(argv[i]) == "--blend")
        {
            if (string(argv[i + 1]) == "no")
                blend_type = Blender::NO;
            else if (string(argv[i + 1]) == "feather")
                blend_type = Blender::FEATHER;
            else if (string(argv[i + 1]) == "multiband")
                blend_type = Blender::MULTI_BAND;
            else
            {
                cout << "Bad blending method\n";
                return -1;
            }
            i++;
        }
P
Petr Glotov 已提交
320 321 322 323 324 325 326 327 328 329 330 331 332 333
        else if (string(argv[i]) == "--timelapse")
        {
            timelapse = true;

            if (string(argv[i + 1]) == "as_is")
                timelapse_type = Timelapser::AS_IS;
            else if (string(argv[i + 1]) == "crop")
                timelapse_type = Timelapser::CROP;
            else
            {
                cout << "Bad timelapse method\n";
                return -1;
            }
            i++;
334 335 336 337
        }
        else if (string(argv[i]) == "--rangewidth")
        {
            range_width = atoi(argv[i + 1]);
P
Petr Glotov 已提交
338 339
            i++;
        }
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
        else if (string(argv[i]) == "--blend_strength")
        {
            blend_strength = static_cast<float>(atof(argv[i + 1]));
            i++;
        }
        else if (string(argv[i]) == "--output")
        {
            result_name = argv[i + 1];
            i++;
        }
        else
            img_names.push_back(argv[i]);
    }
    if (preview)
    {
        compose_megapix = 0.6;
    }
    return 0;
}


int main(int argc, char* argv[])
{
A
Andrey Kamaev 已提交
363
#if ENABLE_LOG
364
    int64 app_start_time = getTickCount();
A
Andrey Kamaev 已提交
365 366
#endif

367
#if 0
368
    cv::setBreakOnError(true);
369
#endif
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386

    int retval = parseCmdArgs(argc, argv);
    if (retval)
        return retval;

    // Check if have enough images
    int num_images = static_cast<int>(img_names.size());
    if (num_images < 2)
    {
        LOGLN("Need more images");
        return -1;
    }

    double work_scale = 1, seam_scale = 1, compose_scale = 1;
    bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false;

    LOGLN("Finding features...");
A
Andrey Kamaev 已提交
387
#if ENABLE_LOG
388
    int64 t = getTickCount();
A
Andrey Kamaev 已提交
389
#endif
390

391
    Ptr<FeaturesFinder> finder;
392
    if (features_type == "surf")
393
    {
394
#ifdef HAVE_OPENCV_XFEATURES2D
I
Ilya Lavrenov 已提交
395
        if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
396
            finder = makePtr<SurfFeaturesFinderGpu>();
397
        else
398
#endif
399
            finder = makePtr<SurfFeaturesFinder>();
400
    }
401
    else if (features_type == "orb")
402
    {
403
        finder = makePtr<OrbFeaturesFinder>();
404 405 406
    }
    else
    {
407
        cout << "Unknown 2D features type: '" << features_type << "'.\n";
408 409
        return -1;
    }
410

411 412
    Mat full_img, img;
    vector<ImageFeatures> features(num_images);
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
    vector<Mat> images(num_images);
    vector<Size> full_img_sizes(num_images);
    double seam_work_aspect = 1;

    for (int i = 0; i < num_images; ++i)
    {
        full_img = imread(img_names[i]);
        full_img_sizes[i] = full_img.size();

        if (full_img.empty())
        {
            LOGLN("Can't open image " << img_names[i]);
            return -1;
        }
        if (work_megapix < 0)
        {
            img = full_img;
            work_scale = 1;
            is_work_scale_set = true;
        }
        else
        {
            if (!is_work_scale_set)
            {
                work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area()));
                is_work_scale_set = true;
            }
            resize(full_img, img, Size(), work_scale, work_scale);
        }
        if (!is_seam_scale_set)
        {
            seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area()));
            seam_work_aspect = seam_scale / work_scale;
            is_seam_scale_set = true;
        }

449
        (*finder)(img, features[i]);
450 451 452 453 454 455 456
        features[i].img_idx = i;
        LOGLN("Features in image #" << i+1 << ": " << features[i].keypoints.size());

        resize(full_img, img, Size(), seam_scale, seam_scale);
        images[i] = img.clone();
    }

457
    finder->collectGarbage();
458 459 460 461 462 463
    full_img.release();
    img.release();

    LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");

    LOG("Pairwise matching");
A
Andrey Kamaev 已提交
464
#if ENABLE_LOG
465
    t = getTickCount();
A
Andrey Kamaev 已提交
466
#endif
467
    vector<MatchesInfo> pairwise_matches;
468
    if (range_width==-1)
P
Petr Glotov 已提交
469 470 471 472 473 474 475
    {
        BestOf2NearestMatcher matcher(try_cuda, match_conf);
        matcher(features, pairwise_matches);
        matcher.collectGarbage();
    }
    else
    {
476
        BestOf2NearestRangeMatcher matcher(range_width, try_cuda, match_conf);
P
Petr Glotov 已提交
477 478 479 480
        matcher(features, pairwise_matches);
        matcher.collectGarbage();
    }

481 482 483 484 485 486 487 488 489 490 491 492 493
    LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");

    // Check if we should save matches graph
    if (save_graph)
    {
        LOGLN("Saving matches graph...");
        ofstream f(save_graph_to.c_str());
        f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh);
    }

    // Leave only images we are sure are from the same panorama
    vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh);
    vector<Mat> img_subset;
A
Andrey Kamaev 已提交
494
    vector<String> img_names_subset;
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
    vector<Size> full_img_sizes_subset;
    for (size_t i = 0; i < indices.size(); ++i)
    {
        img_names_subset.push_back(img_names[indices[i]]);
        img_subset.push_back(images[indices[i]]);
        full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);
    }

    images = img_subset;
    img_names = img_names_subset;
    full_img_sizes = full_img_sizes_subset;

    // Check if we still have enough images
    num_images = static_cast<int>(img_names.size());
    if (num_images < 2)
    {
        LOGLN("Need more images");
        return -1;
    }

    HomographyBasedEstimator estimator;
    vector<CameraParams> cameras;
517 518 519 520 521
    if (!estimator(features, pairwise_matches, cameras))
    {
        cout << "Homography estimation failed.\n";
        return -1;
    }
522 523 524 525 526 527

    for (size_t i = 0; i < cameras.size(); ++i)
    {
        Mat R;
        cameras[i].R.convertTo(R, CV_32F);
        cameras[i].R = R;
528
        LOGLN("Initial intrinsics #" << indices[i]+1 << ":\n" << cameras[i].K());
529 530
    }

531
    Ptr<detail::BundleAdjusterBase> adjuster;
532 533
    if (ba_cost_func == "reproj") adjuster = makePtr<detail::BundleAdjusterReproj>();
    else if (ba_cost_func == "ray") adjuster = makePtr<detail::BundleAdjusterRay>();
534 535 536 537
    else
    {
        cout << "Unknown bundle adjustment cost function: '" << ba_cost_func << "'.\n";
        return -1;
538 539
    }
    adjuster->setConfThresh(conf_thresh);
540 541 542 543 544 545 546
    Mat_<uchar> refine_mask = Mat::zeros(3, 3, CV_8U);
    if (ba_refine_mask[0] == 'x') refine_mask(0,0) = 1;
    if (ba_refine_mask[1] == 'x') refine_mask(0,1) = 1;
    if (ba_refine_mask[2] == 'x') refine_mask(0,2) = 1;
    if (ba_refine_mask[3] == 'x') refine_mask(1,1) = 1;
    if (ba_refine_mask[4] == 'x') refine_mask(1,2) = 1;
    adjuster->setRefinementMask(refine_mask);
547 548 549 550 551
    if (!(*adjuster)(features, pairwise_matches, cameras))
    {
        cout << "Camera parameters adjusting failed.\n";
        return -1;
    }
552 553

    // Find median focal length
554

555 556 557
    vector<double> focals;
    for (size_t i = 0; i < cameras.size(); ++i)
    {
558
        LOGLN("Camera #" << indices[i]+1 << ":\n" << cameras[i].K());
559 560
        focals.push_back(cameras[i].focal);
    }
561 562 563 564 565 566 567

    sort(focals.begin(), focals.end());
    float warped_image_scale;
    if (focals.size() % 2 == 1)
        warped_image_scale = static_cast<float>(focals[focals.size() / 2]);
    else
        warped_image_scale = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;
568

569
    if (do_wave_correct)
570 571 572
    {
        vector<Mat> rmats;
        for (size_t i = 0; i < cameras.size(); ++i)
573
            rmats.push_back(cameras[i].R.clone());
574
        waveCorrect(rmats, wave_correct);
575 576 577 578 579
        for (size_t i = 0; i < cameras.size(); ++i)
            cameras[i].R = rmats[i];
    }

    LOGLN("Warping images (auxiliary)... ");
A
Andrey Kamaev 已提交
580
#if ENABLE_LOG
581
    t = getTickCount();
A
Andrey Kamaev 已提交
582
#endif
583 584

    vector<Point> corners(num_images);
585 586
    vector<UMat> masks_warped(num_images);
    vector<UMat> images_warped(num_images);
587
    vector<Size> sizes(num_images);
588
    vector<UMat> masks(num_images);
589 590 591 592 593 594 595 596 597

    // Preapre images masks
    for (int i = 0; i < num_images; ++i)
    {
        masks[i].create(images[i].size(), CV_8U);
        masks[i].setTo(Scalar::all(255));
    }

    // Warp images and their masks
598 599

    Ptr<WarperCreator> warper_creator;
600
#ifdef HAVE_OPENCV_CUDAWARPING
601
    if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
602
    {
603 604 605 606 607 608
        if (warp_type == "plane")
            warper_creator = makePtr<cv::PlaneWarperGpu>();
        else if (warp_type == "cylindrical")
            warper_creator = makePtr<cv::CylindricalWarperGpu>();
        else if (warp_type == "spherical")
            warper_creator = makePtr<cv::SphericalWarperGpu>();
609 610 611 612
    }
    else
#endif
    {
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
        if (warp_type == "plane")
            warper_creator = makePtr<cv::PlaneWarper>();
        else if (warp_type == "cylindrical")
            warper_creator = makePtr<cv::CylindricalWarper>();
        else if (warp_type == "spherical")
            warper_creator = makePtr<cv::SphericalWarper>();
        else if (warp_type == "fisheye")
            warper_creator = makePtr<cv::FisheyeWarper>();
        else if (warp_type == "stereographic")
            warper_creator = makePtr<cv::StereographicWarper>();
        else if (warp_type == "compressedPlaneA2B1")
            warper_creator = makePtr<cv::CompressedRectilinearWarper>(2.0f, 1.0f);
        else if (warp_type == "compressedPlaneA1.5B1")
            warper_creator = makePtr<cv::CompressedRectilinearWarper>(1.5f, 1.0f);
        else if (warp_type == "compressedPlanePortraitA2B1")
            warper_creator = makePtr<cv::CompressedRectilinearPortraitWarper>(2.0f, 1.0f);
        else if (warp_type == "compressedPlanePortraitA1.5B1")
            warper_creator = makePtr<cv::CompressedRectilinearPortraitWarper>(1.5f, 1.0f);
        else if (warp_type == "paniniA2B1")
            warper_creator = makePtr<cv::PaniniWarper>(2.0f, 1.0f);
        else if (warp_type == "paniniA1.5B1")
            warper_creator = makePtr<cv::PaniniWarper>(1.5f, 1.0f);
        else if (warp_type == "paniniPortraitA2B1")
            warper_creator = makePtr<cv::PaniniPortraitWarper>(2.0f, 1.0f);
        else if (warp_type == "paniniPortraitA1.5B1")
            warper_creator = makePtr<cv::PaniniPortraitWarper>(1.5f, 1.0f);
        else if (warp_type == "mercator")
            warper_creator = makePtr<cv::MercatorWarper>();
        else if (warp_type == "transverseMercator")
            warper_creator = makePtr<cv::TransverseMercatorWarper>();
643 644
    }

645
    if (!warper_creator)
646 647 648 649
    {
        cout << "Can't create the following warper '" << warp_type << "'\n";
        return 1;
    }
650

651
    Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale * seam_work_aspect));
652

653 654
    for (int i = 0; i < num_images; ++i)
    {
655 656
        Mat_<float> K;
        cameras[i].K().convertTo(K, CV_32F);
657 658 659
        float swa = (float)seam_work_aspect;
        K(0,0) *= swa; K(0,2) *= swa;
        K(1,1) *= swa; K(1,2) *= swa;
660

661
        corners[i] = warper->warp(images[i], K, cameras[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);
662
        sizes[i] = images_warped[i].size();
663

664
        warper->warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
665 666
    }

667
    vector<UMat> images_warped_f(num_images);
668 669 670 671 672 673 674 675
    for (int i = 0; i < num_images; ++i)
        images_warped[i].convertTo(images_warped_f[i], CV_32F);

    LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");

    Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type);
    compensator->feed(corners, images_warped, masks_warped);

676
    Ptr<SeamFinder> seam_finder;
677
    if (seam_find_type == "no")
678
        seam_finder = makePtr<detail::NoSeamFinder>();
679
    else if (seam_find_type == "voronoi")
680
        seam_finder = makePtr<detail::VoronoiSeamFinder>();
681 682
    else if (seam_find_type == "gc_color")
    {
V
Vladislav Vinogradov 已提交
683
#ifdef HAVE_OPENCV_CUDALEGACY
I
Ilya Lavrenov 已提交
684
        if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
685
            seam_finder = makePtr<detail::GraphCutSeamFinderGpu>(GraphCutSeamFinderBase::COST_COLOR);
686 687
        else
#endif
688
            seam_finder = makePtr<detail::GraphCutSeamFinder>(GraphCutSeamFinderBase::COST_COLOR);
689 690 691
    }
    else if (seam_find_type == "gc_colorgrad")
    {
V
Vladislav Vinogradov 已提交
692
#ifdef HAVE_OPENCV_CUDALEGACY
I
Ilya Lavrenov 已提交
693
        if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
694
            seam_finder = makePtr<detail::GraphCutSeamFinderGpu>(GraphCutSeamFinderBase::COST_COLOR_GRAD);
695 696
        else
#endif
697
            seam_finder = makePtr<detail::GraphCutSeamFinder>(GraphCutSeamFinderBase::COST_COLOR_GRAD);
698
    }
699
    else if (seam_find_type == "dp_color")
700
        seam_finder = makePtr<detail::DpSeamFinder>(DpSeamFinder::COLOR);
701
    else if (seam_find_type == "dp_colorgrad")
702 703
        seam_finder = makePtr<detail::DpSeamFinder>(DpSeamFinder::COLOR_GRAD);
    if (!seam_finder)
704 705 706 707 708
    {
        cout << "Can't create the following seam finder '" << seam_find_type << "'\n";
        return 1;
    }

709 710 711 712 713 714 715 716 717
    seam_finder->find(images_warped_f, corners, masks_warped);

    // Release unused memory
    images.clear();
    images_warped.clear();
    images_warped_f.clear();
    masks.clear();

    LOGLN("Compositing...");
A
Andrey Kamaev 已提交
718
#if ENABLE_LOG
719
    t = getTickCount();
A
Andrey Kamaev 已提交
720
#endif
721 722 723 724

    Mat img_warped, img_warped_s;
    Mat dilated_mask, seam_mask, mask, mask_warped;
    Ptr<Blender> blender;
P
Petr Glotov 已提交
725
    Ptr<Timelapser> timelapser;
A
Andrey Kamaev 已提交
726
    //double compose_seam_aspect = 1;
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
    double compose_work_aspect = 1;

    for (int img_idx = 0; img_idx < num_images; ++img_idx)
    {
        LOGLN("Compositing image #" << indices[img_idx]+1);

        // Read image and resize it if necessary
        full_img = imread(img_names[img_idx]);
        if (!is_compose_scale_set)
        {
            if (compose_megapix > 0)
                compose_scale = min(1.0, sqrt(compose_megapix * 1e6 / full_img.size().area()));
            is_compose_scale_set = true;

            // Compute relative scales
A
Andrey Kamaev 已提交
742
            //compose_seam_aspect = compose_scale / seam_scale;
743 744 745 746
            compose_work_aspect = compose_scale / work_scale;

            // Update warped image scale
            warped_image_scale *= static_cast<float>(compose_work_aspect);
747
            warper = warper_creator->create(warped_image_scale);
748 749 750 751

            // Update corners and sizes
            for (int i = 0; i < num_images; ++i)
            {
752
                // Update intrinsics
753
                cameras[i].focal *= compose_work_aspect;
754 755
                cameras[i].ppx *= compose_work_aspect;
                cameras[i].ppy *= compose_work_aspect;
756 757 758

                // Update corner and size
                Size sz = full_img_sizes[i];
759
                if (std::abs(compose_scale - 1) > 1e-1)
760 761 762 763 764
                {
                    sz.width = cvRound(full_img_sizes[i].width * compose_scale);
                    sz.height = cvRound(full_img_sizes[i].height * compose_scale);
                }

765 766 767
                Mat K;
                cameras[i].K().convertTo(K, CV_32F);
                Rect roi = warper->warpRoi(sz, K, cameras[i].R);
768 769 770 771 772 773 774 775 776 777 778
                corners[i] = roi.tl();
                sizes[i] = roi.size();
            }
        }
        if (abs(compose_scale - 1) > 1e-1)
            resize(full_img, img, Size(), compose_scale, compose_scale);
        else
            img = full_img;
        full_img.release();
        Size img_size = img.size();

779 780 781
        Mat K;
        cameras[img_idx].K().convertTo(K, CV_32F);

782
        // Warp the current image
783
        warper->warp(img, K, cameras[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);
784 785 786 787

        // Warp the current image mask
        mask.create(img_size, CV_8U);
        mask.setTo(Scalar::all(255));
788
        warper->warp(mask, K, cameras[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);
789 790 791 792 793 794 795 796 797 798 799 800 801

        // Compensate exposure
        compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped);

        img_warped.convertTo(img_warped_s, CV_16S);
        img_warped.release();
        img.release();
        mask.release();

        dilate(masks_warped[img_idx], dilated_mask, Mat());
        resize(dilated_mask, seam_mask, mask_warped.size());
        mask_warped = seam_mask & mask_warped;

P
Petr Glotov 已提交
802
        if (!blender && !timelapse)
803
        {
I
Ilya Lavrenov 已提交
804
            blender = Blender::createDefault(blend_type, try_cuda);
805 806 807
            Size dst_sz = resultRoi(corners, sizes).size();
            float blend_width = sqrt(static_cast<float>(dst_sz.area())) * blend_strength / 100.f;
            if (blend_width < 1.f)
I
Ilya Lavrenov 已提交
808
                blender = Blender::createDefault(Blender::NO, try_cuda);
809 810
            else if (blend_type == Blender::MULTI_BAND)
            {
811
                MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(blender.get());
812 813 814 815 816
                mb->setNumBands(static_cast<int>(ceil(log(blend_width)/log(2.)) - 1.));
                LOGLN("Multi-band blender, number of bands: " << mb->numBands());
            }
            else if (blend_type == Blender::FEATHER)
            {
817
                FeatherBlender* fb = dynamic_cast<FeatherBlender*>(blender.get());
818 819 820 821 822
                fb->setSharpness(1.f/blend_width);
                LOGLN("Feather blender, sharpness: " << fb->sharpness());
            }
            blender->prepare(corners, sizes);
        }
823
        else if (!timelapser && timelapse)
P
Petr Glotov 已提交
824 825 826 827
        {
            timelapser = Timelapser::createDefault(timelapse_type);
            timelapser->initialize(corners, sizes);
        }
828 829

        // Blend the current image
P
Petr Glotov 已提交
830 831 832
        if (timelapse)
        {
            timelapser->process(img_warped_s, Mat::ones(img_warped_s.size(), CV_8UC1), corners[img_idx]);
833 834 835 836 837 838 839 840 841 842 843
            String fixedFileName;
            size_t pos_s = String(img_names[img_idx]).find_last_of("/\\");
            if (pos_s == String::npos)
            {
                fixedFileName = "fixed_" + img_names[img_idx];
            }
            else
            {
                fixedFileName = "fixed_" + String(img_names[img_idx]).substr(pos_s + 1, String(img_names[img_idx]).length() - pos_s);
            }
            imwrite(fixedFileName, timelapser->getDst());
P
Petr Glotov 已提交
844 845 846 847 848
        }
        else
        {
            blender->feed(img_warped_s, mask_warped, corners[img_idx]);
        }
849 850
    }

P
Petr Glotov 已提交
851 852 853 854
    if (!timelapse)
    {
        Mat result, result_mask;
        blender->blend(result, result_mask);
855

P
Petr Glotov 已提交
856
        LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
857

P
Petr Glotov 已提交
858 859
        imwrite(result_name, result);
    }
860 861 862 863

    LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec");
    return 0;
}