Path: blob/master/modules/stitching/src/stitcher.cpp
16337 views
/*M///////////////////////////////////////////////////////////////////////////////////////1//2// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.3//4// By downloading, copying, installing or using the software you agree to this license.5// If you do not agree to this license, do not download, install,6// copy or use the software.7//8//9// License Agreement10// For Open Source Computer Vision Library11//12// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.13// Copyright (C) 2009, Willow Garage Inc., all rights reserved.14// Third party copyrights are property of their respective owners.15//16// Redistribution and use in source and binary forms, with or without modification,17// are permitted provided that the following conditions are met:18//19// * Redistribution's of source code must retain the above copyright notice,20// this list of conditions and the following disclaimer.21//22// * Redistribution's in binary form must reproduce the above copyright notice,23// this list of conditions and the following disclaimer in the documentation24// and/or other materials provided with the distribution.25//26// * The name of the copyright holders may not be used to endorse or promote products27// derived from this software without specific prior written permission.28//29// This software is provided by the copyright holders and contributors "as is" and30// any express or implied warranties, including, but not limited to, the implied31// warranties of merchantability and fitness for a particular purpose are disclaimed.32// In no event shall the Intel Corporation or contributors be liable for any direct,33// indirect, incidental, special, exemplary, or consequential damages34// (including, but not limited to, procurement of substitute goods or services;35// loss of use, data, or profits; or business interruption) however caused36// and on any theory of liability, whether in contract, strict liability,37// or tort (including negligence or otherwise) arising in any way out of38// the use of this software, even if advised of the possibility of such damage.39//40//M*/4142#include "precomp.hpp"4344namespace cv {4546Stitcher Stitcher::createDefault(bool try_use_gpu)47{48Stitcher stitcher;49stitcher.setRegistrationResol(0.6);50stitcher.setSeamEstimationResol(0.1);51stitcher.setCompositingResol(ORIG_RESOL);52stitcher.setPanoConfidenceThresh(1);53stitcher.setWaveCorrection(true);54stitcher.setWaveCorrectKind(detail::WAVE_CORRECT_HORIZ);55stitcher.setFeaturesMatcher(makePtr<detail::BestOf2NearestMatcher>(try_use_gpu));56stitcher.setBundleAdjuster(makePtr<detail::BundleAdjusterRay>());5758#ifdef HAVE_OPENCV_CUDALEGACY59if (try_use_gpu && cuda::getCudaEnabledDeviceCount() > 0)60{61#ifdef HAVE_OPENCV_XFEATURES2D62stitcher.setFeaturesFinder(makePtr<detail::SurfFeaturesFinderGpu>());63#else64stitcher.setFeaturesFinder(makePtr<detail::OrbFeaturesFinder>());65#endif66stitcher.setWarper(makePtr<SphericalWarperGpu>());67stitcher.setSeamFinder(makePtr<detail::GraphCutSeamFinderGpu>());68}69else70#endif71{72#ifdef HAVE_OPENCV_XFEATURES2D73stitcher.setFeaturesFinder(makePtr<detail::SurfFeaturesFinder>());74#else75stitcher.setFeaturesFinder(makePtr<detail::OrbFeaturesFinder>());76#endif77stitcher.setWarper(makePtr<SphericalWarper>());78stitcher.setSeamFinder(makePtr<detail::GraphCutSeamFinder>(detail::GraphCutSeamFinderBase::COST_COLOR));79}8081stitcher.setExposureCompensator(makePtr<detail::BlocksGainCompensator>());82stitcher.setBlender(makePtr<detail::MultiBandBlender>(try_use_gpu));8384stitcher.work_scale_ = 1;85stitcher.seam_scale_ = 1;86stitcher.seam_work_aspect_ = 1;87stitcher.warped_image_scale_ = 1;8889return stitcher;90}919293Ptr<Stitcher> Stitcher::create(Mode mode, bool try_use_gpu)94{95Stitcher stit = createDefault(try_use_gpu);96Ptr<Stitcher> stitcher = makePtr<Stitcher>(stit);9798switch (mode)99{100case PANORAMA: // PANORAMA is the default101// already setup102break;103104case SCANS:105stitcher->setWaveCorrection(false);106stitcher->setFeaturesMatcher(makePtr<detail::AffineBestOf2NearestMatcher>(false, try_use_gpu));107stitcher->setBundleAdjuster(makePtr<detail::BundleAdjusterAffinePartial>());108stitcher->setWarper(makePtr<AffineWarper>());109stitcher->setExposureCompensator(makePtr<detail::NoExposureCompensator>());110break;111112default:113CV_Error(Error::StsBadArg, "Invalid stitching mode. Must be one of Stitcher::Mode");114break;115}116117return stitcher;118}119120121Stitcher::Status Stitcher::estimateTransform(InputArrayOfArrays images)122{123CV_INSTRUMENT_REGION();124125return estimateTransform(images, std::vector<std::vector<Rect> >());126}127128129Stitcher::Status Stitcher::estimateTransform(InputArrayOfArrays images, const std::vector<std::vector<Rect> > &rois)130{131CV_INSTRUMENT_REGION();132133images.getUMatVector(imgs_);134rois_ = rois;135136Status status;137138if ((status = matchImages()) != OK)139return status;140141if ((status = estimateCameraParams()) != OK)142return status;143144return OK;145}146147148149Stitcher::Status Stitcher::composePanorama(OutputArray pano)150{151CV_INSTRUMENT_REGION();152153return composePanorama(std::vector<UMat>(), pano);154}155156157Stitcher::Status Stitcher::composePanorama(InputArrayOfArrays images, OutputArray pano)158{159CV_INSTRUMENT_REGION();160161LOGLN("Warping images (auxiliary)... ");162163std::vector<UMat> imgs;164images.getUMatVector(imgs);165if (!imgs.empty())166{167CV_Assert(imgs.size() == imgs_.size());168169UMat img;170seam_est_imgs_.resize(imgs.size());171172for (size_t i = 0; i < imgs.size(); ++i)173{174imgs_[i] = imgs[i];175resize(imgs[i], img, Size(), seam_scale_, seam_scale_, INTER_LINEAR_EXACT);176seam_est_imgs_[i] = img.clone();177}178179std::vector<UMat> seam_est_imgs_subset;180std::vector<UMat> imgs_subset;181182for (size_t i = 0; i < indices_.size(); ++i)183{184imgs_subset.push_back(imgs_[indices_[i]]);185seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]);186}187188seam_est_imgs_ = seam_est_imgs_subset;189imgs_ = imgs_subset;190}191192UMat pano_;193194#if ENABLE_LOG195int64 t = getTickCount();196#endif197198std::vector<Point> corners(imgs_.size());199std::vector<UMat> masks_warped(imgs_.size());200std::vector<UMat> images_warped(imgs_.size());201std::vector<Size> sizes(imgs_.size());202std::vector<UMat> masks(imgs_.size());203204// Prepare image masks205for (size_t i = 0; i < imgs_.size(); ++i)206{207masks[i].create(seam_est_imgs_[i].size(), CV_8U);208masks[i].setTo(Scalar::all(255));209}210211// Warp images and their masks212Ptr<detail::RotationWarper> w = warper_->create(float(warped_image_scale_ * seam_work_aspect_));213for (size_t i = 0; i < imgs_.size(); ++i)214{215Mat_<float> K;216cameras_[i].K().convertTo(K, CV_32F);217K(0,0) *= (float)seam_work_aspect_;218K(0,2) *= (float)seam_work_aspect_;219K(1,1) *= (float)seam_work_aspect_;220K(1,2) *= (float)seam_work_aspect_;221222corners[i] = w->warp(seam_est_imgs_[i], K, cameras_[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);223sizes[i] = images_warped[i].size();224225w->warp(masks[i], K, cameras_[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);226}227228229LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");230231// Compensate exposure before finding seams232exposure_comp_->feed(corners, images_warped, masks_warped);233for (size_t i = 0; i < imgs_.size(); ++i)234exposure_comp_->apply(int(i), corners[i], images_warped[i], masks_warped[i]);235236// Find seams237std::vector<UMat> images_warped_f(imgs_.size());238for (size_t i = 0; i < imgs_.size(); ++i)239images_warped[i].convertTo(images_warped_f[i], CV_32F);240seam_finder_->find(images_warped_f, corners, masks_warped);241242// Release unused memory243seam_est_imgs_.clear();244images_warped.clear();245images_warped_f.clear();246masks.clear();247248LOGLN("Compositing...");249#if ENABLE_LOG250t = getTickCount();251#endif252253UMat img_warped, img_warped_s;254UMat dilated_mask, seam_mask, mask, mask_warped;255256//double compose_seam_aspect = 1;257double compose_work_aspect = 1;258bool is_blender_prepared = false;259260double compose_scale = 1;261bool is_compose_scale_set = false;262263std::vector<detail::CameraParams> cameras_scaled(cameras_);264265UMat full_img, img;266for (size_t img_idx = 0; img_idx < imgs_.size(); ++img_idx)267{268LOGLN("Compositing image #" << indices_[img_idx] + 1);269#if ENABLE_LOG270int64 compositing_t = getTickCount();271#endif272273// Read image and resize it if necessary274full_img = imgs_[img_idx];275if (!is_compose_scale_set)276{277if (compose_resol_ > 0)278compose_scale = std::min(1.0, std::sqrt(compose_resol_ * 1e6 / full_img.size().area()));279is_compose_scale_set = true;280281// Compute relative scales282//compose_seam_aspect = compose_scale / seam_scale_;283compose_work_aspect = compose_scale / work_scale_;284285// Update warped image scale286float warp_scale = static_cast<float>(warped_image_scale_ * compose_work_aspect);287w = warper_->create(warp_scale);288289// Update corners and sizes290for (size_t i = 0; i < imgs_.size(); ++i)291{292// Update intrinsics293cameras_scaled[i].ppx *= compose_work_aspect;294cameras_scaled[i].ppy *= compose_work_aspect;295cameras_scaled[i].focal *= compose_work_aspect;296297// Update corner and size298Size sz = full_img_sizes_[i];299if (std::abs(compose_scale - 1) > 1e-1)300{301sz.width = cvRound(full_img_sizes_[i].width * compose_scale);302sz.height = cvRound(full_img_sizes_[i].height * compose_scale);303}304305Mat K;306cameras_scaled[i].K().convertTo(K, CV_32F);307Rect roi = w->warpRoi(sz, K, cameras_scaled[i].R);308corners[i] = roi.tl();309sizes[i] = roi.size();310}311}312if (std::abs(compose_scale - 1) > 1e-1)313{314#if ENABLE_LOG315int64 resize_t = getTickCount();316#endif317resize(full_img, img, Size(), compose_scale, compose_scale, INTER_LINEAR_EXACT);318LOGLN(" resize time: " << ((getTickCount() - resize_t) / getTickFrequency()) << " sec");319}320else321img = full_img;322full_img.release();323Size img_size = img.size();324325LOGLN(" after resize time: " << ((getTickCount() - compositing_t) / getTickFrequency()) << " sec");326327Mat K;328cameras_scaled[img_idx].K().convertTo(K, CV_32F);329330#if ENABLE_LOG331int64 pt = getTickCount();332#endif333// Warp the current image334w->warp(img, K, cameras_[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);335LOGLN(" warp the current image: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");336#if ENABLE_LOG337pt = getTickCount();338#endif339340// Warp the current image mask341mask.create(img_size, CV_8U);342mask.setTo(Scalar::all(255));343w->warp(mask, K, cameras_[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);344LOGLN(" warp the current image mask: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");345#if ENABLE_LOG346pt = getTickCount();347#endif348349// Compensate exposure350exposure_comp_->apply((int)img_idx, corners[img_idx], img_warped, mask_warped);351LOGLN(" compensate exposure: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");352#if ENABLE_LOG353pt = getTickCount();354#endif355356img_warped.convertTo(img_warped_s, CV_16S);357img_warped.release();358img.release();359mask.release();360361// Make sure seam mask has proper size362dilate(masks_warped[img_idx], dilated_mask, Mat());363resize(dilated_mask, seam_mask, mask_warped.size(), 0, 0, INTER_LINEAR_EXACT);364365bitwise_and(seam_mask, mask_warped, mask_warped);366367LOGLN(" other: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");368#if ENABLE_LOG369pt = getTickCount();370#endif371372if (!is_blender_prepared)373{374blender_->prepare(corners, sizes);375is_blender_prepared = true;376}377378LOGLN(" other2: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");379380LOGLN(" feed...");381#if ENABLE_LOG382int64 feed_t = getTickCount();383#endif384// Blend the current image385blender_->feed(img_warped_s, mask_warped, corners[img_idx]);386LOGLN(" feed time: " << ((getTickCount() - feed_t) / getTickFrequency()) << " sec");387LOGLN("Compositing ## time: " << ((getTickCount() - compositing_t) / getTickFrequency()) << " sec");388}389390#if ENABLE_LOG391int64 blend_t = getTickCount();392#endif393UMat result, result_mask;394blender_->blend(result, result_mask);395LOGLN("blend time: " << ((getTickCount() - blend_t) / getTickFrequency()) << " sec");396397LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");398399// Preliminary result is in CV_16SC3 format, but all values are in [0,255] range,400// so convert it to avoid user confusing401result.convertTo(pano, CV_8U);402403return OK;404}405406407Stitcher::Status Stitcher::stitch(InputArrayOfArrays images, OutputArray pano)408{409CV_INSTRUMENT_REGION();410411Status status = estimateTransform(images);412if (status != OK)413return status;414return composePanorama(pano);415}416417418Stitcher::Status Stitcher::stitch(InputArrayOfArrays images, const std::vector<std::vector<Rect> > &rois, OutputArray pano)419{420CV_INSTRUMENT_REGION();421422Status status = estimateTransform(images, rois);423if (status != OK)424return status;425return composePanorama(pano);426}427428429Stitcher::Status Stitcher::matchImages()430{431if ((int)imgs_.size() < 2)432{433LOGLN("Need more images");434return ERR_NEED_MORE_IMGS;435}436437work_scale_ = 1;438seam_work_aspect_ = 1;439seam_scale_ = 1;440bool is_work_scale_set = false;441bool is_seam_scale_set = false;442UMat full_img, img;443features_.resize(imgs_.size());444seam_est_imgs_.resize(imgs_.size());445full_img_sizes_.resize(imgs_.size());446447LOGLN("Finding features...");448#if ENABLE_LOG449int64 t = getTickCount();450#endif451452std::vector<UMat> feature_find_imgs(imgs_.size());453std::vector<std::vector<Rect> > feature_find_rois(rois_.size());454455for (size_t i = 0; i < imgs_.size(); ++i)456{457full_img = imgs_[i];458full_img_sizes_[i] = full_img.size();459460if (registr_resol_ < 0)461{462img = full_img;463work_scale_ = 1;464is_work_scale_set = true;465}466else467{468if (!is_work_scale_set)469{470work_scale_ = std::min(1.0, std::sqrt(registr_resol_ * 1e6 / full_img.size().area()));471is_work_scale_set = true;472}473resize(full_img, img, Size(), work_scale_, work_scale_, INTER_LINEAR_EXACT);474}475if (!is_seam_scale_set)476{477seam_scale_ = std::min(1.0, std::sqrt(seam_est_resol_ * 1e6 / full_img.size().area()));478seam_work_aspect_ = seam_scale_ / work_scale_;479is_seam_scale_set = true;480}481482if (rois_.empty())483feature_find_imgs[i] = img;484else485{486feature_find_rois[i].resize(rois_[i].size());487for (size_t j = 0; j < rois_[i].size(); ++j)488{489Point tl(cvRound(rois_[i][j].x * work_scale_), cvRound(rois_[i][j].y * work_scale_));490Point br(cvRound(rois_[i][j].br().x * work_scale_), cvRound(rois_[i][j].br().y * work_scale_));491feature_find_rois[i][j] = Rect(tl, br);492}493feature_find_imgs[i] = img;494}495features_[i].img_idx = (int)i;496LOGLN("Features in image #" << i+1 << ": " << features_[i].keypoints.size());497498resize(full_img, img, Size(), seam_scale_, seam_scale_, INTER_LINEAR_EXACT);499seam_est_imgs_[i] = img.clone();500}501502// find features possibly in parallel503if (rois_.empty())504(*features_finder_)(feature_find_imgs, features_);505else506(*features_finder_)(feature_find_imgs, features_, feature_find_rois);507508// Do it to save memory509features_finder_->collectGarbage();510full_img.release();511img.release();512feature_find_imgs.clear();513feature_find_rois.clear();514515LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");516517LOG("Pairwise matching");518#if ENABLE_LOG519t = getTickCount();520#endif521(*features_matcher_)(features_, pairwise_matches_, matching_mask_);522features_matcher_->collectGarbage();523LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");524525// Leave only images we are sure are from the same panorama526indices_ = detail::leaveBiggestComponent(features_, pairwise_matches_, (float)conf_thresh_);527std::vector<UMat> seam_est_imgs_subset;528std::vector<UMat> imgs_subset;529std::vector<Size> full_img_sizes_subset;530for (size_t i = 0; i < indices_.size(); ++i)531{532imgs_subset.push_back(imgs_[indices_[i]]);533seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]);534full_img_sizes_subset.push_back(full_img_sizes_[indices_[i]]);535}536seam_est_imgs_ = seam_est_imgs_subset;537imgs_ = imgs_subset;538full_img_sizes_ = full_img_sizes_subset;539540if ((int)imgs_.size() < 2)541{542LOGLN("Need more images");543return ERR_NEED_MORE_IMGS;544}545546return OK;547}548549550Stitcher::Status Stitcher::estimateCameraParams()551{552/* TODO OpenCV ABI 4.x553get rid of this dynamic_cast hack and use estimator_554*/555Ptr<detail::Estimator> estimator;556if (dynamic_cast<detail::AffineBestOf2NearestMatcher*>(features_matcher_.get()))557estimator = makePtr<detail::AffineBasedEstimator>();558else559estimator = makePtr<detail::HomographyBasedEstimator>();560561if (!(*estimator)(features_, pairwise_matches_, cameras_))562return ERR_HOMOGRAPHY_EST_FAIL;563564for (size_t i = 0; i < cameras_.size(); ++i)565{566Mat R;567cameras_[i].R.convertTo(R, CV_32F);568cameras_[i].R = R;569//LOGLN("Initial intrinsic parameters #" << indices_[i] + 1 << ":\n " << cameras_[i].K());570}571572bundle_adjuster_->setConfThresh(conf_thresh_);573if (!(*bundle_adjuster_)(features_, pairwise_matches_, cameras_))574return ERR_CAMERA_PARAMS_ADJUST_FAIL;575576// Find median focal length and use it as final image scale577std::vector<double> focals;578for (size_t i = 0; i < cameras_.size(); ++i)579{580//LOGLN("Camera #" << indices_[i] + 1 << ":\n" << cameras_[i].K());581focals.push_back(cameras_[i].focal);582}583584std::sort(focals.begin(), focals.end());585if (focals.size() % 2 == 1)586warped_image_scale_ = static_cast<float>(focals[focals.size() / 2]);587else588warped_image_scale_ = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;589590if (do_wave_correct_)591{592std::vector<Mat> rmats;593for (size_t i = 0; i < cameras_.size(); ++i)594rmats.push_back(cameras_[i].R.clone());595detail::waveCorrect(rmats, wave_correct_kind_);596for (size_t i = 0; i < cameras_.size(); ++i)597cameras_[i].R = rmats[i];598}599600return OK;601}602603604Ptr<Stitcher> createStitcher(bool try_use_gpu)605{606CV_INSTRUMENT_REGION();607608return Stitcher::create(Stitcher::PANORAMA, try_use_gpu);609}610611Ptr<Stitcher> createStitcherScans(bool try_use_gpu)612{613CV_INSTRUMENT_REGION();614615return Stitcher::create(Stitcher::SCANS, try_use_gpu);616}617} // namespace cv618619620