OpenCV 实现图像拼接例子
一 实验介绍
图像拼接是指将拍摄到的的具有重叠区域的的若干图像拼接成一张无缝全景图, 使得在获得大视 角的同时确保了图像具有很高的分辨率的技术。一个例子如下,输入三张具有重叠区域的图像:
拼接的结果为:
1.1 图像拼接基本步骤
图像拼接的完整流程如上所示,首先对输入图像提取鲁棒的特征点,并根据特征描述子完成特征点的匹配,然后根据已经匹配的特征点对得到相邻图像的位置关系从而进行图像配准,由于直接进行图像配准会破坏视场的一致性,因而先将图像投影在球面或者柱面上,最后计算相邻图像的拼缝并完成重叠区域的融合,得到最终的全景图像。
1.2 目标
由于图像拼接是一个过程较为复杂的任务,其中包括了很多步骤,每一个步骤都包含了较为复杂的数学模型和求解算法,为了简化任务,本次着重于以下几个方面:
- 掌握图像拼接的完整流程,在尽可能少的介绍数学模型和求解算法的同时,理解图像拼接各个阶段的作用;
- 利用OpenCV中stitching pipeline模块实现图像拼接的各个步骤,并理解其中各个参数的作用;
- 利用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 }