/*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.//////M*/#include<iostream>#include<fstream>#include<string>#include"opencv2/opencv_modules.hpp"#include<opencv2/core/utility.hpp>#include"opencv2/highgui.hpp"#include"opencv2/stitching/detail/autocalib.hpp"#include"opencv2/stitching/detail/blenders.hpp"#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/util.hpp"#include"opencv2/stitching/detail/warpers.hpp"#include"opencv2/stitching/warpers.hpp"usingnamespacestd;usingnamespacecv;usingnamespacecv::detail;staticvoidprintUsage(){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"" --try_gpu (yes|no)\n"" Try to use GPU. The default value is 'no'. All default values\n"" are for CPU mode.\n""\nMotion Estimation Flags:\n"" --work_megapix <float>\n"" Resolution for image registration step. The default is 0.6 Mpx.\n"" --features (surf|orb)\n"" Type of features used for images matching. The default is surf.\n"" --match_conf <float>\n"" Confidence for feature matching step. The default is 0.65 for surf and 0.3 for orb.\n"" --conf_thresh <float>\n"" Threshold for two images are from the same panorama confidence.\n"" The default is 1.0.\n"" --ba (reproj|ray)\n"" Bundle adjustment cost function. The default is ray.\n"" --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"" --wave_correct (no|horiz|vert)\n"" Perform wave effect correction. The default is 'horiz'.\n"" --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"" --warp (plane|cylindrical|spherical|fisheye|stereographic|compressedPlaneA2B1|compressedPlaneA1.5B1|compressedPlanePortraitA2B1|compressedPlanePortraitA1.5B1|paniniA2B1|paniniA1.5B1|paniniPortraitA2B1|paniniPortraitA1.5B1|mercator|transverseMercator)\n"" 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"" The default is 'result.jpg'.\n";}// Default command line argsvector<String>img_names;boolpreview=false;booltry_gpu=false;doublework_megapix=0.6;doubleseam_megapix=0.1;doublecompose_megapix=-1;floatconf_thresh=1.f;stringfeatures_type="surf";stringba_cost_func="ray";stringba_refine_mask="xxxxx";booldo_wave_correct=true;WaveCorrectKindwave_correct=detail::WAVE_CORRECT_HORIZ;boolsave_graph=false;std::stringsave_graph_to;stringwarp_type="spherical";intexpos_comp_type=ExposureCompensator::GAIN_BLOCKS;floatmatch_conf=0.3f;stringseam_find_type="gc_color";intblend_type=Blender::MULTI_BAND;floatblend_strength=5;stringresult_name="result.jpg";staticintparseCmdArgs(intargc,char**argv){if(argc==1){printUsage();return-1;}for(inti=1;i<argc;++i){if(string(argv[i])=="--help"||string(argv[i])=="/?"){printUsage();return-1;}elseif(string(argv[i])=="--preview"){preview=true;}elseif(string(argv[i])=="--try_gpu"){if(string(argv[i+1])=="no")try_gpu=false;elseif(string(argv[i+1])=="yes")try_gpu=true;else{cout<<"Bad --try_gpu flag value\n";return-1;}i++;}elseif(string(argv[i])=="--work_megapix"){work_megapix=atof(argv[i+1]);i++;}elseif(string(argv[i])=="--seam_megapix"){seam_megapix=atof(argv[i+1]);i++;}elseif(string(argv[i])=="--compose_megapix"){compose_megapix=atof(argv[i+1]);i++;}elseif(string(argv[i])=="--result"){result_name=argv[i+1];i++;}elseif(string(argv[i])=="--features"){features_type=argv[i+1];if(features_type=="orb")match_conf=0.3f;i++;}elseif(string(argv[i])=="--match_conf"){match_conf=static_cast<float>(atof(argv[i+1]));i++;}elseif(string(argv[i])=="--conf_thresh"){conf_thresh=static_cast<float>(atof(argv[i+1]));i++;}elseif(string(argv[i])=="--ba"){ba_cost_func=argv[i+1];i++;}elseif(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++;}elseif(string(argv[i])=="--wave_correct"){if(string(argv[i+1])=="no")do_wave_correct=false;elseif(string(argv[i+1])=="horiz"){do_wave_correct=true;wave_correct=detail::WAVE_CORRECT_HORIZ;}elseif(string(argv[i+1])=="vert"){do_wave_correct=true;wave_correct=detail::WAVE_CORRECT_VERT;}else{cout<<"Bad --wave_correct flag value\n";return-1;}i++;}elseif(string(argv[i])=="--save_graph"){save_graph=true;save_graph_to=argv[i+1];i++;}elseif(string(argv[i])=="--warp"){warp_type=string(argv[i+1]);i++;}elseif(string(argv[i])=="--expos_comp"){if(string(argv[i+1])=="no")expos_comp_type=ExposureCompensator::NO;elseif(string(argv[i+1])=="gain")expos_comp_type=ExposureCompensator::GAIN;elseif(string(argv[i+1])=="gain_blocks")expos_comp_type=ExposureCompensator::GAIN_BLOCKS;else{cout<<"Bad exposure compensation method\n";return-1;}i++;}elseif(string(argv[i])=="--seam"){if(string(argv[i+1])=="no"||string(argv[i+1])=="voronoi"||string(argv[i+1])=="gc_color"||string(argv[i+1])=="gc_colorgrad"||string(argv[i+1])=="dp_color"||string(argv[i+1])=="dp_colorgrad")seam_find_type=argv[i+1];else{cout<<"Bad seam finding method\n";return-1;}i++;}elseif(string(argv[i])=="--blend"){if(string(argv[i+1])=="no")blend_type=Blender::NO;elseif(string(argv[i+1])=="feather")blend_type=Blender::FEATHER;elseif(string(argv[i+1])=="multiband")blend_type=Blender::MULTI_BAND;else{cout<<"Bad blending method\n";return-1;}i++;}elseif(string(argv[i])=="--blend_strength"){blend_strength=static_cast<float>(atof(argv[i+1]));i++;}elseif(string(argv[i])=="--output"){result_name=argv[i+1];i++;}elseimg_names.push_back(argv[i]);}if(preview){compose_megapix=0.6;}return0;}intmain(intargc,char*argv[]){#if ENABLE_LOGint64app_start_time=getTickCount();#endifcv::setBreakOnError(true);intretval=parseCmdArgs(argc,argv);if(retval)returnretval;// Check if have enough imagesintnum_images=static_cast<int>(img_names.size());if(num_images<2){LOGLN("Need more images");return-1;}doublework_scale=1,seam_scale=1,compose_scale=1;boolis_work_scale_set=false,is_seam_scale_set=false,is_compose_scale_set=false;LOGLN("Finding features...");#if ENABLE_LOGint64t=getTickCount();#endifPtr<FeaturesFinder>finder;if(features_type=="surf"){#ifdef HAVE_OPENCV_NONFREEif(try_gpu&&gpu::getCudaEnabledDeviceCount()>0)finder=newSurfFeaturesFinderGpu();else#endiffinder=newSurfFeaturesFinder();}elseif(features_type=="orb"){finder=newOrbFeaturesFinder();}else{cout<<"Unknown 2D features type: '"<<features_type<<"'.\n";return-1;}Matfull_img,img;vector<ImageFeatures>features(num_images);vector<Mat>images(num_images);vector<Size>full_img_sizes(num_images);doubleseam_work_aspect=1;for(inti=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;}(*finder)(img,features[i]);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();}finder->collectGarbage();full_img.release();img.release();LOGLN("Finding features, time: "<<((getTickCount()-t)/getTickFrequency())<<" sec");LOG("Pairwise matching");#if ENABLE_LOGt=getTickCount();#endifvector<MatchesInfo>pairwise_matches;BestOf2NearestMatchermatcher(try_gpu,match_conf);matcher(features,pairwise_matches);matcher.collectGarbage();LOGLN("Pairwise matching, time: "<<((getTickCount()-t)/getTickFrequency())<<" sec");// Check if we should save matches graphif(save_graph){LOGLN("Saving matches graph...");ofstreamf(save_graph_to.c_str());f<<matchesGraphAsString(img_names,pairwise_matches,conf_thresh);}// Leave only images we are sure are from the same panoramavector<int>indices=leaveBiggestComponent(features,pairwise_matches,conf_thresh);vector<Mat>img_subset;vector<String>img_names_subset;vector<Size>full_img_sizes_subset;for(size_ti=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 imagesnum_images=static_cast<int>(img_names.size());if(num_images<2){LOGLN("Need more images");return-1;}HomographyBasedEstimatorestimator;vector<CameraParams>cameras;if(!estimator(features,pairwise_matches,cameras)){cout<<"Homography estimation failed.\n";return-1;}for(size_ti=0;i<cameras.size();++i){MatR;cameras[i].R.convertTo(R,CV_32F);cameras[i].R=R;LOGLN("Initial intrinsics #"<<indices[i]+1<<":\n"<<cameras[i].K());}Ptr<detail::BundleAdjusterBase>adjuster;if(ba_cost_func=="reproj")adjuster=newdetail::BundleAdjusterReproj();elseif(ba_cost_func=="ray")adjuster=newdetail::BundleAdjusterRay();else{cout<<"Unknown bundle adjustment cost function: '"<<ba_cost_func<<"'.\n";return-1;}adjuster->setConfThresh(conf_thresh);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);if(!(*adjuster)(features,pairwise_matches,cameras)){cout<<"Camera parameters adjusting failed.\n";return-1;}// Find median focal lengthvector<double>focals;for(size_ti=0;i<cameras.size();++i){LOGLN("Camera #"<<indices[i]+1<<":\n"<<cameras[i].K());focals.push_back(cameras[i].focal);}sort(focals.begin(),focals.end());floatwarped_image_scale;if(focals.size()%2==1)warped_image_scale=static_cast<float>(focals[focals.size()/2]);elsewarped_image_scale=static_cast<float>(focals[focals.size()/2-1]+focals[focals.size()/2])*0.5f;if(do_wave_correct){vector<Mat>rmats;for(size_ti=0;i<cameras.size();++i)rmats.push_back(cameras[i].R);waveCorrect(rmats,wave_correct);for(size_ti=0;i<cameras.size();++i)cameras[i].R=rmats[i];}LOGLN("Warping images (auxiliary)... ");#if ENABLE_LOGt=getTickCount();#endifvector<Point>corners(num_images);vector<Mat>masks_warped(num_images);vector<Mat>images_warped(num_images);vector<Size>sizes(num_images);vector<Mat>masks(num_images);// Preapre images masksfor(inti=0;i<num_images;++i){masks[i].create(images[i].size(),CV_8U);masks[i].setTo(Scalar::all(255));}// Warp images and their masksPtr<WarperCreator>warper_creator;#ifdef HAVE_OPENCV_GPUWARPINGif(try_gpu&&gpu::getCudaEnabledDeviceCount()>0){if(warp_type=="plane")warper_creator=newcv::PlaneWarperGpu();elseif(warp_type=="cylindrical")warper_creator=newcv::CylindricalWarperGpu();elseif(warp_type=="spherical")warper_creator=newcv::SphericalWarperGpu();}else#endif{if(warp_type=="plane")warper_creator=newcv::PlaneWarper();elseif(warp_type=="cylindrical")warper_creator=newcv::CylindricalWarper();elseif(warp_type=="spherical")warper_creator=newcv::SphericalWarper();elseif(warp_type=="fisheye")warper_creator=newcv::FisheyeWarper();elseif(warp_type=="stereographic")warper_creator=newcv::StereographicWarper();elseif(warp_type=="compressedPlaneA2B1")warper_creator=newcv::CompressedRectilinearWarper(2,1);elseif(warp_type=="compressedPlaneA1.5B1")warper_creator=newcv::CompressedRectilinearWarper(1.5,1);elseif(warp_type=="compressedPlanePortraitA2B1")warper_creator=newcv::CompressedRectilinearPortraitWarper(2,1);elseif(warp_type=="compressedPlanePortraitA1.5B1")warper_creator=newcv::CompressedRectilinearPortraitWarper(1.5,1);elseif(warp_type=="paniniA2B1")warper_creator=newcv::PaniniWarper(2,1);elseif(warp_type=="paniniA1.5B1")warper_creator=newcv::PaniniWarper(1.5,1);elseif(warp_type=="paniniPortraitA2B1")warper_creator=newcv::PaniniPortraitWarper(2,1);elseif(warp_type=="paniniPortraitA1.5B1")warper_creator=newcv::PaniniPortraitWarper(1.5,1);elseif(warp_type=="mercator")warper_creator=newcv::MercatorWarper();elseif(warp_type=="transverseMercator")warper_creator=newcv::TransverseMercatorWarper();}if(warper_creator.empty()){cout<<"Can't create the following warper '"<<warp_type<<"'\n";return1;}Ptr<RotationWarper>warper=warper_creator->create(static_cast<float>(warped_image_scale*seam_work_aspect));for(inti=0;i<num_images;++i){Mat_<float>K;cameras[i].K().convertTo(K,CV_32F);floatswa=(float)seam_work_aspect;K(0,0)*=swa;K(0,2)*=swa;K(1,1)*=swa;K(1,2)*=swa;corners[i]=warper->warp(images[i],K,cameras[i].R,INTER_LINEAR,BORDER_REFLECT,images_warped[i]);sizes[i]=images_warped[i].size();warper->warp(masks[i],K,cameras[i].R,INTER_NEAREST,BORDER_CONSTANT,masks_warped[i]);}vector<Mat>images_warped_f(num_images);for(inti=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);Ptr<SeamFinder>seam_finder;if(seam_find_type=="no")seam_finder=newdetail::NoSeamFinder();elseif(seam_find_type=="voronoi")seam_finder=newdetail::VoronoiSeamFinder();elseif(seam_find_type=="gc_color"){#ifdef HAVE_OPENCV_GPUif(try_gpu&&gpu::getCudaEnabledDeviceCount()>0)seam_finder=newdetail::GraphCutSeamFinderGpu(GraphCutSeamFinderBase::COST_COLOR);else#endifseam_finder=newdetail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR);}elseif(seam_find_type=="gc_colorgrad"){#ifdef HAVE_OPENCV_GPUif(try_gpu&&gpu::getCudaEnabledDeviceCount()>0)seam_finder=newdetail::GraphCutSeamFinderGpu(GraphCutSeamFinderBase::COST_COLOR_GRAD);else#endifseam_finder=newdetail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR_GRAD);}elseif(seam_find_type=="dp_color")seam_finder=newdetail::DpSeamFinder(DpSeamFinder::COLOR);elseif(seam_find_type=="dp_colorgrad")seam_finder=newdetail::DpSeamFinder(DpSeamFinder::COLOR_GRAD);if(seam_finder.empty()){cout<<"Can't create the following seam finder '"<<seam_find_type<<"'\n";return1;}seam_finder->find(images_warped_f,corners,masks_warped);// Release unused memoryimages.clear();images_warped.clear();images_warped_f.clear();masks.clear();LOGLN("Compositing...");#if ENABLE_LOGt=getTickCount();#endifMatimg_warped,img_warped_s;Matdilated_mask,seam_mask,mask,mask_warped;Ptr<Blender>blender;//double compose_seam_aspect = 1;doublecompose_work_aspect=1;for(intimg_idx=0;img_idx<num_images;++img_idx){LOGLN("Compositing image #"<<indices[img_idx]+1);// Read image and resize it if necessaryfull_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//compose_seam_aspect = compose_scale / seam_scale;compose_work_aspect=compose_scale/work_scale;// Update warped image scalewarped_image_scale*=static_cast<float>(compose_work_aspect);warper=warper_creator->create(warped_image_scale);// Update corners and sizesfor(inti=0;i<num_images;++i){// Update intrinsicscameras[i].focal*=compose_work_aspect;cameras[i].ppx*=compose_work_aspect;cameras[i].ppy*=compose_work_aspect;// Update corner and sizeSizesz=full_img_sizes[i];if(std::abs(compose_scale-1)>1e-1){sz.width=cvRound(full_img_sizes[i].width*compose_scale);sz.height=cvRound(full_img_sizes[i].height*compose_scale);}MatK;cameras[i].K().convertTo(K,CV_32F);Rectroi=warper->warpRoi(sz,K,cameras[i].R);corners[i]=roi.tl();sizes[i]=roi.size();}}if(abs(compose_scale-1)>1e-1)resize(full_img,img,Size(),compose_scale,compose_scale);elseimg=full_img;full_img.release();Sizeimg_size=img.size();MatK;cameras[img_idx].K().convertTo(K,CV_32F);// Warp the current imagewarper->warp(img,K,cameras[img_idx].R,INTER_LINEAR,BORDER_REFLECT,img_warped);// Warp the current image maskmask.create(img_size,CV_8U);mask.setTo(Scalar::all(255));warper->warp(mask,K,cameras[img_idx].R,INTER_NEAREST,BORDER_CONSTANT,mask_warped);// Compensate exposurecompensator->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;if(blender.empty()){blender=Blender::createDefault(blend_type,try_gpu);Sizedst_sz=resultRoi(corners,sizes).size();floatblend_width=sqrt(static_cast<float>(dst_sz.area()))*blend_strength/100.f;if(blend_width<1.f)blender=Blender::createDefault(Blender::NO,try_gpu);elseif(blend_type==Blender::MULTI_BAND){MultiBandBlender*mb=dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender));mb->setNumBands(static_cast<int>(ceil(log(blend_width)/log(2.))-1.));LOGLN("Multi-band blender, number of bands: "<<mb->numBands());}elseif(blend_type==Blender::FEATHER){FeatherBlender*fb=dynamic_cast<FeatherBlender*>(static_cast<Blender*>(blender));fb->setSharpness(1.f/blend_width);LOGLN("Feather blender, sharpness: "<<fb->sharpness());}blender->prepare(corners,sizes);}// Blend the current imageblender->feed(img_warped_s,mask_warped,corners[img_idx]);}Matresult,result_mask;blender->blend(result,result_mask);LOGLN("Compositing, time: "<<((getTickCount()-t)/getTickFrequency())<<" sec");imwrite(result_name,result);LOGLN("Finished, total time: "<<((getTickCount()-app_start_time)/getTickFrequency())<<" sec");return0;}