zoukankan      html  css  js  c++  java
  • OpenCV 图像拼接-Stitcher类-Stitching detailed使用与参数介绍

    关于OpenCV图像拼接的方法,如果不熟悉的话,可以先看看我整理的如下四篇博客:

    • OpenCV常用图像拼接方法(一):直接拼接(硬拼)

    • OpenCV常用图像拼接方法(二):基于模板匹配拼接

    • OpenCV常用图像拼接方法(三):基于特征匹配拼接

    • OpenCV常用图像拼接方法(四):基于Stitcher类拼接

    本篇博客是Stitcher类的扩展介绍,通过例程stitching_detailed.cpp的使用和参数介绍,帮助大家了解Stitcher类拼接的具体步骤和方法,先看看其内部的流程结构图(如下):

    这里写图片描述

    stitching_detailed.cpp目录如下,可以在自己安装的OpenCV目录下找到,笔者这里使用的OpenCV4.4版本 

    stitching_detailed.cpp具体源码如下: 

      1 // 05_Image_Stitch_Stitching_Detailed.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
      2 //
      3 #include "pch.h"
      4 #include <iostream>
      5 #include <fstream>
      6 #include <string>
      7 #include "opencv2/opencv_modules.hpp"
      8 #include <opencv2/core/utility.hpp>
      9 #include "opencv2/imgcodecs.hpp"
     10 #include "opencv2/highgui.hpp"
     11 #include "opencv2/stitching/detail/autocalib.hpp"
     12 #include "opencv2/stitching/detail/blenders.hpp"
     13 #include "opencv2/stitching/detail/timelapsers.hpp"
     14 #include "opencv2/stitching/detail/camera.hpp"
     15 #include "opencv2/stitching/detail/exposure_compensate.hpp"
     16 #include "opencv2/stitching/detail/matchers.hpp"
     17 #include "opencv2/stitching/detail/motion_estimators.hpp"
     18 #include "opencv2/stitching/detail/seam_finders.hpp"
     19 #include "opencv2/stitching/detail/warpers.hpp"
     20 #include "opencv2/stitching/warpers.hpp"
     21  
     22 #ifdef HAVE_OPENCV_XFEATURES2D
     23 #include "opencv2/xfeatures2d.hpp"
     24 #include "opencv2/xfeatures2d/nonfree.hpp"
     25 #endif
     26  
     27 #define ENABLE_LOG 1
     28 #define LOG(msg) std::cout << msg
     29 #define LOGLN(msg) std::cout << msg << std::endl
     30  
     31 using namespace std;
     32 using namespace cv;
     33 using namespace cv::detail;
     34  
     35 static void printUsage(char** argv)
     36 {
     37     cout <<
     38         "Rotation model images stitcher.
    
    "
     39         << argv[0] << " img1 img2 [...imgN] [flags]
    
    "
     40         "Flags:
    "
     41         "  --preview
    "
     42         "      Run stitching in the preview mode. Works faster than usual mode,
    "
     43         "      but output image will have lower resolution.
    "
     44         "  --try_cuda (yes|no)
    "
     45         "      Try to use CUDA. The default value is 'no'. All default values
    "
     46         "      are for CPU mode.
    "
     47         "
    Motion Estimation Flags:
    "
     48         "  --work_megapix <float>
    "
     49         "      Resolution for image registration step. The default is 0.6 Mpx.
    "
     50         "  --features (surf|orb|sift|akaze)
    "
     51         "      Type of features used for images matching.
    "
     52         "      The default is surf if available, orb otherwise.
    "
     53         "  --matcher (homography|affine)
    "
     54         "      Matcher used for pairwise image matching.
    "
     55         "  --estimator (homography|affine)
    "
     56         "      Type of estimator used for transformation estimation.
    "
     57         "  --match_conf <float>
    "
     58         "      Confidence for feature matching step. The default is 0.65 for surf and 0.3 for orb.
    "
     59         "  --conf_thresh <float>
    "
     60         "      Threshold for two images are from the same panorama confidence.
    "
     61         "      The default is 1.0.
    "
     62         "  --ba (no|reproj|ray|affine)
    "
     63         "      Bundle adjustment cost function. The default is ray.
    "
     64         "  --ba_refine_mask (mask)
    "
     65         "      Set refinement mask for bundle adjustment. It looks like 'x_xxx',
    "
     66         "      where 'x' means refine respective parameter and '_' means don't
    "
     67         "      refine one, and has the following format:
    "
     68         "      <fx><skew><ppx><aspect><ppy>. The default mask is 'xxxxx'. If bundle
    "
     69         "      adjustment doesn't support estimation of selected parameter then
    "
     70         "      the respective flag is ignored.
    "
     71         "  --wave_correct (no|horiz|vert)
    "
     72         "      Perform wave effect correction. The default is 'horiz'.
    "
     73         "  --save_graph <file_name>
    "
     74         "      Save matches graph represented in DOT language to <file_name> file.
    "
     75         "      Labels description: Nm is number of matches, Ni is number of inliers,
    "
     76         "      C is confidence.
    "
     77         "
    Compositing Flags:
    "
     78         "  --warp (affine|plane|cylindrical|spherical|fisheye|stereographic|compressedPlaneA2B1|compressedPlaneA1.5B1|compressedPlanePortraitA2B1|compressedPlanePortraitA1.5B1|paniniA2B1|paniniA1.5B1|paniniPortraitA2B1|paniniPortraitA1.5B1|mercator|transverseMercator)
    "
     79         "      Warp surface type. The default is 'spherical'.
    "
     80         "  --seam_megapix <float>
    "
     81         "      Resolution for seam estimation step. The default is 0.1 Mpx.
    "
     82         "  --seam (no|voronoi|gc_color|gc_colorgrad)
    "
     83         "      Seam estimation method. The default is 'gc_color'.
    "
     84         "  --compose_megapix <float>
    "
     85         "      Resolution for compositing step. Use -1 for original resolution.
    "
     86         "      The default is -1.
    "
     87         "  --expos_comp (no|gain|gain_blocks|channels|channels_blocks)
    "
     88         "      Exposure compensation method. The default is 'gain_blocks'.
    "
     89         "  --expos_comp_nr_feeds <int>
    "
     90         "      Number of exposure compensation feed. The default is 1.
    "
     91         "  --expos_comp_nr_filtering <int>
    "
     92         "      Number of filtering iterations of the exposure compensation gains.
    "
     93         "      Only used when using a block exposure compensation method.
    "
     94         "      The default is 2.
    "
     95         "  --expos_comp_block_size <int>
    "
     96         "      BLock size in pixels used by the exposure compensator.
    "
     97         "      Only used when using a block exposure compensation method.
    "
     98         "      The default is 32.
    "
     99         "  --blend (no|feather|multiband)
    "
    100         "      Blending method. The default is 'multiband'.
    "
    101         "  --blend_strength <float>
    "
    102         "      Blending strength from [0,100] range. The default is 5.
    "
    103         "  --output <result_img>
    "
    104         "      The default is 'result.jpg'.
    "
    105         "  --timelapse (as_is|crop) 
    "
    106         "      Output warped images separately as frames of a time lapse movie, with 'fixed_' prepended to input file names.
    "
    107         "  --rangewidth <int>
    "
    108         "      uses range_width to limit number of images to match with.
    ";
    109 }
    110  
    111  
    112 // Default command line args
    113 vector<String> img_names;
    114 bool preview = false;
    115 bool try_cuda = false;
    116 double work_megapix = 0.6;
    117 double seam_megapix = 0.1;
    118 double compose_megapix = -1;
    119 float conf_thresh = 1.f;
    120 #ifdef HAVE_OPENCV_XFEATURES2D
    121 string features_type = "surf";
    122 float match_conf = 0.65f;
    123 #else
    124 string features_type = "orb";
    125 float match_conf = 0.3f;
    126 #endif
    127 string matcher_type = "homography";
    128 string estimator_type = "homography";
    129 string ba_cost_func = "ray";
    130 string ba_refine_mask = "xxxxx";
    131 bool do_wave_correct = true;
    132 WaveCorrectKind wave_correct = detail::WAVE_CORRECT_HORIZ;
    133 bool save_graph = false;
    134 std::string save_graph_to;
    135 string warp_type = "spherical";
    136 int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
    137 int expos_comp_nr_feeds = 1;
    138 int expos_comp_nr_filtering = 2;
    139 int expos_comp_block_size = 32;
    140 string seam_find_type = "gc_color";
    141 int blend_type = Blender::MULTI_BAND;
    142 int timelapse_type = Timelapser::AS_IS;
    143 float blend_strength = 5;
    144 string result_name = "result.jpg";
    145 bool timelapse = false;
    146 int range_width = -1;
    147  
    148  
    149 static int parseCmdArgs(int argc, char** argv)
    150 {
    151     if (argc == 1)
    152     {
    153         printUsage(argv);
    154         return -1;
    155     }
    156     for (int i = 1; i < argc; ++i)
    157     {
    158         if (string(argv[i]) == "--help" || string(argv[i]) == "/?")
    159         {
    160             printUsage(argv);
    161             return -1;
    162         }
    163         else if (string(argv[i]) == "--preview")
    164         {
    165             preview = true;
    166         }
    167         else if (string(argv[i]) == "--try_cuda")
    168         {
    169             if (string(argv[i + 1]) == "no")
    170                 try_cuda = false;
    171             else if (string(argv[i + 1]) == "yes")
    172                 try_cuda = true;
    173             else
    174             {
    175                 cout << "Bad --try_cuda flag value
    ";
    176                 return -1;
    177             }
    178             i++;
    179         }
    180         else if (string(argv[i]) == "--work_megapix")
    181         {
    182             work_megapix = atof(argv[i + 1]);
    183             i++;
    184         }
    185         else if (string(argv[i]) == "--seam_megapix")
    186         {
    187             seam_megapix = atof(argv[i + 1]);
    188             i++;
    189         }
    190         else if (string(argv[i]) == "--compose_megapix")
    191         {
    192             compose_megapix = atof(argv[i + 1]);
    193             i++;
    194         }
    195         else if (string(argv[i]) == "--result")
    196         {
    197             result_name = argv[i + 1];
    198             i++;
    199         }
    200         else if (string(argv[i]) == "--features")
    201         {
    202             features_type = argv[i + 1];
    203             if (string(features_type) == "orb")
    204                 match_conf = 0.3f;
    205             i++;
    206         }
    207         else if (string(argv[i]) == "--matcher")
    208         {
    209             if (string(argv[i + 1]) == "homography" || string(argv[i + 1]) == "affine")
    210                 matcher_type = argv[i + 1];
    211             else
    212             {
    213                 cout << "Bad --matcher flag value
    ";
    214                 return -1;
    215             }
    216             i++;
    217         }
    218         else if (string(argv[i]) == "--estimator")
    219         {
    220             if (string(argv[i + 1]) == "homography" || string(argv[i + 1]) == "affine")
    221                 estimator_type = argv[i + 1];
    222             else
    223             {
    224                 cout << "Bad --estimator flag value
    ";
    225                 return -1;
    226             }
    227             i++;
    228         }
    229         else if (string(argv[i]) == "--match_conf")
    230         {
    231             match_conf = static_cast<float>(atof(argv[i + 1]));
    232             i++;
    233         }
    234         else if (string(argv[i]) == "--conf_thresh")
    235         {
    236             conf_thresh = static_cast<float>(atof(argv[i + 1]));
    237             i++;
    238         }
    239         else if (string(argv[i]) == "--ba")
    240         {
    241             ba_cost_func = argv[i + 1];
    242             i++;
    243         }
    244         else if (string(argv[i]) == "--ba_refine_mask")
    245         {
    246             ba_refine_mask = argv[i + 1];
    247             if (ba_refine_mask.size() != 5)
    248             {
    249                 cout << "Incorrect refinement mask length.
    ";
    250                 return -1;
    251             }
    252             i++;
    253         }
    254         else if (string(argv[i]) == "--wave_correct")
    255         {
    256             if (string(argv[i + 1]) == "no")
    257                 do_wave_correct = false;
    258             else if (string(argv[i + 1]) == "horiz")
    259             {
    260                 do_wave_correct = true;
    261                 wave_correct = detail::WAVE_CORRECT_HORIZ;
    262             }
    263             else if (string(argv[i + 1]) == "vert")
    264             {
    265                 do_wave_correct = true;
    266                 wave_correct = detail::WAVE_CORRECT_VERT;
    267             }
    268             else
    269             {
    270                 cout << "Bad --wave_correct flag value
    ";
    271                 return -1;
    272             }
    273             i++;
    274         }
    275         else if (string(argv[i]) == "--save_graph")
    276         {
    277             save_graph = true;
    278             save_graph_to = argv[i + 1];
    279             i++;
    280         }
    281         else if (string(argv[i]) == "--warp")
    282         {
    283             warp_type = string(argv[i + 1]);
    284             i++;
    285         }
    286         else if (string(argv[i]) == "--expos_comp")
    287         {
    288             if (string(argv[i + 1]) == "no")
    289                 expos_comp_type = ExposureCompensator::NO;
    290             else if (string(argv[i + 1]) == "gain")
    291                 expos_comp_type = ExposureCompensator::GAIN;
    292             else if (string(argv[i + 1]) == "gain_blocks")
    293                 expos_comp_type = ExposureCompensator::GAIN_BLOCKS;
    294             else if (string(argv[i + 1]) == "channels")
    295                 expos_comp_type = ExposureCompensator::CHANNELS;
    296             else if (string(argv[i + 1]) == "channels_blocks")
    297                 expos_comp_type = ExposureCompensator::CHANNELS_BLOCKS;
    298             else
    299             {
    300                 cout << "Bad exposure compensation method
    ";
    301                 return -1;
    302             }
    303             i++;
    304         }
    305         else if (string(argv[i]) == "--expos_comp_nr_feeds")
    306         {
    307             expos_comp_nr_feeds = atoi(argv[i + 1]);
    308             i++;
    309         }
    310         else if (string(argv[i]) == "--expos_comp_nr_filtering")
    311         {
    312             expos_comp_nr_filtering = atoi(argv[i + 1]);
    313             i++;
    314         }
    315         else if (string(argv[i]) == "--expos_comp_block_size")
    316         {
    317             expos_comp_block_size = atoi(argv[i + 1]);
    318             i++;
    319         }
    320         else if (string(argv[i]) == "--seam")
    321         {
    322             if (string(argv[i + 1]) == "no" ||
    323                 string(argv[i + 1]) == "voronoi" ||
    324                 string(argv[i + 1]) == "gc_color" ||
    325                 string(argv[i + 1]) == "gc_colorgrad" ||
    326                 string(argv[i + 1]) == "dp_color" ||
    327                 string(argv[i + 1]) == "dp_colorgrad")
    328                 seam_find_type = argv[i + 1];
    329             else
    330             {
    331                 cout << "Bad seam finding method
    ";
    332                 return -1;
    333             }
    334             i++;
    335         }
    336         else if (string(argv[i]) == "--blend")
    337         {
    338             if (string(argv[i + 1]) == "no")
    339                 blend_type = Blender::NO;
    340             else if (string(argv[i + 1]) == "feather")
    341                 blend_type = Blender::FEATHER;
    342             else if (string(argv[i + 1]) == "multiband")
    343                 blend_type = Blender::MULTI_BAND;
    344             else
    345             {
    346                 cout << "Bad blending method
    ";
    347                 return -1;
    348             }
    349             i++;
    350         }
    351         else if (string(argv[i]) == "--timelapse")
    352         {
    353             timelapse = true;
    354  
    355             if (string(argv[i + 1]) == "as_is")
    356                 timelapse_type = Timelapser::AS_IS;
    357             else if (string(argv[i + 1]) == "crop")
    358                 timelapse_type = Timelapser::CROP;
    359             else
    360             {
    361                 cout << "Bad timelapse method
    ";
    362                 return -1;
    363             }
    364             i++;
    365         }
    366         else if (string(argv[i]) == "--rangewidth")
    367         {
    368             range_width = atoi(argv[i + 1]);
    369             i++;
    370         }
    371         else if (string(argv[i]) == "--blend_strength")
    372         {
    373             blend_strength = static_cast<float>(atof(argv[i + 1]));
    374             i++;
    375         }
    376         else if (string(argv[i]) == "--output")
    377         {
    378             result_name = argv[i + 1];
    379             i++;
    380         }
    381         else
    382             img_names.push_back(argv[i]);
    383     }
    384     if (preview)
    385     {
    386         compose_megapix = 0.6;
    387     }
    388     return 0;
    389 }
    390  
    391  
    392 int main(int argc, char* argv[])
    393 {
    394 #if ENABLE_LOG
    395     int64 app_start_time = getTickCount();
    396 #endif
    397  
    398 #if 0
    399     cv::setBreakOnError(true);
    400 #endif
    401  
    402     int retval = parseCmdArgs(argc, argv);
    403     if (retval)
    404         return retval;
    405  
    406     // Check if have enough images
    407     int num_images = static_cast<int>(img_names.size());
    408     if (num_images < 2)
    409     {
    410         LOGLN("Need more images");
    411         return -1;
    412     }
    413  
    414     double work_scale = 1, seam_scale = 1, compose_scale = 1;
    415     bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false;
    416  
    417     LOGLN("Finding features...");
    418 #if ENABLE_LOG
    419     int64 t = getTickCount();
    420 #endif
    421  
    422     Ptr<Feature2D> finder;
    423     if (features_type == "orb")
    424     {
    425         finder = ORB::create();
    426     }
    427     else if (features_type == "akaze")
    428     {
    429         finder = AKAZE::create();
    430     }
    431 #ifdef HAVE_OPENCV_XFEATURES2D
    432     else if (features_type == "surf")
    433     {
    434         finder = xfeatures2d::SURF::create();
    435     }
    436 #endif
    437     else if (features_type == "sift")
    438     {
    439         finder = SIFT::create();
    440     }
    441     else
    442     {
    443         cout << "Unknown 2D features type: '" << features_type << "'.
    ";
    444         return -1;
    445     }
    446  
    447     Mat full_img, img;
    448     vector<ImageFeatures> features(num_images);
    449     vector<Mat> images(num_images);
    450     vector<Size> full_img_sizes(num_images);
    451     double seam_work_aspect = 1;
    452  
    453     for (int i = 0; i < num_images; ++i)
    454     {
    455         full_img = imread(samples::findFile(img_names[i]));
    456         full_img_sizes[i] = full_img.size();
    457  
    458         if (full_img.empty())
    459         {
    460             LOGLN("Can't open image " << img_names[i]);
    461             return -1;
    462         }
    463         if (work_megapix < 0)
    464         {
    465             img = full_img;
    466             work_scale = 1;
    467             is_work_scale_set = true;
    468         }
    469         else
    470         {
    471             if (!is_work_scale_set)
    472             {
    473                 work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area()));
    474                 is_work_scale_set = true;
    475             }
    476             resize(full_img, img, Size(), work_scale, work_scale, INTER_LINEAR_EXACT);
    477         }
    478         if (!is_seam_scale_set)
    479         {
    480             seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area()));
    481             seam_work_aspect = seam_scale / work_scale;
    482             is_seam_scale_set = true;
    483         }
    484  
    485         computeImageFeatures(finder, img, features[i]);
    486         features[i].img_idx = i;
    487         LOGLN("Features in image #" << i + 1 << ": " << features[i].keypoints.size());
    488  
    489         resize(full_img, img, Size(), seam_scale, seam_scale, INTER_LINEAR_EXACT);
    490         images[i] = img.clone();
    491     }
    492  
    493     full_img.release();
    494     img.release();
    495  
    496     LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
    497  
    498     LOG("Pairwise matching");
    499 #if ENABLE_LOG
    500     t = getTickCount();
    501 #endif
    502     vector<MatchesInfo> pairwise_matches;
    503     Ptr<FeaturesMatcher> matcher;
    504     if (matcher_type == "affine")
    505         matcher = makePtr<AffineBestOf2NearestMatcher>(false, try_cuda, match_conf);
    506     else if (range_width == -1)
    507         matcher = makePtr<BestOf2NearestMatcher>(try_cuda, match_conf);
    508     else
    509         matcher = makePtr<BestOf2NearestRangeMatcher>(range_width, try_cuda, match_conf);
    510  
    511     (*matcher)(features, pairwise_matches);
    512     matcher->collectGarbage();
    513  
    514     LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
    515  
    516     // Check if we should save matches graph
    517     if (save_graph)
    518     {
    519         LOGLN("Saving matches graph...");
    520         ofstream f(save_graph_to.c_str());
    521         f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh);
    522     }
    523  
    524     // Leave only images we are sure are from the same panorama
    525     vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh);
    526     vector<Mat> img_subset;
    527     vector<String> img_names_subset;
    528     vector<Size> full_img_sizes_subset;
    529     for (size_t i = 0; i < indices.size(); ++i)
    530     {
    531         img_names_subset.push_back(img_names[indices[i]]);
    532         img_subset.push_back(images[indices[i]]);
    533         full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);
    534     }
    535  
    536     images = img_subset;
    537     img_names = img_names_subset;
    538     full_img_sizes = full_img_sizes_subset;
    539  
    540     // Check if we still have enough images
    541     num_images = static_cast<int>(img_names.size());
    542     if (num_images < 2)
    543     {
    544         LOGLN("Need more images");
    545         return -1;
    546     }
    547  
    548     Ptr<Estimator> estimator;
    549     if (estimator_type == "affine")
    550         estimator = makePtr<AffineBasedEstimator>();
    551     else
    552         estimator = makePtr<HomographyBasedEstimator>();
    553  
    554     vector<CameraParams> cameras;
    555     if (!(*estimator)(features, pairwise_matches, cameras))
    556     {
    557         cout << "Homography estimation failed.
    ";
    558         return -1;
    559     }
    560  
    561     for (size_t i = 0; i < cameras.size(); ++i)
    562     {
    563         Mat R;
    564         cameras[i].R.convertTo(R, CV_32F);
    565         cameras[i].R = R;
    566         LOGLN("Initial camera intrinsics #" << indices[i] + 1 << ":
    K:
    " << cameras[i].K() << "
    R:
    " << cameras[i].R);
    567     }
    568  
    569     Ptr<detail::BundleAdjusterBase> adjuster;
    570     if (ba_cost_func == "reproj") adjuster = makePtr<detail::BundleAdjusterReproj>();
    571     else if (ba_cost_func == "ray") adjuster = makePtr<detail::BundleAdjusterRay>();
    572     else if (ba_cost_func == "affine") adjuster = makePtr<detail::BundleAdjusterAffinePartial>();
    573     else if (ba_cost_func == "no") adjuster = makePtr<NoBundleAdjuster>();
    574     else
    575     {
    576         cout << "Unknown bundle adjustment cost function: '" << ba_cost_func << "'.
    ";
    577         return -1;
    578     }
    579     adjuster->setConfThresh(conf_thresh);
    580     Mat_<uchar> refine_mask = Mat::zeros(3, 3, CV_8U);
    581     if (ba_refine_mask[0] == 'x') refine_mask(0, 0) = 1;
    582     if (ba_refine_mask[1] == 'x') refine_mask(0, 1) = 1;
    583     if (ba_refine_mask[2] == 'x') refine_mask(0, 2) = 1;
    584     if (ba_refine_mask[3] == 'x') refine_mask(1, 1) = 1;
    585     if (ba_refine_mask[4] == 'x') refine_mask(1, 2) = 1;
    586     adjuster->setRefinementMask(refine_mask);
    587     if (!(*adjuster)(features, pairwise_matches, cameras))
    588     {
    589         cout << "Camera parameters adjusting failed.
    ";
    590         return -1;
    591     }
    592  
    593     // Find median focal length
    594  
    595     vector<double> focals;
    596     for (size_t i = 0; i < cameras.size(); ++i)
    597     {
    598         LOGLN("Camera #" << indices[i] + 1 << ":
    K:
    " << cameras[i].K() << "
    R:
    " << cameras[i].R);
    599         focals.push_back(cameras[i].focal);
    600     }
    601  
    602     sort(focals.begin(), focals.end());
    603     float warped_image_scale;
    604     if (focals.size() % 2 == 1)
    605         warped_image_scale = static_cast<float>(focals[focals.size() / 2]);
    606     else
    607         warped_image_scale = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;
    608  
    609     if (do_wave_correct)
    610     {
    611         vector<Mat> rmats;
    612         for (size_t i = 0; i < cameras.size(); ++i)
    613             rmats.push_back(cameras[i].R.clone());
    614         waveCorrect(rmats, wave_correct);
    615         for (size_t i = 0; i < cameras.size(); ++i)
    616             cameras[i].R = rmats[i];
    617     }
    618  
    619     LOGLN("Warping images (auxiliary)... ");
    620 #if ENABLE_LOG
    621     t = getTickCount();
    622 #endif
    623  
    624     vector<Point> corners(num_images);
    625     vector<UMat> masks_warped(num_images);
    626     vector<UMat> images_warped(num_images);
    627     vector<Size> sizes(num_images);
    628     vector<UMat> masks(num_images);
    629  
    630     // Prepare images masks
    631     for (int i = 0; i < num_images; ++i)
    632     {
    633         masks[i].create(images[i].size(), CV_8U);
    634         masks[i].setTo(Scalar::all(255));
    635     }
    636  
    637     // Warp images and their masks
    638  
    639     Ptr<WarperCreator> warper_creator;
    640 #ifdef HAVE_OPENCV_CUDAWARPING
    641     if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
    642     {
    643         if (warp_type == "plane")
    644             warper_creator = makePtr<cv::PlaneWarperGpu>();
    645         else if (warp_type == "cylindrical")
    646             warper_creator = makePtr<cv::CylindricalWarperGpu>();
    647         else if (warp_type == "spherical")
    648             warper_creator = makePtr<cv::SphericalWarperGpu>();
    649     }
    650     else
    651 #endif
    652     {
    653         if (warp_type == "plane")
    654             warper_creator = makePtr<cv::PlaneWarper>();
    655         else if (warp_type == "affine")
    656             warper_creator = makePtr<cv::AffineWarper>();
    657         else if (warp_type == "cylindrical")
    658             warper_creator = makePtr<cv::CylindricalWarper>();
    659         else if (warp_type == "spherical")
    660             warper_creator = makePtr<cv::SphericalWarper>();
    661         else if (warp_type == "fisheye")
    662             warper_creator = makePtr<cv::FisheyeWarper>();
    663         else if (warp_type == "stereographic")
    664             warper_creator = makePtr<cv::StereographicWarper>();
    665         else if (warp_type == "compressedPlaneA2B1")
    666             warper_creator = makePtr<cv::CompressedRectilinearWarper>(2.0f, 1.0f);
    667         else if (warp_type == "compressedPlaneA1.5B1")
    668             warper_creator = makePtr<cv::CompressedRectilinearWarper>(1.5f, 1.0f);
    669         else if (warp_type == "compressedPlanePortraitA2B1")
    670             warper_creator = makePtr<cv::CompressedRectilinearPortraitWarper>(2.0f, 1.0f);
    671         else if (warp_type == "compressedPlanePortraitA1.5B1")
    672             warper_creator = makePtr<cv::CompressedRectilinearPortraitWarper>(1.5f, 1.0f);
    673         else if (warp_type == "paniniA2B1")
    674             warper_creator = makePtr<cv::PaniniWarper>(2.0f, 1.0f);
    675         else if (warp_type == "paniniA1.5B1")
    676             warper_creator = makePtr<cv::PaniniWarper>(1.5f, 1.0f);
    677         else if (warp_type == "paniniPortraitA2B1")
    678             warper_creator = makePtr<cv::PaniniPortraitWarper>(2.0f, 1.0f);
    679         else if (warp_type == "paniniPortraitA1.5B1")
    680             warper_creator = makePtr<cv::PaniniPortraitWarper>(1.5f, 1.0f);
    681         else if (warp_type == "mercator")
    682             warper_creator = makePtr<cv::MercatorWarper>();
    683         else if (warp_type == "transverseMercator")
    684             warper_creator = makePtr<cv::TransverseMercatorWarper>();
    685     }
    686  
    687     if (!warper_creator)
    688     {
    689         cout << "Can't create the following warper '" << warp_type << "'
    ";
    690         return 1;
    691     }
    692  
    693     Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale * seam_work_aspect));
    694  
    695     for (int i = 0; i < num_images; ++i)
    696     {
    697         Mat_<float> K;
    698         cameras[i].K().convertTo(K, CV_32F);
    699         float swa = (float)seam_work_aspect;
    700         K(0, 0) *= swa; K(0, 2) *= swa;
    701         K(1, 1) *= swa; K(1, 2) *= swa;
    702  
    703         corners[i] = warper->warp(images[i], K, cameras[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);
    704         sizes[i] = images_warped[i].size();
    705  
    706         warper->warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
    707     }
    708  
    709     vector<UMat> images_warped_f(num_images);
    710     for (int i = 0; i < num_images; ++i)
    711         images_warped[i].convertTo(images_warped_f[i], CV_32F);
    712  
    713     LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
    714  
    715     LOGLN("Compensating exposure...");
    716 #if ENABLE_LOG
    717     t = getTickCount();
    718 #endif
    719  
    720     Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type);
    721     if (dynamic_cast<GainCompensator*>(compensator.get()))
    722     {
    723         GainCompensator* gcompensator = dynamic_cast<GainCompensator*>(compensator.get());
    724         gcompensator->setNrFeeds(expos_comp_nr_feeds);
    725     }
    726  
    727     if (dynamic_cast<ChannelsCompensator*>(compensator.get()))
    728     {
    729         ChannelsCompensator* ccompensator = dynamic_cast<ChannelsCompensator*>(compensator.get());
    730         ccompensator->setNrFeeds(expos_comp_nr_feeds);
    731     }
    732  
    733     if (dynamic_cast<BlocksCompensator*>(compensator.get()))
    734     {
    735         BlocksCompensator* bcompensator = dynamic_cast<BlocksCompensator*>(compensator.get());
    736         bcompensator->setNrFeeds(expos_comp_nr_feeds);
    737         bcompensator->setNrGainsFilteringIterations(expos_comp_nr_filtering);
    738         bcompensator->setBlockSize(expos_comp_block_size, expos_comp_block_size);
    739     }
    740  
    741     compensator->feed(corners, images_warped, masks_warped);
    742  
    743     LOGLN("Compensating exposure, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
    744  
    745     LOGLN("Finding seams...");
    746 #if ENABLE_LOG
    747     t = getTickCount();
    748 #endif
    749  
    750     Ptr<SeamFinder> seam_finder;
    751     if (seam_find_type == "no")
    752         seam_finder = makePtr<detail::NoSeamFinder>();
    753     else if (seam_find_type == "voronoi")
    754         seam_finder = makePtr<detail::VoronoiSeamFinder>();
    755     else if (seam_find_type == "gc_color")
    756     {
    757 #ifdef HAVE_OPENCV_CUDALEGACY
    758         if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
    759             seam_finder = makePtr<detail::GraphCutSeamFinderGpu>(GraphCutSeamFinderBase::COST_COLOR);
    760         else
    761 #endif
    762             seam_finder = makePtr<detail::GraphCutSeamFinder>(GraphCutSeamFinderBase::COST_COLOR);
    763     }
    764     else if (seam_find_type == "gc_colorgrad")
    765     {
    766 #ifdef HAVE_OPENCV_CUDALEGACY
    767         if (try_cuda && cuda::getCudaEnabledDeviceCount() > 0)
    768             seam_finder = makePtr<detail::GraphCutSeamFinderGpu>(GraphCutSeamFinderBase::COST_COLOR_GRAD);
    769         else
    770 #endif
    771             seam_finder = makePtr<detail::GraphCutSeamFinder>(GraphCutSeamFinderBase::COST_COLOR_GRAD);
    772     }
    773     else if (seam_find_type == "dp_color")
    774         seam_finder = makePtr<detail::DpSeamFinder>(DpSeamFinder::COLOR);
    775     else if (seam_find_type == "dp_colorgrad")
    776         seam_finder = makePtr<detail::DpSeamFinder>(DpSeamFinder::COLOR_GRAD);
    777     if (!seam_finder)
    778     {
    779         cout << "Can't create the following seam finder '" << seam_find_type << "'
    ";
    780         return 1;
    781     }
    782  
    783     seam_finder->find(images_warped_f, corners, masks_warped);
    784  
    785     LOGLN("Finding seams, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
    786  
    787     // Release unused memory
    788     images.clear();
    789     images_warped.clear();
    790     images_warped_f.clear();
    791     masks.clear();
    792  
    793     LOGLN("Compositing...");
    794 #if ENABLE_LOG
    795     t = getTickCount();
    796 #endif
    797  
    798     Mat img_warped, img_warped_s;
    799     Mat dilated_mask, seam_mask, mask, mask_warped;
    800     Ptr<Blender> blender;
    801     Ptr<Timelapser> timelapser;
    802     //double compose_seam_aspect = 1;
    803     double compose_work_aspect = 1;
    804  
    805     for (int img_idx = 0; img_idx < num_images; ++img_idx)
    806     {
    807         LOGLN("Compositing image #" << indices[img_idx] + 1);
    808  
    809         // Read image and resize it if necessary
    810         full_img = imread(samples::findFile(img_names[img_idx]));
    811         if (!is_compose_scale_set)
    812         {
    813             if (compose_megapix > 0)
    814                 compose_scale = min(1.0, sqrt(compose_megapix * 1e6 / full_img.size().area()));
    815             is_compose_scale_set = true;
    816  
    817             // Compute relative scales
    818             //compose_seam_aspect = compose_scale / seam_scale;
    819             compose_work_aspect = compose_scale / work_scale;
    820  
    821             // Update warped image scale
    822             warped_image_scale *= static_cast<float>(compose_work_aspect);
    823             warper = warper_creator->create(warped_image_scale);
    824  
    825             // Update corners and sizes
    826             for (int i = 0; i < num_images; ++i)
    827             {
    828                 // Update intrinsics
    829                 cameras[i].focal *= compose_work_aspect;
    830                 cameras[i].ppx *= compose_work_aspect;
    831                 cameras[i].ppy *= compose_work_aspect;
    832  
    833                 // Update corner and size
    834                 Size sz = full_img_sizes[i];
    835                 if (std::abs(compose_scale - 1) > 1e-1)
    836                 {
    837                     sz.width = cvRound(full_img_sizes[i].width * compose_scale);
    838                     sz.height = cvRound(full_img_sizes[i].height * compose_scale);
    839                 }
    840  
    841                 Mat K;
    842                 cameras[i].K().convertTo(K, CV_32F);
    843                 Rect roi = warper->warpRoi(sz, K, cameras[i].R);
    844                 corners[i] = roi.tl();
    845                 sizes[i] = roi.size();
    846             }
    847         }
    848         if (abs(compose_scale - 1) > 1e-1)
    849             resize(full_img, img, Size(), compose_scale, compose_scale, INTER_LINEAR_EXACT);
    850         else
    851             img = full_img;
    852         full_img.release();
    853         Size img_size = img.size();
    854  
    855         Mat K;
    856         cameras[img_idx].K().convertTo(K, CV_32F);
    857  
    858         // Warp the current image
    859         warper->warp(img, K, cameras[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);
    860  
    861         // Warp the current image mask
    862         mask.create(img_size, CV_8U);
    863         mask.setTo(Scalar::all(255));
    864         warper->warp(mask, K, cameras[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);
    865  
    866         // Compensate exposure
    867         compensator->apply(img_idx, corners[img_idx], img_warped, mask_warped);
    868  
    869         img_warped.convertTo(img_warped_s, CV_16S);
    870         img_warped.release();
    871         img.release();
    872         mask.release();
    873  
    874         dilate(masks_warped[img_idx], dilated_mask, Mat());
    875         resize(dilated_mask, seam_mask, mask_warped.size(), 0, 0, INTER_LINEAR_EXACT);
    876         mask_warped = seam_mask & mask_warped;
    877  
    878         if (!blender && !timelapse)
    879         {
    880             blender = Blender::createDefault(blend_type, try_cuda);
    881             Size dst_sz = resultRoi(corners, sizes).size();
    882             float blend_width = sqrt(static_cast<float>(dst_sz.area())) * blend_strength / 100.f;
    883             if (blend_width < 1.f)
    884                 blender = Blender::createDefault(Blender::NO, try_cuda);
    885             else if (blend_type == Blender::MULTI_BAND)
    886             {
    887                 MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(blender.get());
    888                 mb->setNumBands(static_cast<int>(ceil(log(blend_width) / log(2.)) - 1.));
    889                 LOGLN("Multi-band blender, number of bands: " << mb->numBands());
    890             }
    891             else if (blend_type == Blender::FEATHER)
    892             {
    893                 FeatherBlender* fb = dynamic_cast<FeatherBlender*>(blender.get());
    894                 fb->setSharpness(1.f / blend_width);
    895                 LOGLN("Feather blender, sharpness: " << fb->sharpness());
    896             }
    897             blender->prepare(corners, sizes);
    898         }
    899         else if (!timelapser && timelapse)
    900         {
    901             timelapser = Timelapser::createDefault(timelapse_type);
    902             timelapser->initialize(corners, sizes);
    903         }
    904  
    905         // Blend the current image
    906         if (timelapse)
    907         {
    908             timelapser->process(img_warped_s, Mat::ones(img_warped_s.size(), CV_8UC1), corners[img_idx]);
    909             String fixedFileName;
    910             size_t pos_s = String(img_names[img_idx]).find_last_of("/\");
    911             if (pos_s == String::npos)
    912             {
    913                 fixedFileName = "fixed_" + img_names[img_idx];
    914             }
    915             else
    916             {
    917                 fixedFileName = "fixed_" + String(img_names[img_idx]).substr(pos_s + 1, String(img_names[img_idx]).length() - pos_s);
    918             }
    919             imwrite(fixedFileName, timelapser->getDst());
    920         }
    921         else
    922         {
    923             blender->feed(img_warped_s, mask_warped, corners[img_idx]);
    924         }
    925     }
    926  
    927     if (!timelapse)
    928     {
    929         Mat result, result_mask;
    930         blender->blend(result, result_mask);
    931  
    932         LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");
    933  
    934         imwrite(result_name, result);
    935     }
    936  
    937     LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec");
    938     return 0;
    939 }

    stitching_detail 程序运行流程

    • 命令行调用程序,输入源图像以及程序的参数      
    • 特征点检测,判断是使用 surf 还是 orb,默认是 surf
    • 对图像的特征点进行匹配,使用最近邻和次近邻方法,将两个最优的匹配的置信度 保存下来
    • 对图像进行排序以及将置信度高的图像保存到同一个集合中,删除置信度比较低的图像间的匹配,得到能正确匹配的图像序列。这样将置信度高于门限的所有匹配合并到一个集合中 
    • 对所有图像进行相机参数粗略估计,然后求出旋转矩阵
    • 使用光束平均法进一步精准的估计出旋转矩阵
    • 波形校正,水平或者垂直
    • 拼接      
    • 融合,多频段融合,光照补偿

    stitching_detail 程序接口介绍 

    • img1 img2 img3 输入图像      
    • --preview  以预览模式运行程序,比正常模式要快,但输出图像分辨率低,拼接的分辨 率 compose_megapix 设置为 0.6
    • --try_gpu  (yes|no)  是否使用 CUDA加速,默认为 no,使用CPU模式
    • /* 运动估计参数 */    
    • --work_megapix <--work_megapix <float>> 图像匹配时的分辨率大小,默认为 0.6    
    • --features (surf | orb | sift | akaze) 选择 surf 或者 orb 算法进行特征点匹配,默认为 surf  
    • --matcher (homography | affine) 用于成对图像匹配的匹配器  
    • --estimator (homography | affine) 用于转换估计的估计器类型
    • --match_conf <float> 特征点匹配步骤的匹配置信度,最近邻匹配距离与次近邻匹配距离的比值,surf 默认为 0.65,orb 默认为 0.3    
    • --conf_thresh <float> 两幅图来自同一全景图的置信度,默认为 1.0    
    • --ba (no | reproj | ray | affine) 光束平均法的误差函数选择,默认是 ray 方法    
    • --ba_refine_mask (mask) 光束平均法设置优化掩码
    • --wave_correct (no|horiz|vert) 波形校验水平,垂直或者没有 默认是 horiz(水平)
    • --save_graph <file_name> 将匹配的图形以点的形式保存到文件中, Nm 代表匹配的数量,NI代表正确匹配的数量,C 表示置信度
    • /*图像融合参数:*/ 
    • --warp (plane|cylindrical|spherical|fisheye|stereographic|compressedPlaneA2B1|compressedPla  neA1.5B1|compressedPlanePortraitA2B1|compressedPlanePortraitA1.5B1|paniniA2B1|paniniA1.5B1|paniniPortraitA2B1|paniniPor traitA1.5B1|mercator|transverseMercator)     选择融合的平面,默认是球形    
    • --seam_megapix <float> 拼接缝像素的大小 默认是 0.1
    • --seam (no|voronoi|gc_color|gc_colorgrad) 拼接缝隙估计方法 默认是 gc_color    
    • --compose_megapix <float> 拼接分辨率,默认为-1    
    • --expos_comp (no|gain|gain_blocks) 光照补偿方法,默认是 gain_blocks    
    • --blend (no|feather|multiband) 融合方法,默认是多频段融合    
    • --blend_strength <float> 融合强度,0-100.默认是 5.    
    • --output <result_img> 输出图像的文件名,默认是 result,jpg     命令使用实例,以及程序运行时的提示: 

    上面使用默认参数,详细输出信息如下:

      1 E:PracticeOpenCVAlgorithm_SummaryImage_Stitchingx64Debug>05_Image_Stitch_Stitching_Detailed.exe ./imgs/boat1.jpg ./imgs/boat2.jpg ./imgs/boat3.jpg ./imgs/boat4.jpg ./imgs/boat5.jpg ./imgs/boat6.jpg
      2 Finding features...
      3 [ INFO:0] global C:uildmaster_winpack-build-win64-vc15opencvmodulescoresrcocl.cpp (891) cv::ocl::haveOpenCL Initialize OpenCL runtime...
      4 Features in image #1: 500
      5 [ INFO:0] global C:uildmaster_winpack-build-win64-vc15opencvmodulescoresrcocl.cpp (433) cv::ocl::OpenCLBinaryCacheConfigurator::OpenCLBinaryCacheConfigurator Successfully initialized OpenCL cache directory: C:UsersA4080599AppDataLocalTempopencv4.4opencl_cache
      6 [ INFO:0] global C:uildmaster_winpack-build-win64-vc15opencvmodulescoresrcocl.cpp (457) cv::ocl::OpenCLBinaryCacheConfigurator::prepareCacheDirectoryForContext Preparing OpenCL cache configuration for context: NVIDIA_Corporation--GeForce_GTX_1070--411_31
      7 Features in image #2: 500
      8 Features in image #3: 500
      9 Features in image #4: 500
     10 Features in image #5: 500
     11 Features in image #6: 500
     12 Finding features, time: 5.46377 sec
     13 Pairwise matchingPairwise matching, time: 3.24159 sec
     14 Initial camera intrinsics #1:
     15 K:
     16 [534.6674906996568, 0, 474.5;
     17  0, 534.6674906996568, 316;
     18  0, 0, 1]
     19 R:
     20 [0.91843718, -0.09762425, -1.1678253;
     21  0.0034433089, 1.0835428, -0.025021957;
     22  0.28152198, 0.16100603, 0.91920781]
     23 Initial camera intrinsics #2:
     24 K:
     25 [534.6674906996568, 0, 474.5;
     26  0, 534.6674906996568, 316;
     27  0, 0, 1]
     28 R:
     29 [1.001171, -0.085758291, -0.64530683;
     30  0.010103324, 1.0520245, -0.030576767;
     31  0.15743911, 0.12035993, 1]
     32 Initial camera intrinsics #3:
     33 K:
     34 [534.6674906996568, 0, 474.5;
     35  0, 534.6674906996568, 316;
     36  0, 0, 1]
     37 R:
     38 [1, 0, 0;
     39  0, 1, 0;
     40  0, 0, 1]
     41 Initial camera intrinsics #4:
     42 K:
     43 [534.6674906996568, 0, 474.5;
     44  0, 534.6674906996568, 316;
     45  0, 0, 1]
     46 R:
     47 [0.8474561, 0.028589081, 0.75133896;
     48  -0.0014587968, 0.92028928, 0.033205934;
     49  -0.17483309, 0.018777205, 0.84592116]
     50 Initial camera intrinsics #5:
     51 K:
     52 [534.6674906996568, 0, 474.5;
     53  0, 534.6674906996568, 316;
     54  0, 0, 1]
     55 R:
     56 [0.60283858, 0.069275051, 1.2121853;
     57  -0.014153662, 0.85474133, 0.014057174;
     58  -0.29529575, 0.053770453, 0.61932623]
     59 Initial camera intrinsics #6:
     60 K:
     61 [534.6674906996568, 0, 474.5;
     62  0, 534.6674906996568, 316;
     63  0, 0, 1]
     64 R:
     65 [0.41477469, 0.075901195, 1.4396564;
     66  -0.015423983, 0.82344943, 0.0061162044;
     67  -0.35168326, 0.055747174, 0.42653102]
     68 Camera #1:
     69 K:
     70 [1068.953598931666, 0, 474.5;
     71  0, 1068.953598931666, 316;
     72  0, 0, 1]
     73 R:
     74 [0.84266716, -0.010490002, -0.53833258;
     75  0.004485324, 0.99991232, -0.01246338;
     76  0.53841609, 0.0080878884, 0.84264034]
     77 Camera #2:
     78 K:
     79 [1064.878323247434, 0, 474.5;
     80  0, 1064.878323247434, 316;
     81  0, 0, 1]
     82 R:
     83 [0.95117813, -0.015436338, -0.3082563;
     84  0.01137107, 0.99982315, -0.014980057;
     85  0.308433, 0.010743499, 0.95118535]
     86 Camera #3:
     87 K:
     88 [1065.382193682081, 0, 474.5;
     89  0, 1065.382193682081, 316;
     90  0, 0, 1]
     91 R:
     92 [1, -1.6298145e-09, 0;
     93  -1.5716068e-09, 1, 0;
     94  0, 0, 1]
     95 Camera #4:
     96 K:
     97 [1067.611537959627, 0, 474.5;
     98  0, 1067.611537959627, 316;
     99  0, 0, 1]
    100 R:
    101 [0.91316396, -7.9067249e-06, 0.40759254;
    102  -0.0075879274, 0.99982637, 0.017019274;
    103  -0.4075219, -0.018634165, 0.91300529]
    104 Camera #5:
    105 K:
    106 [1080.708135180496, 0, 474.5;
    107  0, 1080.708135180496, 316;
    108  0, 0, 1]
    109 R:
    110 [0.70923853, 0.0025724203, 0.70496398;
    111  -0.0098195076, 0.99993235, 0.0062302947;
    112  -0.70490021, -0.01134116, 0.70921582]
    113 Camera #6:
    114 K:
    115 [1080.90412660159, 0, 474.5;
    116  0, 1080.90412660159, 316;
    117  0, 0, 1]
    118 R:
    119 [0.49985889, 3.5938341e-05, 0.86610687;
    120  -0.00682831, 0.99996907, 0.0038993564;
    121  -0.86607999, -0.0078631733, 0.49984369]
    122 Warping images (auxiliary)...
    123 Warping images, time: 0.0791121 sec
    124 Compensating exposure...
    125 Compensating exposure, time: 0.72288 sec
    126 Finding seams...
    127 Finding seams, time: 3.09237 sec
    128 Compositing...
    129 Compositing image #1
    130 Multi-band blender, number of bands: 8
    131 Compositing image #2
    132 Compositing image #3
    133 Compositing image #4
    134 Compositing image #5
    135 Compositing image #6
    136 Compositing, time: 13.7766 sec
    137 Finished, total time: 29.4535 sec

    输入图像boat1.jpg、boat2.jpg、boat3.jpg、boat4.jpg、boat5.jpg、boat6.jpg如下(可以在OpenCV安装目录下找到D:OpenCV4.4opencv_extra-master estdatastitching)

     

     

     

    结果图:

    参数warp_type 设置为"plane",效果图如下:

    参数warp_type 设置为"fisheye",效果图如下(旋转90°后):

    其他的参数可以根据自己需要修改,如果要自己完成还需要详细了解拼接步骤再优化。

  • 相关阅读:
    (原)Lazarus 异构平台下多层架构思路、DataSet转换核心代码
    (学)新版动态表单研发,阶段成果3
    (学) 如何将 Oracle 序列 重置 清零 How to reset an Oracle sequence
    (学)XtraReport WebService Print 报错
    (原)三星 i6410 刷机 短信 无法 保存 解决 办法
    (原) Devexpress 汉化包 制作工具、测试程序
    linux下网络配置
    apache自带ab.exe小工具使用小结
    Yii::app()用法小结
    PDO使用小结
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/14182716.html
Copyright © 2011-2022 走看看