Stitcher类与detail命名空间

OpenCV提供了高级别的函数封装在Stitcher类中,使用很方便,不用考虑太多的细节。

低级别函数封装在detail命名空间中,展示了opencv算法实现的很多步骤和细节,使熟悉如下拼接流水线的用户,方便自己定制。

可见OpenCV图像拼接模块的实现是十分精密和复杂的,拼接的结果很完善,但同时也是费时的,完全不能够实现实时应用。

我在研究detail源码时,由于水平有限,并不能自由灵活地对各种部件取其所需,取舍随意。

官方提供的stitching和stitching_detailed使用示例,分别是高级别和低级别封装这两种方式正确地使用示例。两种结果产生的拼接结果相同,后者却可以允许用户,在参数变量初始化时,选择各项算法。如下所示:

这涉及到以下算法流程:

  1. 命令行调用程序,输入源图像以及程序的参数

  2. 特征点检测,判断是使用surf还是orb,默认是surf。

  3. 对图像的特征点进行匹配,使用最近邻和次近邻方法, 
    将两个最优的匹配的置信度保存下来。

  4. 对图像进行排序以及将置信度高的图像保存到同一个集合中, 
    删除置信度比较低的图像间的匹配,得到能正确匹配的图像序列。 
    这样将置信度高于门限的所有匹配合并到一个集合中。

  5. 对所有图像进行相机参数粗略估计,然后求出旋转矩阵

  6. 使用光束平均法进一步精准的估计出旋转矩阵。

  7. 波形校正,水平或者垂直

  8. 拼接

  9. 融合,多频段融合,光照补偿,


Stitcher类使用示例

 
#include <iostream>#include <fstream>#include "opencv2/highgui/highgui.hpp"#include "opencv2/stitching/stitcher.hpp"#include<io.h>using namespace std;using namespace cv;bool try_use_gpu = false;vector<Mat> imgs;string result_name = "result.jpg";void getFiles(string path, vector<string>& files){//文件句柄long hFile = 0;//文件信息struct _finddata_t fileinfo;string p;if ((hFile = _findfirst(p.assign(path).append("/*").c_str(), &fileinfo)) != -1){do{//如果是目录,迭代之//如果不是,加入列表if ((fileinfo.attrib & _A_SUBDIR)){if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)getFiles(p.assign(path).append("/").append(fileinfo.name), files);}else{files.push_back(p.assign(path).append("/").append(fileinfo.name));}} while (_findnext(hFile, &fileinfo) == 0);_findclose(hFile);}}int main(int argc, char* argv[]){vector<string> filesName;getFiles("E:/workspace/iamge/dataset/",filesName);for (string fileName:filesName){Mat img = imread(fileName,1);imgs.push_back(img);}Mat pano;Stitcher stitcher = Stitcher::createDefault(try_use_gpu);Stitcher::Status status = stitcher.stitch(imgs, pano);if (status != Stitcher::OK){cout << "Can't stitch images, error code = " << int(status) << endl;return -1;}imwrite(result_name, pano);imshow(result_name,pano);waitKey(0);return 0;}

其中的getfiles()函数的功能是获取一个目录下的所有文件地址。这使得可以在windows下批量的读取图像的地址。


stitching_detailed使用示例

 
#include <iostream>#include <fstream>#include <string>#include "opencv2/opencv_modules.hpp"#include "opencv2/highgui/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"using namespace std;using namespace cv;using namespace cv::detail;//#define ENABLE_LOG 1// Default command line argsvector<string> img_names;bool preview = false;bool try_gpu = true;double work_megapix = 0.6;double seam_megapix = 0.1;double compose_megapix = -1;float conf_thresh = 1.f;string features_type = "surf";string ba_cost_func = "ray";string ba_refine_mask = "xxxxx";bool do_wave_correct = true;WaveCorrectKind wave_correct = detail::WAVE_CORRECT_HORIZ;bool save_graph = false;std::string save_graph_to;string warp_type = "spherical";int expos_comp_type = ExposureCompensator::GAIN_BLOCKS;float match_conf = 0.3f;string seam_find_type = "gc_color";int blend_type = Blender::MULTI_BAND;float blend_strength = 5;string result_name = "result.jpg";int main(int argc, char* argv[]){//读入图像double ttt = getTickCount();img_names.push_back("E:/workspace/iamge/dataset/yard1.jpg");img_names.push_back("E:/workspace/iamge/dataset/yard2.jpg");img_names.push_back("E:/workspace/iamge/dataset/yard3.jpg");img_names.push_back("E:/workspace/iamge/dataset/yard4.jpg");#if ENABLE_LOGint64 app_start_time = getTickCount();#endifcv::setBreakOnError(true);/*int retval = parseCmdArgs(argc, argv);if (retval)return retval;*/// Check if have enough imagesint num_images = static_cast<int>(img_names.size());if (num_images < 2){LOGLN("Need more images");return -1;}double work_scale = 1, seam_scale = 1, compose_scale = 1;bool is_work_scale_set = false, is_seam_scale_set = false, is_compose_scale_set = false;LOGLN("Finding features...");#if ENABLE_LOGint64 t = getTickCount();#endifPtr<FeaturesFinder> finder;if (features_type == "surf"){#if defined(HAVE_OPENCV_NONFREE) && defined(HAVE_OPENCV_GPU)if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0)finder = new SurfFeaturesFinderGpu();else#endiffinder = new SurfFeaturesFinder();}else if (features_type == "orb"){finder = new OrbFeaturesFinder();}else{cout << "Unknown 2D features type: '" << features_type << "'.\n";return -1;}Mat full_img, img;vector<ImageFeatures> features(num_images);vector<Mat> images(num_images);vector<Size> full_img_sizes(num_images);double seam_work_aspect = 1;for (int i = 0; i < num_images; ++i){full_img = imread(img_names[i]);full_img_sizes[i] = full_img.size();if (full_img.empty()){LOGLN("Can't open image " << img_names[i]);return -1;}if (work_megapix < 0){img = full_img;work_scale = 1;is_work_scale_set = true;}else{if (!is_work_scale_set){work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area()));is_work_scale_set = true;}resize(full_img, img, Size(), work_scale, work_scale);}if (!is_seam_scale_set){seam_scale = min(1.0, sqrt(seam_megapix * 1e6 / full_img.size().area()));seam_work_aspect = seam_scale / work_scale;is_seam_scale_set = true;}(*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;BestOf2NearestMatcher matcher(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...");ofstream f(save_graph_to.c_str());f << matchesGraphAsString(img_names, pairwise_matches, conf_thresh);}// Leave only images we are sure are from the same 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_t i = 0; i < indices.size(); ++i){img_names_subset.push_back(img_names[indices[i]]);img_subset.push_back(images[indices[i]]);full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);}images = img_subset;img_names = img_names_subset;full_img_sizes = full_img_sizes_subset;// Check if we still have enough imagesnum_images = static_cast<int>(img_names.size());if (num_images < 2){LOGLN("Need more images");return -1;}HomographyBasedEstimator estimator;vector<CameraParams> cameras;estimator(features, pairwise_matches, cameras);for (size_t i = 0; i < cameras.size(); ++i){Mat R;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 = new detail::BundleAdjusterReproj();else if (ba_cost_func == "ray") adjuster = new detail::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);(*adjuster)(features, pairwise_matches, cameras);// Find median focal lengthvector<double> focals;for (size_t i = 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());float warped_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_t i = 0; i < cameras.size(); ++i)rmats.push_back(cameras[i].R.clone());waveCorrect(rmats, wave_correct);for (size_t i = 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 (int i = 0; i < num_images; ++i){masks[i].create(images[i].size(), CV_8U);masks[i].setTo(Scalar::all(255));}// Warp images and their masksPtr<WarperCreator> warper_creator;#if defined(HAVE_OPENCV_GPU)if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0){if (warp_type == "plane") warper_creator = new cv::PlaneWarperGpu();else if (warp_type == "cylindrical") warper_creator = new cv::CylindricalWarperGpu();else if (warp_type == "spherical") warper_creator = new cv::SphericalWarperGpu();}else#endif{if (warp_type == "plane") warper_creator = new cv::PlaneWarper();else if (warp_type == "cylindrical") warper_creator = new cv::CylindricalWarper();else if (warp_type == "spherical") warper_creator = new cv::SphericalWarper();else if (warp_type == "fisheye") warper_creator = new cv::FisheyeWarper();else if (warp_type == "stereographic") warper_creator = new cv::StereographicWarper();else if (warp_type == "compressedPlaneA2B1") warper_creator = new cv::CompressedRectilinearWarper(2, 1);else if (warp_type == "compressedPlaneA1.5B1") warper_creator = new cv::CompressedRectilinearWarper(1.5, 1);else if (warp_type == "compressedPlanePortraitA2B1") warper_creator = new cv::CompressedRectilinearPortraitWarper(2, 1);else if (warp_type == "compressedPlanePortraitA1.5B1") warper_creator = new cv::CompressedRectilinearPortraitWarper(1.5, 1);else if (warp_type == "paniniA2B1") warper_creator = new cv::PaniniWarper(2, 1);else if (warp_type == "paniniA1.5B1") warper_creator = new cv::PaniniWarper(1.5, 1);else if (warp_type == "paniniPortraitA2B1") warper_creator = new cv::PaniniPortraitWarper(2, 1);else if (warp_type == "paniniPortraitA1.5B1") warper_creator = new cv::PaniniPortraitWarper(1.5, 1);else if (warp_type == "mercator") warper_creator = new cv::MercatorWarper();else if (warp_type == "transverseMercator") warper_creator = new cv::TransverseMercatorWarper();}if (warper_creator.empty()){cout << "Can't create the following warper '" << warp_type << "'\n";return 1;}Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale * seam_work_aspect));for (int i = 0; i < num_images; ++i){Mat_<float> K;cameras[i].K().convertTo(K, CV_32F);float swa = (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 (int i = 0; i < num_images; ++i)images_warped[i].convertTo(images_warped_f[i], CV_32F);LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");Ptr<ExposureCompensator> compensator = ExposureCompensator::createDefault(expos_comp_type);compensator->feed(corners, images_warped, masks_warped);Ptr<SeamFinder> seam_finder;if (seam_find_type == "no")seam_finder = new detail::NoSeamFinder();else if (seam_find_type == "voronoi")seam_finder = new detail::VoronoiSeamFinder();else if (seam_find_type == "gc_color"){#if defined(HAVE_OPENCV_GPU)if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0)seam_finder = new detail::GraphCutSeamFinderGpu(GraphCutSeamFinderBase::COST_COLOR);else#endifseam_finder = new detail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR);}else if (seam_find_type == "gc_colorgrad"){#if defined(HAVE_OPENCV_GPU)if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0)seam_finder = new detail::GraphCutSeamFinderGpu(GraphCutSeamFinderBase::COST_COLOR_GRAD);else#endifseam_finder = new detail::GraphCutSeamFinder(GraphCutSeamFinderBase::COST_COLOR_GRAD);}else if (seam_find_type == "dp_color")seam_finder = new detail::DpSeamFinder(DpSeamFinder::COLOR);else if (seam_find_type == "dp_colorgrad")seam_finder = new detail::DpSeamFinder(DpSeamFinder::COLOR_GRAD);if (seam_finder.empty()){cout << "Can't create the following seam finder '" << seam_find_type << "'\n";return 1;}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();#endifMat img_warped, img_warped_s;Mat dilated_mask, seam_mask, mask, mask_warped;Ptr<Blender> blender;//double compose_seam_aspect = 1;double compose_work_aspect = 1;for (int img_idx = 0; img_idx < num_images; ++img_idx){LOGLN("Compositing image #" << indices[img_idx]+1);// Read image and resize it if 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 (int i = 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 sizeSize sz = 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);}Mat K;cameras[i].K().convertTo(K, CV_32F);Rect roi = 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();Size img_size = img.size();Mat K;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);Size dst_sz = resultRoi(corners, sizes).size();float blend_width = sqrt(static_cast<float>(dst_sz.area())) * blend_strength / 100.f;if (blend_width < 1.f)blender = Blender::createDefault(Blender::NO, try_gpu);else if (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());}else if (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]);}Mat result, result_mask;blender->blend(result, result_mask);LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");imwrite(result_name, result);result.convertTo(result,CV_8UC1);imshow("stitch",result);ttt = ((double)getTickCount() - ttt) / getTickFrequency();cout << "总的拼接时间:" << ttt << endl;waitKey(0);LOGLN("Finished, total time: " << ((getTickCount() - app_start_time) / getTickFrequency()) << " sec");return 0;}

拼接结果

输入4张图像,每张分辨率为327*245,总的拼接时间为9.25s。

OpenCV图像拼接之Stitching和Stitching_detailed相关推荐

  1. OPenCV 图像拼接之------stitching和stitching_detailed

    Stitcher类与detail命名空间 OpenCV提供了高级别的函数封装在Stitcher类中,使用很方便,不用考虑太多的细节. 低级别函数封装在detail命名空间中,展示了opencv算法实现 ...

  2. 图像拼接(十):OPenCV stitching和stitching_detailed

    #Stitcher类与detail命名空间 OpenCV提供了高级别的函数封装在Stitcher类中,使用很方便,不用考虑太多的细节. 低级别函数封装在detail命名空间中,展示了OpenCV算法实 ...

  3. OpenCV图像拼接-Stitcher类-Stitching detailed使用与参数介绍

    关于OpenCV图像拼接的方法,如果不熟悉的话,可以先看看我整理的如下四篇博客: OpenCV常用图像拼接方法(一):直接拼接(硬拼) OpenCV常用图像拼接方法(二):基于模板匹配拼接 OpenC ...

  4. Opencv 图像拼接与融合简单方法Stitcher

    Opencv 图像拼接与融合简单方法Stitcher 官方示例 使用方法 运行效果 官方示例 #include "opencv2/imgcodecs.hpp" #include & ...

  5. python+opencv图像拼接-python opencv 图像拼接的实现方法

    初级的图像拼接为将两幅图像简单的粘贴在一起,仅仅是图像几何空间的转移与合成,与图像内容无关.高级图像拼接也叫作基于特征匹配的图像拼接,拼接时消去两幅图像相同的部分,实现拼接合成全景图. 具有相同尺寸的 ...

  6. OpenCV拼接细节stitching detailed的实例(附完整代码)

    OpenCV拼接细节stitching detailed的实例 OpenCV拼接细节stitching detailed的实例 OpenCV拼接细节stitching detailed的实例 #inc ...

  7. opencv图像拼接【二】

      在opencv图像拼接[一]中,实现了图像的直接连接,那么本文将实现基于特征匹配的图像融合,就是两幅图像中会有相同的部分,根据图像中相同的特征,实现图像的"拼接". 原图 特征 ...

  8. python图像拼接_python opencv 图像拼接的实现方法

    初级的图像拼接为将两幅图像简单的粘贴在一起,仅仅是图像几何空间的转移与合成,与图像内容无关.高级图像拼接也叫作基于特征匹配的图像拼接,拼接时消去两幅图像相同的部分,实现拼接合成全景图. 具有相同尺寸的 ...

  9. opencv 图像拼接

    详细的图像拼接实例注释,但是觉得这个代码整体比较乱,接下来自己会整理一个更加有序的代码. 代码和数据可见 完整的代码和数据请见:代码数据链接 #include <iostream> #in ...

最新文章

  1. 什么是三层交换机、网关、DNS、子网掩码、MAC地址
  2. 完爆Facebook/GraphQL,APIJSON全方位对比解析(一)-基础功能
  3. [HTTP协议]基础篇-待完结
  4. FPGA设计中MEMORY型数据怎么综合到blockRAM里面(二)
  5. Spring 一二事(8) - annotation 形式的 MVC
  6. golang的错误汇总
  7. XCTF-Reverse:Hello,CTF
  8. Hologres如何支持亿级用户UV计算
  9. intellij idea开发工具
  10. html5 调用微信分享,HTML5教程之微信调用分享接口
  11. Android 异步获取网络图片并处理图片Out Of Memory 内存溢出问题
  12. ALERT日志中常见监听错误:ORA-3136错误的排查
  13. [转]C# 中的常用正则表达式总结
  14. java wait() notify_Java的wait(), notify()和notifyAll()使用小结
  15. ppt 母板 如何修改你的背景图片
  16. Orientation模块管理设备的方向信息,包括alpha、beta、gamma三个方向信息,通过plus.orientation可获取设备方向管理对象
  17. cesium雷达图_cesium添加闪烁点,雷达图(一般用于预警)
  18. Excel VBA 中有关使用 UBound + CurrentRegion 提示类型不匹配的问题及解决方案
  19. 基于java+ssm+mysql的大学生考勤管理系统及智能分析系统
  20. Django教程 —— Django入门

热门文章

  1. wltp和nedc续航差多少_继续退坡,2021年买纯电动汽车补贴多少?_搜狐汽车
  2. 当PowerDesigner的工具栏不见时候该怎么调出来
  3. Python pip 用法大全
  4. smartforms长文本处理方式
  5. 基于买方意向的货物撮合交易_CCF货物撮合交易赛题 Baseline
  6. Win2008使用WEB方式更改工作组计算机用户帐户密码
  7. WinSCP无法连接linux,而secureCRT却可以
  8. html 英文字母不换行,css如何设置英文单词不换行?
  9. 随便说说Silverlight
  10. SQL那些事儿(六)--数据库三大范式