一 實驗介紹
圖像拼接是指將拍攝到的的具有重疊區域的的若干圖像拼接成一張無縫全景圖, 使得在獲得大視 角的同時確保了圖像具有很高的分辨率的技術。一個例子如下,輸入三張具有重疊區域的圖像:
拼接的結果為:
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 }