root/trunk/opencv/samples/cpp/stitching_detailed.cpp @ 6856

Revision 6812, 24.3 KB (checked in by alexeys, 3 years ago)

Refactored warpers in the stitching module, added buildMaps function into the RotationWarper? interface

Line 
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 <fstream>
45#include <string>
46#include "opencv2/highgui/highgui.hpp"
47#include "opencv2/stitching/detail/autocalib.hpp"
48#include "opencv2/stitching/detail/blenders.hpp"
49#include "opencv2/stitching/detail/camera.hpp"
50#include "opencv2/stitching/detail/exposure_compensate.hpp"
51#include "opencv2/stitching/detail/matchers.hpp"
52#include "opencv2/stitching/detail/motion_estimators.hpp"
53#include "opencv2/stitching/detail/seam_finders.hpp"
54#include "opencv2/stitching/detail/util.hpp"
55#include "opencv2/stitching/detail/warpers.hpp"
56#include "opencv2/stitching/warpers.hpp"
57
58using namespace std;
59using namespace cv;
60using namespace cv::detail;
61
62void printUsage()
63{
64    cout <<
65        "Rotation model images stitcher.\n\n"
66        "stitching_detailed img1 img2 [...imgN] [flags]\n\n"
67        "Flags:\n"
68        "  --preview\n"
69        "      Run stitching in the preview mode. Works faster than usual mode,\n"
70        "      but output image will have lower resolution.\n"
71        "  --try_gpu (yes|no)\n"
72        "      Try to use GPU. The default value is 'no'. All default values\n"
73        "      are for CPU mode.\n"
74        "\nMotion Estimation Flags:\n"
75        "  --work_megapix <float>\n"
76        "      Resolution for image registration step. The default is 0.6 Mpx.\n"
77        "  --match_conf <float>\n"
78        "      Confidence for feature matching step. The default is 0.65.\n"
79        "  --conf_thresh <float>\n"
80        "      Threshold for two images are from the same panorama confidence.\n"
81        "      The default is 1.0.\n"
82        "  --ba (reproj|ray)\n"
83        "      Bundle adjustment cost function. The default is ray.\n"
84        "  --ba_refine_mask (mask)\n"
85        "      Set refinement mask for bundle adjustment. It looks like 'x_xxx',\n"
86        "      where 'x' means refine respective parameter and '_' means don't\n"
87        "      refine one, and has the following format:\n"
88        "      <fx><skew><ppx><aspect><ppy>. The default mask is 'xxxxx'. If bundle\n"
89        "      adjustment doesn't support estimation of selected parameter then\n"
90        "      the respective flag is ignored.\n"
91        "  --wave_correct (no|horiz|vert)\n"
92        "      Perform wave effect correction. The default is 'horiz'.\n"
93        "  --save_graph <file_name>\n"
94        "      Save matches graph represented in DOT language to <file_name> file.\n"
95        "      Labels description: Nm is number of matches, Ni is number of inliers,\n"
96        "      C is confidence.\n"
97        "\nCompositing Flags:\n"
98        "  --warp (plane|cylindrical|spherical)\n"
99        "      Warp surface type. The default is 'spherical'.\n"
100        "  --seam_megapix <float>\n"
101        "      Resolution for seam estimation step. The default is 0.1 Mpx.\n"
102        "  --seam (no|voronoi|gc_color|gc_colorgrad)\n"
103        "      Seam estimation method. The default is 'gc_color'.\n"
104        "  --compose_megapix <float>\n"
105        "      Resolution for compositing step. Use -1 for original resolution.\n"
106        "      The default is -1.\n"
107        "  --expos_comp (no|gain|gain_blocks)\n"
108        "      Exposure compensation method. The default is 'gain_blocks'.\n"
109        "  --blend (no|feather|multiband)\n"
110        "      Blending method. The default is 'multiband'.\n"
111        "  --blend_strength <float>\n"
112        "      Blending strength from [0,100] range. The default is 5.\n"
113        "  --output <result_img>\n"
114        "      The default is 'result.jpg'.\n";
115}
116
117
118// Default command line args
119vector<string> img_names;
120bool preview = false;
121bool try_gpu = false;
122double work_megapix = 0.6;
123double seam_megapix = 0.1;
124double compose_megapix = -1;
125float conf_thresh = 1.f;
126string ba_cost_func = "ray";
127string ba_refine_mask = "xxxxx";
128bool do_wave_correct = true;
129WaveCorrectKind wave_correct = detail::WAVE_CORRECT_HORIZ;
130bool save_graph = false;
131std::string save_graph_to;
132string warp_type = "spherical";
133int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
134float match_conf = 0.65f;
135string seam_find_type = "gc_color";
136int blend_type = Blender::MULTI_BAND;
137float blend_strength = 5;
138string result_name = "result.jpg";
139
140int parseCmdArgs(int argc, char** argv)
141{
142    if (argc == 1)
143    {
144        printUsage();
145        return -1;
146    }
147    for (int i = 1; i < argc; ++i)
148    {
149        if (string(argv[i]) == "--help" || string(argv[i]) == "/?")
150        {
151            printUsage();
152            return -1;
153        }
154        else if (string(argv[i]) == "--preview")
155        {
156            preview = true;
157        }
158        else if (string(argv[i]) == "--try_gpu")
159        {
160            if (string(argv[i + 1]) == "no")
161                try_gpu = false;
162            else if (string(argv[i + 1]) == "yes")
163                try_gpu = true;
164            else
165            {
166                cout << "Bad --try_gpu flag value\n";
167                return -1;
168            }
169            i++;
170        }
171        else if (string(argv[i]) == "--work_megapix")
172        {
173            work_megapix = atof(argv[i + 1]);
174            i++;
175        }
176        else if (string(argv[i]) == "--seam_megapix")
177        {
178            seam_megapix = atof(argv[i + 1]);
179            i++;
180        }
181        else if (string(argv[i]) == "--compose_megapix")
182        {
183            compose_megapix = atof(argv[i + 1]);
184            i++;
185        }
186        else if (string(argv[i]) == "--result")
187        {
188            result_name = argv[i + 1];
189            i++;
190        }
191        else if (string(argv[i]) == "--match_conf")
192        {
193            match_conf = static_cast<float>(atof(argv[i + 1]));
194            i++;
195        }
196        else if (string(argv[i]) == "--conf_thresh")
197        {
198            conf_thresh = static_cast<float>(atof(argv[i + 1]));
199            i++;
200        }
201        else if (string(argv[i]) == "--ba")
202        {
203            ba_cost_func = argv[i + 1];
204            i++;
205        }
206        else if (string(argv[i]) == "--ba_refine_mask")
207        {
208            ba_refine_mask = argv[i + 1];
209            if (ba_refine_mask.size() != 5)
210            {
211                cout << "Incorrect refinement mask length.\n";
212                return -1;
213            }
214            i++;
215        }
216        else if (string(argv[i]) == "--wave_correct")
217        {
218            if (string(argv[i + 1]) == "no")
219                do_wave_correct = false;
220            else if (string(argv[i + 1]) == "horiz")
221            {
222                do_wave_correct = true;
223                wave_correct = detail::WAVE_CORRECT_HORIZ;
224            }
225            else if (string(argv[i + 1]) == "vert")
226            {
227                do_wave_correct = true;
228                wave_correct = detail::WAVE_CORRECT_VERT;
229            }
230            else
231            {
232                cout << "Bad --wave_correct flag value\n";
233                return -1;
234            }
235            i++;
236        }
237        else if (string(argv[i]) == "--save_graph")
238        {
239            save_graph = true;
240            save_graph_to = argv[i + 1];
241            i++;
242        }
243        else if (string(argv[i]) == "--warp")
244        {
245            warp_type = string(argv[i + 1]);
246            i++;
247        }
248        else if (string(argv[i]) == "--expos_comp")
249        {
250            if (string(argv[i + 1]) == "no")
251                expos_comp_type = ExposureCompensator::NO;
252            else if (string(argv[i + 1]) == "gain")
253                expos_comp_type = ExposureCompensator::GAIN;
254            else if (string(argv[i + 1]) == "gain_blocks")
255                expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
256            else
257            {
258                cout << "Bad exposure compensation method\n";
259                return -1;
260            }
261            i++;
262        }
263        else if (string(argv[i]) == "--seam")
264        {
265            if (string(argv[i + 1]) == "no" ||
266                string(argv[i + 1]) == "voronoi" ||
267                string(argv[i + 1]) == "gc_color" ||
268                string(argv[i + 1]) == "gc_colorgrad")
269                seam_find_type = argv[i + 1];
270            else
271            {
272                cout << "Bad seam finding method\n";
273                return -1;
274            }
275            i++;
276        }
277        else if (string(argv[i]) == "--blend")
278        {
279            if (string(argv[i + 1]) == "no")
280                blend_type = Blender::NO;
281            else if (string(argv[i + 1]) == "feather")
282                blend_type = Blender::FEATHER;
283            else if (string(argv[i + 1]) == "multiband")
284                blend_type = Blender::MULTI_BAND;
285            else
286            {
287                cout << "Bad blending method\n";
288                return -1;
289            }
290            i++;
291        }
292        else if (string(argv[i]) == "--blend_strength")
293        {
294            blend_strength = static_cast<float>(atof(argv[i + 1]));
295            i++;
296        }
297        else if (string(argv[i]) == "--output")
298        {
299            result_name = argv[i + 1];
300            i++;
301        }
302        else
303            img_names.push_back(argv[i]);
304    }
305    if (preview)
306    {
307        compose_megapix = 0.6;
308    }
309    return 0;
310}
311
312
313int main(int argc, char* argv[])
314{
315    int64 app_start_time = getTickCount();
316    cv::setBreakOnError(true);
317
318    int retval = parseCmdArgs(argc, argv);
319    if (retval)
320        return retval;
321
322    // Check if have enough images
323    int num_images = static_cast<int>(img_names.size());
324    if (num_images < 2)
325    {
326        LOGLN("Need more images");
327        return -1;
328    }
329
330    double work_scale = 1, seam_scale = 1, compose_scale = 1;
331    bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false;
332
333    LOGLN("Finding features...");
334    int64 t = getTickCount();
335
336    Ptr<FeaturesFinder> finder;
337#ifndef ANDROID
338    if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0)
339        finder = new SurfFeaturesFinderGpu();
340    else
341#endif
342        finder = new SurfFeaturesFinder();
343
344    Mat full_img, img;
345    vector<ImageFeatures> features(num_images);
346    vector<Mat> images(num_images);
347    vector<Size> full_img_sizes(num_images);
348    double seam_work_aspect = 1;
349
350    for (int i = 0; i < num_images; ++i)
351    {
352        full_img = imread(img_names[i]);
353        full_img_sizes[i] = full_img.size();
354
355        if (full_img.empty())
356        {
357            LOGLN("Can't open image " << img_names[i]);
358            return -1;
359        }
360        if (work_megapix < 0)
361        {
362            img = full_img;
363            work_scale = 1;
364            is_work_scale_set = true;
365        }
366        else
367        {
368            if (!is_work_scale_set)
369            {
370                work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area()));
371                is_work_scale_set = true;
372            }
373            resize(full_img, img, Size(), work_scale, work_scale);
374        }
375        if (!is_seam_scale_set)
376        {
377            seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area()));
378            seam_work_aspect = seam_scale / work_scale;
379            is_seam_scale_set = true;
380        }
381
382        (*finder)(img, features[i]);
383        features[i].img_idx = i;
384        LOGLN("Features in image #" << i+1 << ": " << features[i].keypoints.size());
385
386        resize(full_img, img, Size(), seam_scale, seam_scale);
387        images[i] = img.clone();
388    }
389
390    finder->collectGarbage();
391    full_img.release();
392    img.release();
393
394    LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
395
396    LOG("Pairwise matching");
397    t = getTickCount();
398    vector<MatchesInfo> pairwise_matches;
399    BestOf2NearestMatcher matcher(try_gpu, match_conf);
400    matcher(features, pairwise_matches);
401    matcher.collectGarbage();
402    LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
403
404    // Check if we should save matches graph
405    if (save_graph)
406    {
407        LOGLN("Saving matches graph...");
408        ofstream f(save_graph_to.c_str());
409        f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh);
410    }
411
412    // Leave only images we are sure are from the same panorama
413    vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh);
414    vector<Mat> img_subset;
415    vector<string> img_names_subset;
416    vector<Size> full_img_sizes_subset;
417    for (size_t i = 0; i < indices.size(); ++i)
418    {
419        img_names_subset.push_back(img_names[indices[i]]);
420        img_subset.push_back(images[indices[i]]);
421        full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);
422    }
423
424    images = img_subset;
425    img_names = img_names_subset;
426    full_img_sizes = full_img_sizes_subset;
427
428    // Check if we still have enough images
429    num_images = static_cast<int>(img_names.size());
430    if (num_images < 2)
431    {
432        LOGLN("Need more images");
433        return -1;
434    }
435
436    HomographyBasedEstimator estimator;
437    vector<CameraParams> cameras;
438    estimator(features, pairwise_matches, cameras);
439
440    for (size_t i = 0; i < cameras.size(); ++i)
441    {
442        Mat R;
443        cameras[i].R.convertTo(R, CV_32F);
444        cameras[i].R = R;
445        LOGLN("Initial intrinsics #" << indices[i]+1 << ":\n" << cameras[i].K());
446    }
447
448    Ptr<detail::BundleAdjusterBase> adjuster;
449    if (ba_cost_func == "reproj") adjuster = new detail::BundleAdjusterReproj();
450    else if (ba_cost_func == "ray") adjuster = new detail::BundleAdjusterRay();
451    else 
452    { 
453        cout << "Unknown bundle adjustment cost function: '" << ba_cost_func << "'.\n"; 
454        return -1; 
455    }
456    adjuster->setConfThresh(conf_thresh);
457    Mat_<uchar> refine_mask = Mat::zeros(3, 3, CV_8U);
458    if (ba_refine_mask[0] == 'x') refine_mask(0,0) = 1;
459    if (ba_refine_mask[1] == 'x') refine_mask(0,1) = 1;
460    if (ba_refine_mask[2] == 'x') refine_mask(0,2) = 1;
461    if (ba_refine_mask[3] == 'x') refine_mask(1,1) = 1;
462    if (ba_refine_mask[4] == 'x') refine_mask(1,2) = 1;
463    adjuster->setRefinementMask(refine_mask);
464    (*adjuster)(features, pairwise_matches, cameras);
465
466    // Find median focal length
467    vector<double> focals;
468    for (size_t i = 0; i < cameras.size(); ++i)
469    {
470        LOGLN("Camera #" << indices[i]+1 << ":\n" << cameras[i].K());
471        focals.push_back(cameras[i].focal);
472    }
473    nth_element(focals.begin(), focals.begin() + focals.size()/2, focals.end());
474    float warped_image_scale = static_cast<float>(focals[focals.size() / 2]);
475
476    if (do_wave_correct)
477    {
478        vector<Mat> rmats;
479        for (size_t i = 0; i < cameras.size(); ++i)
480            rmats.push_back(cameras[i].R);
481        waveCorrect(rmats, wave_correct);
482        for (size_t i = 0; i < cameras.size(); ++i)
483            cameras[i].R = rmats[i];
484    }
485
486    LOGLN("Warping images (auxiliary)... ");
487    t = getTickCount();
488
489    vector<Point> corners(num_images);
490    vector<Mat> masks_warped(num_images);
491    vector<Mat> images_warped(num_images);
492    vector<Size> sizes(num_images);
493    vector<Mat> masks(num_images);
494
495    // Preapre images masks
496    for (int i = 0; i < num_images; ++i)
497    {
498        masks[i].create(images[i].size(), CV_8U);
499        masks[i].setTo(Scalar::all(255));
500    }
501
502    // Warp images and their masks
503
504    Ptr<WarperCreator> warper_creator;
505#ifndef ANDROID
506    if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0)
507    {
508        if (warp_type == "plane") warper_creator = new cv::PlaneWarperGpu();
509        else if (warp_type == "cylindrical") warper_creator = new cv::CylindricalWarperGpu();
510        else if (warp_type == "spherical") warper_creator = new cv::SphericalWarperGpu();
511    }
512    else
513#endif
514    {
515        if (warp_type == "plane") warper_creator = new cv::PlaneWarper();
516        else if (warp_type == "cylindrical") warper_creator = new cv::CylindricalWarper();
517        else if (warp_type == "spherical") warper_creator = new cv::SphericalWarper();
518    }
519
520    if (warper_creator.empty())
521    {
522        cout << "Can't create the following warper '" << warp_type << "'\n";
523        return 1;
524    }
525   
526    Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale * seam_work_aspect));
527
528    for (int i = 0; i < num_images; ++i)
529    {
530        Mat_<float> K;
531        cameras[i].K().convertTo(K, CV_32F);
532        K(0,0) *= seam_work_aspect; K(0,2) *= seam_work_aspect;
533        K(1,1) *= seam_work_aspect; K(1,2) *= seam_work_aspect;
534
535        corners[i] = warper->warp(images[i], K, cameras[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);
536        sizes[i] = images_warped[i].size();
537
538        warper->warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
539    }
540
541    vector<Mat> images_warped_f(num_images);
542    for (int i = 0; i < num_images; ++i)
543        images_warped[i].convertTo(images_warped_f[i], CV_32F);
544
545    LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
546
547    Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type);
548    compensator->feed(corners, images_warped, masks_warped);
549
550    Ptr<SeamFinder> seam_finder;
551    if (seam_find_type == "no")
552        seam_finder = new detail::NoSeamFinder();
553    else if (seam_find_type == "voronoi")
554        seam_finder = new detail::VoronoiSeamFinder();
555    else if (seam_find_type == "gc_color")
556    {
557#ifndef ANDROID
558        if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0)
559            seam_finder = new detail::GraphCutSeamFinderGpu(GraphCutSeamFinderBase::COST_COLOR);
560        else
561#endif
562            seam_finder = new detail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR);
563    }
564    else if (seam_find_type == "gc_colorgrad")
565    {
566#ifndef ANDROID
567        if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0)
568            seam_finder = new detail::GraphCutSeamFinderGpu(GraphCutSeamFinderBase::COST_COLOR_GRAD);
569        else
570#endif
571            seam_finder = new detail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR_GRAD);
572    }
573    if (seam_finder.empty())
574    {
575        cout << "Can't create the following seam finder '" << seam_find_type << "'\n";
576        return 1;
577    }
578
579    seam_finder->find(images_warped_f, corners, masks_warped);
580
581    // Release unused memory
582    images.clear();
583    images_warped.clear();
584    images_warped_f.clear();
585    masks.clear();
586
587    LOGLN("Compositing...");
588    t = getTickCount();
589
590    Mat img_warped, img_warped_s;
591    Mat dilated_mask, seam_mask, mask, mask_warped;
592    Ptr<Blender> blender;
593    double compose_seam_aspect = 1;
594    double compose_work_aspect = 1;
595
596    for (int img_idx = 0; img_idx < num_images; ++img_idx)
597    {
598        LOGLN("Compositing image #" << indices[img_idx]+1);
599
600        // Read image and resize it if necessary
601        full_img = imread(img_names[img_idx]);
602        if (!is_compose_scale_set)
603        {
604            if (compose_megapix > 0)
605                compose_scale = min(1.0, sqrt(compose_megapix * 1e6 / full_img.size().area()));
606            is_compose_scale_set = true;
607
608            // Compute relative scales
609            compose_seam_aspect = compose_scale / seam_scale;
610            compose_work_aspect = compose_scale / work_scale;
611
612            // Update warped image scale
613            warped_image_scale *= static_cast<float>(compose_work_aspect);
614            warper = warper_creator->create(warped_image_scale);
615
616            // Update corners and sizes
617            for (int i = 0; i < num_images; ++i)
618            {
619                // Update intrinsics
620                cameras[i].focal *= compose_work_aspect;
621                cameras[i].ppx *= compose_work_aspect;
622                cameras[i].ppy *= compose_work_aspect;
623
624                // Update corner and size
625                Size sz = full_img_sizes[i];
626                if (std::abs(compose_scale - 1) > 1e-1)
627                {
628                    sz.width = cvRound(full_img_sizes[i].width * compose_scale);
629                    sz.height = cvRound(full_img_sizes[i].height * compose_scale);
630                }
631
632                Mat K;
633                cameras[i].K().convertTo(K, CV_32F);
634                Rect roi = warper->warpRoi(sz, K, cameras[i].R);
635                corners[i] = roi.tl();
636                sizes[i] = roi.size();
637            }
638        }
639        if (abs(compose_scale - 1) > 1e-1)
640            resize(full_img, img, Size(), compose_scale, compose_scale);
641        else
642            img = full_img;
643        full_img.release();
644        Size img_size = img.size();
645
646        Mat K;
647        cameras[img_idx].K().convertTo(K, CV_32F);
648
649        // Warp the current image
650        warper->warp(img, K, cameras[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);
651
652        // Warp the current image mask
653        mask.create(img_size, CV_8U);
654        mask.setTo(Scalar::all(255));
655        warper->warp(mask, K, cameras[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);
656
657        // Compensate exposure
658        compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped);
659
660        img_warped.convertTo(img_warped_s, CV_16S);
661        img_warped.release();
662        img.release();
663        mask.release();
664
665        dilate(masks_warped[img_idx], dilated_mask, Mat());
666        resize(dilated_mask, seam_mask, mask_warped.size());
667        mask_warped = seam_mask & mask_warped;
668
669        if (blender.empty())
670        {
671            blender = Blender::createDefault(blend_type, try_gpu);
672            Size dst_sz = resultRoi(corners, sizes).size();
673            float blend_width = sqrt(static_cast<float>(dst_sz.area())) * blend_strength / 100.f;
674            if (blend_width < 1.f)
675                blender = Blender::createDefault(Blender::NO, try_gpu);
676            else if (blend_type == Blender::MULTI_BAND)
677            {
678                MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender));
679                mb->setNumBands(static_cast<int>(ceil(log(blend_width)/log(2.)) - 1.));
680                LOGLN("Multi-band blender, number of bands: " << mb->numBands());
681            }
682            else if (blend_type == Blender::FEATHER)
683            {
684                FeatherBlender* fb = dynamic_cast<FeatherBlender*>(static_cast<Blender*>(blender));
685                fb->setSharpness(1.f/blend_width);
686                LOGLN("Feather blender, sharpness: " << fb->sharpness());
687            }
688            blender->prepare(corners, sizes);
689        }
690
691        // Blend the current image
692        blender->feed(img_warped_s, mask_warped, corners[img_idx]);
693    }
694
695    Mat result, result_mask;
696    blender->blend(result, result_mask);
697
698    LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
699
700    imwrite(result_name, result);
701
702    LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec");
703    return 0;
704}
705
706
Note: See TracBrowser for help on using the browser.