一 实验介绍

图像拼接是指将拍摄到的的具有重叠区域的的若干图像拼接成一张无缝全景图, 使得在获得大视 角的同时确保了图像具有很高的分辨率的技术。一个例子如下,输入三张具有重叠区域的图像:

拼接的结果为:

1.1 图像拼接基本步骤

图像拼接的完整流程如上所示,首先对输入图像提取鲁棒的特征点,并根据特征描述子完成特征点的匹配,然后根据已经匹配的特征点对得到相邻图像的位置关系从而进行图像配准,由于直接进行图像配准会破坏视场的一致性,因而先将图像投影在球面或者柱面上,最后计算相邻图像的拼缝并完成重叠区域的融合,得到最终的全景图像。

1.2 目标

由于图像拼接是一个过程较为复杂的任务,其中包括了很多步骤,每一个步骤都包含了较为复杂的数学模型和求解算法,为了简化任务,本次着重于以下几个方面:

  1. 掌握图像拼接的完整流程,在尽可能少的介绍数学模型和求解算法的同时,理解图像拼接各个阶段的作用;
  2. 利用OpenCV中stitching pipeline模块实现图像拼接的各个步骤,并理解其中各个参数的作用;
  3. 利用CMake进行简单的C++工程管理;

二 步骤

2.1 输入图像

本次实验把图像路径存储在一个txt文件中,使用ifstream读取txt文件得到图像路径,然后使用OpenCV读取图像存入到vector<Mat>中,代码如下:

 1 vector<Mat> imgs;
 2 ifstream fin("../img.txt"); // 打开txt文件
 3 string img_name;
 4 while(getline(fin, img_name))
 5 {
 6     Mat img = imread(img_name);
 7     //resize(img, img, Size(), 0.25, 0.25);
 8     imgs.push_back(img);
 9 }
10 int num_images = imgs.size();    //图像数量

2.2 特征点提取和特征匹配

特征点指的是图像灰度值发生剧烈变化的点或者在图像边缘上曲率较大的点,用直白的话来说就是指,从不同的角度对同一个场景进行拍照,在每一幅照片中都能鲁棒的提取的图像的点就是特征点。一个好的特征点提取算法需要具有以下的特征:数量多,在不同场景下都能提取得到足够数量的特征点;独特性好,从而便于对特征点进行匹配;抗旋转,抗亮度变化,抗尺度缩放等,常用的特征点提取算法包括SIFT,SURF,ORB等,OpenCV的拼接模块集成了SURF/ORB两种特征点提取算法,代码如下:

1 Ptr<FeaturesFinder> finder;    //定义特征寻找器
2 finder = new SurfFeaturesFinder();    //应用SURF方法寻找特征
3 //finder = new OrbFeaturesFinder();    //应用ORB方法寻找特征
4 vector<ImageFeatures> features(num_images);    //表示图像特征
5 for (int i =0 ;i<num_images;i++)
6     (*finder)(imgs[i], features[i]);    //特征检测

对提取的特征点利用特征描述子进行匹配,从而找到不同图片中提取到的相同的特征点就是特征匹配。特征描述子就是描述一个特征点的属性,比如sift算法就是使用一个128维的向量,通过比较不同图像特征点的特征向量的欧式距离从而判断这两个特征点是否能进行匹配。

1 vector<MatchesInfo> pairwise_matches;    //表示特征匹配信息变量
2 // false含义为不使用GPU进行加速,
3 // 0.35f为阈值,范围在0-1之间,该值越大,一般匹配越严格,得到的匹配对就越少
4 BestOf2NearestMatcher matcher(false, 0.35f);    //定义特征匹配器,2NN方法
5 matcher(features, pairwise_matches);    //进行特征匹配

2.3 图像配准

在得到了匹配对之后,需要根据这些匹配对得到图像的相对位置,从而把多幅图像融合成为一幅图像,该步骤的计算思路是计算两幅图像的单应性矩阵,从而得到一幅图像相对于另一幅图像的位置,用公式描述为

[公式]

其中 [公式] ​, ​均代表图像的坐标,为了便于使用矩阵运算,需要将​ [公式] 变换为齐次坐标形式​。上面式子 [公式] 中​即为单应性矩阵,为​ [公式] 的矩阵,但​ [公式] ,因而该变换矩阵是一个八参数模型,通过该方程即可实现相邻图像的配准。

由于每个特征点都有​ [公式] 两个坐标,因此只需要四个点即可求解出该八参数模型的解,同时考虑到前面利用特征向量匹配得到的特征点对可能存在误匹配,因此使用RANSAC算法进行求解,简单地说就是,每次随机抽取四个点求解单应性矩阵,然后根据该单应性矩阵判断剩余的匹配对是否为正确匹配,选择正确匹配数量最多的一组来进行求解,一个简单的算法流程图为:

OpenCV代码实现为:

1 HomographyBasedEstimator estimator;    //定义参数评估器,为八参数模型
2 vector<CameraParams> cameras;    //表示相机参数,求解相机的相对位置
3 estimator(features, pairwise_matches, cameras);    //进行相机参数求解

另外前面的算法孤立求解两幅图像之间的位置,如果直接进行多幅图像的拼接会造成误差的累积,因此使用光束平差法进行联合优化,可以同时优化多个相机参数,从而得到更准确的图像位置,OpenCV代码为

1 Ptr<detail::BundleAdjusterBase> adjuster;    //光束平差法,精确相机参数
2 adjuster = new detail::BundleAdjusterReproj();    //重映射误差方法
3 adjuster->setConfThresh(1.0f);    //设置匹配置信度,该值默认设为1
4 (*adjuster)(features, pairwise_matches, cameras);    //精确评估相机参数

水平矫正

由于相机拍摄时候往往不是沿着水平方向的,那么就会导致拼接结果出现波纹状,为了解决这个问题,需要进行水平矫正:

水平矫正前:

 

水平矫正后:

 

OpenCV代码为:

1 vector<Mat> rmats;
2 for (size_t i = 0; i < cameras.size(); ++i)    //复制相机的旋转参数
3     rmats.push_back(cameras[i].R.clone());
4 waveCorrect(rmats, WAVE_CORRECT_HORIZ);    //进行波形校正
5 for (size_t i = 0; i < cameras.size(); ++i)    //相机参数赋值
6     cameras[i].R = rmats[i];
7 rmats.clear();    //清变量

2.4 图像投影

在得到相机的相对位置,如果直接进行拼接会破坏视场的一致性,使得拼接得到的全景图看起来不够连贯,因此需要通过投影变换将所有图像都投影在球面,柱面等,投影平面的选择与相机拍摄的方式有关系,一般来说球面投影,柱面投影是最为常用的投影方式。OpenCV代码为:

 1 vector<Point> corners(num_images);    //表示映射变换后图像的左上角坐标
 2 vector<UMat> masks_warped(num_images);    //表示映射变换后的图像掩码
 3 vector<UMat> images_warped(num_images);    //表示映射变换后的图像
 4 vector<Size> sizes(num_images);    //表示映射变换后的图像尺寸
 5 vector<UMat> masks(num_images);    //表示源图的掩码
 6  7 for (int i = 0; i < num_images; ++i)    //初始化源图的掩码
 8 {
 9     masks[i].create(imgs[i].size(), CV_8U);    //定义尺寸大小
10     masks[i].setTo(Scalar::all(255));    //全部赋值为255,表示源图的所有区域都使用
11 }
12 13 Ptr<WarperCreator> warper_creator;    //定义图像映射变换创造器
14 warper_creator = new cv::SphericalWarper();
15 //warper_creator = makePtr<cv::PlaneWarper>();     //平面投影
16 //warper_creator = new cv::CylindricalWarper();    //柱面投影
17 //warper_creator = new cv::SphericalWarper();    //球面投影
18 //warper_creator = new cv::FisheyeWarper();    //鱼眼投影
19 //warper_creator = new cv::StereographicWarper();    //立方体投影
20 21 //定义图像映射变换器,设置映射的尺度为相机的焦距,所有相机的焦距都相同
22 vector<double> focals;
23 24 for (size_t i = 0; i < cameras.size(); ++i)
25 {
26     cout<<""<<i<<"焦距为"<<cameras[i].focal<<endl;
27     focals.push_back(cameras[i].focal);
28 }
29 sort(focals.begin(), focals.end());
30 // 使用所有相机焦距的中位数作为投影变换的焦距
31 float warped_image_scale;
32 if (focals.size() % 2 == 1)
33     warped_image_scale = static_cast<float>(focals[focals.size() / 2]);
34 else
35     warped_image_scale = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;
36 37 cout<<"最终选择的图像的焦距为"<<warped_image_scale<<endl;
38 Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale));
39 40 for (int i = 0; i < num_images; ++i)
41 {
42     Mat_<float> K;
43     //转换相机内参数的数据类型
44     cameras[i].K().convertTo(K, CV_32F);    
45     //对当前图像镜像投影变换,得到变换后的图像以及该图像的左上角坐标
46     corners[i] = warper->warp(imgs[i], K, cameras[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);
47      //得到投影变换后的尺寸
48     sizes[i] = images_warped[i].size();   
49     //得到变换后的图像掩码
50     warper->warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
51 }

到这一步为止,图像拼接可以认为基本完成了,但是此时拼接得到的结果可能存在明显的亮暗变化,可能存在部分错位,图像与图像之间的重叠区域也会有明显的过渡痕迹,为了解决这些问题,通过下面的后处理手段可以得到更好的结果。

2.5曝光补偿

如果在拍摄过程中,由于未固定曝光,就会导致不同时刻拍摄得到的图片的整体亮度不同,那么直接进行拼接就会出现明显的明暗上的变化,因此需要设置曝光补偿,使得不同照片的整体亮度一致:

1 Ptr<ExposureCompensator> compensator =
2     ExposureCompensator::createDefault(ExposureCompensator::GAIN);
3 compensator->feed(corners, images_warped, masks_warped);    //得到曝光补偿器
4 for(int i=0;i<num_images;++i)    //应用曝光补偿器,对图像进行曝光补偿
5 {
6     compensator->apply(i, corners[i], images_warped[i], masks_warped[i]);
7 }

2.6 拼缝计算和图像融合

拼缝就是指图像之间重叠区域中最为相似的那条线,在得到相邻两幅图像的拼缝位置后,在拼缝附近的若干个像素使用融合算法,对于重叠区域中远离拼缝的位置只选择一侧的图像,通过这样的方法,可以有效的去除图像之间的错位,伪像,得到更好的拼接结果。

目前常用的寻找接缝线的方法有三种,逐点法,动态规划法和图割法,这里不详细的介绍其原理,仅仅简单的分析复杂度和效果。三种方法的目的都是寻找重叠区域中最相似的线,其中逐点法最简单,效果也相对较差,图割法计算复杂度最高,效果一般也最好,而动态规划法则相当于二者的折中,计算复杂度和效果都居中,根据实际的需要,选择不同的拼缝计算方法。OpenCV代码为:

 1 //在后面,我们还需要用到映射变换图的掩码masks_warped,因此这里为该变量添加一个副本masks_seam
 2 vector<UMat> masks_seam(num_images);
 3 for(int i = 0; i<num_images;i++)
 4     masks_warped[i].copyTo(masks_seam[i]);
 5  6  7 Ptr<SeamFinder> seam_finder;    //定义接缝线寻找器
 8 //seam_finder = new NoSeamFinder();    //无需寻找接缝线
 9 //seam_finder = new VoronoiSeamFinder();    //逐点法
10 //seam_finder = new DpSeamFinder(DpSeamFinder::COLOR);    //动态规范法,损失函数基于灰度
11 //seam_finder = new DpSeamFinder(DpSeamFinder::COLOR_GRAD);//动态规范法,损失函数基于灰度梯度
12 //图割法
13 seam_finder = new GraphCutSeamFinder(GraphCutSeamFinder::COST_COLOR);
14 //seam_finder = new GraphCutSeamFinder(GraphCutSeamFinder::COST_COLOR_GRAD);
15 16 vector<UMat> images_warped_f(num_images);
17 for (int i = 0; i < num_images; ++i)    //图像数据类型转换
18     images_warped[i].convertTo(images_warped_f[i], CV_32F);
19 20 images_warped.clear();    //清内存
21 //得到接缝线的掩码图像masks_seam
22 seam_finder->find(images_warped_f, corners, masks_seam);

常用的融合算法有羽化融合和拉普拉斯融合算法,羽化融合就是对拼缝附近的位置根据与接缝的距离求出权重,加权融合,而拉普拉斯融合算法相当于求出图像不同频率的分量,然后按频率进行融合,显然拉普拉斯融合算法效果更好,但计算复杂度也更高。OpenCV代码为:

 1 blender->prepare(corners, sizes);    //生成全景图像区域
 2 //在融合的时候,最重要的是在接缝线两侧进行处理,而上一步在寻找接缝线后得到的掩码的边界就是接缝线处,因此需要在接缝线使用膨胀算法在两侧开辟一块区域用于融合处理。
 3 vector<Mat> dilate_img(num_images);
 4 vector<Mat> masks_seam_new(num_images);
 5 Mat tem;
 6 Mat element = getStructuringElement(MORPH_RECT, Size(20, 20));    //定义结构元素
 7 for(int k=0;k<num_images;k++)
 8 {
 9     images_warped_f[k].convertTo(images_warped_s[k], CV_16S);    //改变数据类型
10     dilate(masks_seam[k], masks_seam_new[k], element);    //膨胀运算
11     //映射变换图的掩码和膨胀后的掩码相“与”,从而使扩展的区域仅仅限于接缝线两侧,其他边界处不受影响
12     masks_warped[k].copyTo(tem);
13     masks_seam_new[k] = masks_seam_new[k] & tem;
14 15     blender->feed(images_warped_s[k], masks_seam_new[k], corners[k]);    //初始化数据
16     cout<<"处理完成"<<k<<"图片"<<endl;
17 }
18 19 masks_seam.clear();    //清内存
20 images_warped_s.clear();
21 masks_warped.clear();
22 images_warped_f.clear();
23 24 Mat result, result_mask;
25 //完成融合操作,得到全景图像result和它的掩码result_mask
26 blender->blend(result, result_mask);
27 28 imwrite("result.jpg", result);    //存储全景图像

2.7 其他

2.7.1 使用CMake编译程序

CMake是一个跨平台的生成makefile的一个工具,它可以读入所有源文件,自动生成makefile,从而在linux系统便捷的实现C++程序的编译和执行,其特别适用于大型C++程序的管理,CMake的所有语句都写在一个叫CMakeLists.txt的文件中,然后执行cmake相应指令,生成linux系统下的可执行代码,本次实验的CMakeLists.txt如下

 1 # 指定cmake的最低版本
 2 cmake_minimum_required(VERSION 2.8)
 3 # 该工程名字
 4 project( imageStich )
 5 # 支持C++11特性
 6 add_definitions(-std=c++11)
 7 # 寻找安装的OpenCV库
 8 find_package( OpenCV REQUIRED )
 9 # 添加OpenCV的头文件
10 include_directories( ${OpenCV_INCLUDE_DIRS} )
11 # 生成可执行文件imageStitch
12 add_executable(imageStich imageStich.cpp)
13 # 链接OpenCV的库
14 target_link_libraries( imageStich ${OpenCV_LIBS} )

在写完了CMakeLists.txt后,可以通过以下指令编译执行:

1 cmake . // 生成相应的makefile文件
2 make    //  编译生成可执行文件
3 ./imageStich // 执行代码

2.7.2 完整的拼接程序为

  1 #include <fstream>
  2 #include <string>
  3 #include<iostream>
  4 #include "opencv2/opencv_modules.hpp"
  5 #include <opencv2/core/utility.hpp>
  6 #include "opencv2/imgcodecs.hpp"
  7 #include "opencv2/highgui.hpp"
  8 #include "opencv2/stitching/detail/autocalib.hpp"
  9 #include "opencv2/stitching/detail/blenders.hpp"
 10 #include "opencv2/stitching/detail/timelapsers.hpp"
 11 #include "opencv2/stitching/detail/camera.hpp"
 12 #include "opencv2/stitching/detail/exposure_compensate.hpp"
 13 #include "opencv2/stitching/detail/matchers.hpp"
 14 #include "opencv2/stitching/detail/motion_estimators.hpp"
 15 #include "opencv2/stitching/detail/seam_finders.hpp"
 16 #include "opencv2/stitching/detail/warpers.hpp"
 17 #include "opencv2/stitching/warpers.hpp"
 18 #include<opencv2/xfeatures2d.hpp>
 19  20 using namespace std;
 21 using namespace cv;
 22 using namespace cv::detail;
 23  24  25 int main(int argc, char** argv)
 26 {
 27     vector<Mat> imgs;
 28     ifstream fin("../img.txt");
 29     string img_name;
 30     while(getline(fin, img_name))
 31     {
 32         Mat img = imread(img_name);
 33         imgs.push_back(img);
 34     }
 35  36     int num_images = imgs.size();    //图像数量
 37     cout<<"图像数量为"<<num_images<<endl;
 38     cout<<"图像读取完毕"<<endl;
 39  40  41  42     Ptr<FeaturesFinder> finder;    //定义特征寻找器
 43     finder = new SurfFeaturesFinder();    //应用SURF方法寻找特征
 44     //finder = new OrbFeaturesFinder();    //应用ORB方法寻找特征
 45     vector<ImageFeatures> features(num_images);    //表示图像特征
 46     for (int i =0 ;i<num_images;i++)
 47         (*finder)(imgs[i], features[i]);    //特征检测
 48     cout<<"特征提取完毕"<<endl;
 49     vector<MatchesInfo> pairwise_matches;    //表示特征匹配信息变量
 50     BestOf2NearestMatcher matcher(false, 0.35f, 6, 6);    //定义特征匹配器,2NN方法
 51     matcher(features, pairwise_matches);    //进行特征匹配
 52     cout<<"特征匹配完毕"<<endl;
 53     
 54     HomographyBasedEstimator estimator;    //定义参数评估器
 55     vector<CameraParams> cameras;    //表示相机参数,内参加外参
 56     estimator(features, pairwise_matches, cameras);    //进行相机参数评
 57     for (size_t i = 0; i < cameras.size(); ++i)    //转换相机旋转参数的数据类型
 58     {
 59         Mat R;
 60         cameras[i].R.convertTo(R, CV_32F);
 61         cameras[i].R = R;
 62     }
 63     cout<<"相机参数预测完毕"<<endl;
 64  65  66     for (size_t i = 0; i < cameras.size(); ++i)
 67     {
 68         cout<<""<<i<<"焦距为"<<cameras[i].focal<<endl;
 69     }
 70     // 在一部可以计算重映射误差,想办法让他可以输出出来
 71     Ptr<detail::BundleAdjusterBase> adjuster;    //光束平差法,精确相机参数
 72     //adjuster->setRefinementMask();
 73     adjuster = new detail::BundleAdjusterReproj();    //重映射误差方法
 74     //adjuster = new detail::BundleAdjusterRay();    //射线发散误差方法
 75  76     adjuster->setConfThresh(1.0f);    //设置匹配置信度,该值设为1
 77     (*adjuster)(features, pairwise_matches, cameras);    //精确评估相机参数
 78  79     vector<Mat> rmats;
 80     for (size_t i = 0; i < cameras.size(); ++i)    //复制相机的旋转参数
 81         rmats.push_back(cameras[i].R.clone());
 82     waveCorrect(rmats, WAVE_CORRECT_HORIZ);    //进行波形校正
 83     for (size_t i = 0; i < cameras.size(); ++i)    //相机参数赋值
 84         cameras[i].R = rmats[i];
 85     rmats.clear();    //清变量
 86  87     cout<<"利用光束平差法进行相机矩阵更新"<<endl;
 88  89     vector<Point> corners(num_images);    //表示映射变换后图像的左上角坐标
 90     vector<UMat> masks_warped(num_images);    //表示映射变换后的图像掩码
 91     vector<UMat> images_warped(num_images);    //表示映射变换后的图像
 92     vector<Size> sizes(num_images);    //表示映射变换后的图像尺寸
 93     vector<UMat> masks(num_images);    //表示源图的掩码
 94  95     for (int i = 0; i < num_images; ++i)    //初始化源图的掩码
 96     {
 97         masks[i].create(imgs[i].size(), CV_8U);    //定义尺寸大小
 98         masks[i].setTo(Scalar::all(255));    //全部赋值为255,表示源图的所有区域都使用
 99     }
100 101     Ptr<WarperCreator> warper_creator;    //定义图像映射变换创造器
102     warper_creator = new cv::SphericalWarper();
103     //warper_creator = makePtr<cv::PlaneWarper>();     //平面投影
104     //warper_creator = new cv::CylindricalWarper();    //柱面投影
105     //warper_creator = new cv::SphericalWarper();    //球面投影
106     //warper_creator = new cv::FisheyeWarper();    //鱼眼投影
107     //warper_creator = new cv::StereographicWarper();    //立方体投影
108 109     //定义图像映射变换器,设置映射的尺度为相机的焦距,所有相机的焦距都相同
110     vector<double> focals;
111     for (size_t i = 0; i < cameras.size(); ++i)
112     {
113         cout<<""<<i<<"焦距为"<<cameras[i].focal<<endl;
114         focals.push_back(cameras[i].focal);
115     }
116     sort(focals.begin(), focals.end());
117     float warped_image_scale;
118     if (focals.size() % 2 == 1)
119         warped_image_scale = static_cast<float>(focals[focals.size() / 2]);
120     else
121         warped_image_scale = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;
122     Ptr<RotationWarper> warper = warper_creator->create(static_cast<float>(warped_image_scale));
123     for (int i = 0; i < num_images; ++i)
124     {
125         Mat_<float> K;
126         cameras[i].K().convertTo(K, CV_32F);    //转换相机内参数的数据类型
127         //对当前图像镜像投影变换,得到变换后的图像以及该图像的左上角坐标
128         corners[i] = warper->warp(imgs[i], K, cameras[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);
129         sizes[i] = images_warped[i].size();    //得到尺寸
130         //得到变换后的图像掩码
131         warper->warp(masks[i], K, cameras[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
132     }
133 134     imgs.clear();    //清变量
135     masks.clear();
136     cout<<"图像映射完毕"<<endl;
137     //创建曝光补偿器,应用增益补偿方法
138     Ptr<ExposureCompensator> compensator =
139             ExposureCompensator::createDefault(ExposureCompensator::GAIN);
140     compensator->feed(corners, images_warped, masks_warped);    //得到曝光补偿器
141     for(int i=0;i<num_images;++i)    //应用曝光补偿器,对图像进行曝光补偿
142     {
143         compensator->apply(i, corners[i], images_warped[i], masks_warped[i]);
144     }
145     cout<<"图像曝光完毕"<<endl;
146     
147     //在后面,我们还需要用到映射变换图的掩码masks_warped,因此这里为该变量添加一个副本masks_seam
148     vector<UMat> masks_seam(num_images);
149     for(int i = 0; i<num_images;i++)
150         masks_warped[i].copyTo(masks_seam[i]);
151 152     Ptr<SeamFinder> seam_finder;    //定义接缝线寻找器
153     //seam_finder = new NoSeamFinder();    //无需寻找接缝线
154     //seam_finder = new VoronoiSeamFinder();    //逐点法
155     //seam_finder = new DpSeamFinder(DpSeamFinder::COLOR);    //动态规范法
156     //seam_finder = new DpSeamFinder(DpSeamFinder::COLOR_GRAD);
157     //图割法
158     seam_finder = new GraphCutSeamFinder(GraphCutSeamFinder::COST_COLOR);
159     //seam_finder = new GraphCutSeamFinder(GraphCutSeamFinder::COST_COLOR_GRAD);
160 161     vector<UMat> images_warped_f(num_images);
162     for (int i = 0; i < num_images; ++i)    //图像数据类型转换
163         images_warped[i].convertTo(images_warped_f[i], CV_32F);
164 165     images_warped.clear();    //清内存
166     //得到接缝线的掩码图像masks_seam
167     seam_finder->find(images_warped_f, corners, masks_seam);
168     for(size_t i = 0; i < num_images; i++)
169     {
170         namedWindow("mask_cut", WINDOW_NORMAL);
171         imshow("mask_cut", masks_seam[i]);
172         waitKey(0);
173     }
174 175 176     cout<<"拼缝优化完毕"<<endl;
177 178     vector<Mat> images_warped_s(num_images);
179     Ptr<Blender> blender;    //定义图像融合器
180 181     blender = Blender::createDefault(Blender::NO, false);    //简单融合方法
182     //羽化融合方法
183 //    blender = Blender::createDefault(Blender::FEATHER, false);
184 //    //dynamic_cast多态强制类型转换时候使用
185 //    FeatherBlender* fb = dynamic_cast<FeatherBlender*>(static_cast<Blender*>(blender));
186 //    fb->setSharpness(0.005);    //设置羽化锐度
187 188 //    blender = Blender::createDefault(Blender::MULTI_BAND, false);    //多频段融合
189  //   MultiBandBlender* mb = dynamic_cast<MultiBandBlender*>(static_cast<Blender*>(blender));
190  //   mb->setNumBands(8);   //设置频段数,即金字塔层数
191 192     blender->prepare(corners, sizes);    //生成全景图像区域
193     cout<<"生成全景图像区域"<<endl;
194     vector<Mat> dilate_img(num_images);
195     vector<Mat> masks_seam_new(num_images);
196     Mat tem;
197     Mat element = getStructuringElement(MORPH_RECT, Size(20, 20));    //定义结构元素
198     for(int k=0;k<num_images;k++)
199     {
200         images_warped_f[k].convertTo(images_warped_s[k], CV_16S);    //改变数据类型
201         dilate(masks_seam[k], masks_seam_new[k], element);    //膨胀运算
202         //映射变换图的掩码和膨胀后的掩码相“与”,从而使扩展的区域仅仅限于接缝线两侧,其他边界处不受影响
203         masks_warped[k].copyTo(tem);
204         masks_seam_new[k] = masks_seam_new[k] & tem;
205         blender->feed(images_warped_s[k], masks_seam_new[k], corners[k]);    //初始化数据
206         cout<<"处理完成"<<k<<"图片"<<endl;
207     }
208 209     masks_seam.clear();    //清内存
210     images_warped_s.clear();
211     masks_warped.clear();
212     images_warped_f.clear();
213 214 215     Mat result, result_mask;
216     //完成融合操作,得到全景图像result和它的掩码result_mask
217     blender->blend(result, result_mask);
218     imwrite("result.jpg", result);    //存储全景图像
219 220     return 0;
221 }

 

版权声明:本文为ybqjymy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ybqjymy/p/14178626.html