利用OpenCV實現影象拼接例子
更新:
知乎程式碼可能無法編譯過去,放一個github連結
https://
github。com/purse1996/op
encv_example/blob/master/imageStitch/imageStich。cpp
,
一 實驗介紹
影象拼接是指將拍攝到的的具有重疊區域的的若干影象拼接成一張無縫全景圖, 使得在獲得大視 角的同時確保了影象具有很高的解析度的技術。一個例子如下,輸入三張具有重疊區域的影象:
拼接的結果為:
1.1 影象拼接基本步驟
影象拼接的完整流程如上所示,首先對輸入影象提取魯棒的特徵點,並根據特徵描述子完成特徵點的匹配,然後根據已經匹配的特徵點對得到相鄰影象的位置關係從而進行影象配準,由於直接進行影象配準會破壞視場的一致性,因而先將影象投影在球面或者柱面上,最後計算相鄰影象的拼縫並完成重疊區域的融合,得到最終的全景影象。
1.2 目標
由於影象拼接是一個過程較為複雜的任務,其中包括了很多步驟,每一個步驟都包含了較為複雜的數學模型和求解演算法,為了簡化任務,本次著重於以下幾個方面:
掌握影象拼接的完整流程,在儘可能少的介紹數學模型和求解演算法的同時,理解影象拼接各個階段的作用;
利用OpenCV中stitching pipeline模組實現影象拼接的各個步驟,並理解其中各個引數的作用;
利用CMake進行簡單的C++工程管理;
1.3 其他資料
AutoStitch論文
http://
matthewalunbrown。com/pa
pers/ijcv2007。pdf
OpenCV官方教程
https://
docs。opencv。org/3。4。1/d
9/df8/tutorial_root。html
https://
blog。csdn。net/zhaocj/ar
ticle/details/78967041
1.4 環境搭建教程
可參照OpenCV官網教程,我自己使用的OpenCV版本為OpenCV3。4。1,這裡不再贅述:
https://
docs。opencv。org/3。4。1/d
7/d9f/tutorial_linux_install。html
另外注意由於影象拼接中使用到了特徵點提取演算法,而在OpenCV3。0以後,這部分程式碼被封裝在了OpenCV Contrib中,因此在編譯OpenCV時需要將這部分也編譯進去。
二 步驟
2.1 輸入影象
本次實驗把影象路徑儲存在一個txt檔案中,使用ifstream讀取txt檔案得到影象路徑,然後使用OpenCV讀取影象存入到vector
vector
<
Mat
>
imgs
;
ifstream
fin
(
“。。/img。txt”
);
// 開啟txt檔案
string
img_name
;
while
(
getline
(
fin
,
img_name
))
{
Mat
img
=
imread
(
img_name
);
//resize(img, img, Size(), 0。25, 0。25);
imgs
。
push_back
(
img
);
}
int
num_images
=
imgs
。
size
();
//影象數量
2.2 特徵點提取和特徵匹配
特徵點
指的是影象灰度值發生劇烈變化的點或者在影象邊緣上曲率較大的點,用直白的話來說就是指,從不同的角度對同一個場景進行拍照,在每一幅照片中都能魯棒的提取的影象的點就是特徵點。一個好的特徵點提取演算法需要具有以下的特徵:數量多,在不同場景下都能提取得到足夠數量的特徵點;獨特性好,從而便於對特徵點進行匹配;抗旋轉,抗亮度變化,抗尺度縮放等,常用的特徵點提取演算法包括SIFT,SURF,ORB等,OpenCV的拼接模組集成了SURF/ORB兩種特徵點提取演算法,程式碼如下:
Ptr
<
FeaturesFinder
>
finder
;
//定義特徵尋找器
finder
=
new
SurfFeaturesFinder
();
//應用SURF方法尋找特徵
//finder = new OrbFeaturesFinder(); //應用ORB方法尋找特徵
vector
<
ImageFeatures
>
features
(
num_images
);
//表示影象特徵
for
(
int
i
=
0
;
i
<
num_images
;
i
++
)
(
*
finder
)(
imgs
[
i
],
features
[
i
]);
//特徵檢測
對提取的特徵點利用特徵描述子進行匹配,從而找到不同圖片中提取到的相同的特徵點就是
特徵匹配
。特徵描述子就是描述一個特徵點的屬性,比如sift演算法就是使用一個128維的向量,透過比較不同影象特徵點的特徵向量的歐式距離從而判斷這兩個特徵點是否能進行匹配。
vector
<
MatchesInfo
>
pairwise_matches
;
//表示特徵匹配資訊變數
// false含義為不使用GPU進行加速,
// 0。35f為閾值,範圍在0-1之間,該值越大,一般匹配越嚴格,得到的匹配對就越少
BestOf2NearestMatcher
matcher
(
false
,
0。35f
);
//定義特徵匹配器,2NN方法
matcher
(
features
,
pairwise_matches
);
//進行特徵匹配
2.3 影象配準
在得到了匹配對之後,需要根據這些匹配對得到影象的相對位置,從而把多幅影象融合成為一幅影象,該步驟的計算思路是計算兩幅影象的單應性矩陣,從而得到一幅影象相對於另一幅影象的位置,用公式描述為
其中
,
均代表影象的座標,為了便於使用矩陣運算,需要將
變換為齊次座標形式。上面式子
中即為單應性矩陣,為
的矩陣,但
,因而該變換矩陣是一個八引數模型,透過該方程即可實現相鄰影象的配準。
由於每個特徵點都有
兩個座標,因此只需要四個點即可求解出該八引數模型的解,同時考慮到前面利用特徵向量匹配得到的特徵點對可能存在誤匹配,因此使用RANSAC演算法進行求解,簡單地說就是,每次隨機抽取四個點求解單應性矩陣,然後根據該單應性矩陣判斷剩餘的匹配對是否為正確匹配,選擇正確匹配數量最多的一組來進行求解,一個簡單的演算法流程圖為:
OpenCV程式碼實現為:
HomographyBasedEstimator
estimator
;
//定義引數評估器,為八引數模型
vector
<
CameraParams
>
cameras
;
//表示相機引數,求解相機的相對位置
estimator
(
features
,
pairwise_matches
,
cameras
);
//進行相機引數求解
另外前面的演算法孤立求解兩幅影象之間的位置,如果直接進行多幅影象的拼接會造成誤差的累積,因此使用光束平差法進行聯合最佳化,可以同時最佳化多個相機引數,從而得到更準確的影象位置,OpenCV程式碼為
Ptr
<
detail
::
BundleAdjusterBase
>
adjuster
;
//光束平差法,精確相機引數
adjuster
=
new
detail
::
BundleAdjusterReproj
();
//重對映誤差方法
adjuster
->
setConfThresh
(
1。0f
);
//設定匹配置信度,該值預設設為1
(
*
adjuster
)(
features
,
pairwise_matches
,
cameras
);
//精確評估相機引數
水平矯正
由於相機拍攝時候往往不是沿著水平方向的,那麼就會導致拼接結果出現波紋狀,為了解決這個問題,需要進行水平矯正:
水平矯正前:
水平矯正後:
OpenCV程式碼為:
vector
<
Mat
>
rmats
;
for
(
size_t
i
=
0
;
i
<
cameras
。
size
();
++
i
)
//複製相機的旋轉引數
rmats
。
push_back
(
cameras
[
i
]。
R
。
clone
());
waveCorrect
(
rmats
,
WAVE_CORRECT_HORIZ
);
//進行波形校正
for
(
size_t
i
=
0
;
i
<
cameras
。
size
();
++
i
)
//相機引數賦值
cameras
[
i
]。
R
=
rmats
[
i
];
rmats
。
clear
();
//清變數
2.4 影象投影
在得到相機的相對位置,如果直接進行拼接會破壞視場的一致性,使得拼接得到的全景圖看起來不夠連貫,因此需要透過投影變換將所有影象都投影在球面,柱面等,投影平面的選擇與相機拍攝的方式有關係,一般來說球面投影,柱面投影是最為常用的投影方式。OpenCV程式碼為:
vector
<
Point
>
corners
(
num_images
);
//表示對映變換後圖像的左上角座標
vector
<
UMat
>
masks_warped
(
num_images
);
//表示對映變換後的影象掩碼
vector
<
UMat
>
images_warped
(
num_images
);
//表示對映變換後的影象
vector
<
Size
>
sizes
(
num_images
);
//表示對映變換後的影象尺寸
vector
<
UMat
>
masks
(
num_images
);
//表示源圖的掩碼
for
(
int
i
=
0
;
i
<
num_images
;
++
i
)
//初始化源圖的掩碼
{
masks
[
i
]。
create
(
imgs
[
i
]。
size
(),
CV_8U
);
//定義尺寸大小
masks
[
i
]。
setTo
(
Scalar
::
all
(
255
));
//全部賦值為255,表示源圖的所有區域都使用
}
Ptr
<
WarperCreator
>
warper_creator
;
//定義影象對映變換創造器
warper_creator
=
new
cv
::
SphericalWarper
();
//warper_creator = makePtr
//warper_creator = new cv::CylindricalWarper(); //柱面投影
//warper_creator = new cv::SphericalWarper(); //球面投影
//warper_creator = new cv::FisheyeWarper(); //魚眼投影
//warper_creator = new cv::StereographicWarper(); //立方體投影
//定義影象對映變換器,設定對映的尺度為相機的焦距,所有相機的焦距都相同
vector
<
double
>
focals
;
for
(
size_t
i
=
0
;
i
<
cameras
。
size
();
++
i
)
{
cout
<<
“第”
<<
i
<<
“焦距為”
<<
cameras
[
i
]。
focal
<<
endl
;
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
]);
else
warped_image_scale
=
static_cast
<
float
>
(
focals
[
focals
。
size
()
/
2
-
1
]
+
focals
[
focals
。
size
()
/
2
])
*
0。5f
;
cout
<<
“最終選擇的影象的焦距為”
<<
warped_image_scale
<<
endl
;
Ptr
<
RotationWarper
>
warper
=
warper_creator
->
create
(
static_cast
<
float
>
(
warped_image_scale
));
for
(
int
i
=
0
;
i
<
num_images
;
++
i
)
{
Mat_
<
float
>
K
;
//轉換相機內參數的資料型別
cameras
[
i
]。
K
()。
convertTo
(
K
,
CV_32F
);
//對當前影象映象投影變換,得到變換後的影象以及該影象的左上角座標
corners
[
i
]
=
warper
->
warp
(
imgs
[
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
]);
}
到這一步為止,影象拼接可以認為基本完成了,但是此時拼接得到的結果可能存在明顯的亮暗變化,可能存在部分錯位,影象與影象之間的重疊區域也會有明顯的過渡痕跡,為了解決這些問題,透過下面的後處理手段可以得到更好的結果。
2.5曝光補償
如果在拍攝過程中,由於未固定曝光,就會導致不同時刻拍攝得到的圖片的整體亮度不同,那麼直接進行拼接就會出現明顯的明暗上的變化,因此需要設定曝光補償,使得不同照片的整體亮度一致:
Ptr
<
ExposureCompensator
>
compensator
=
ExposureCompensator
::
createDefault
(
ExposureCompensator
::
GAIN
);
compensator
->
feed
(
corners
,
images_warped
,
masks_warped
);
//得到曝光補償器
for
(
int
i
=
0
;
i
<
num_images
;
++
i
)
//應用曝光補償器,對影象進行曝光補償
{
compensator
->
apply
(
i
,
corners
[
i
],
images_warped
[
i
],
masks_warped
[
i
]);
}
2.6 拼縫計算和影象融合
拼縫就是指影象之間重疊區域中最為相似的那條線,在得到相鄰兩幅影象的拼縫位置後,在拼縫附近的若干個畫素使用融合演算法,對於重疊區域中遠離拼縫的位置只選擇一側的影象,透過這樣的方法,可以有效的去除影象之間的錯位,偽像,得到更好的拼接結果。
目前常用的
尋找接縫線的方法
有三種,逐點法,動態規劃法和圖割法,這裡不詳細的介紹其原理,僅僅簡單的分析複雜度和效果。三種方法的目的都是尋找重疊區域中最相似的線,其中逐點法最簡單,效果也相對較差,圖割法計算複雜度最高,效果一般也最好,而動態規劃法則相當於二者的折中,計算複雜度和效果都居中,根據實際的需要,選擇不同的拼縫計算方法。OpenCV程式碼為:
//在後面,我們還需要用到對映變換圖的掩碼masks_warped,因此這裡為該變數新增一個副本masks_seam
vector
<
UMat
>
masks_seam
(
num_images
);
for
(
int
i
=
0
;
i
<
num_images
;
i
++
)
masks_warped
[
i
]。
copyTo
(
masks_seam
[
i
]);
Ptr
<
SeamFinder
>
seam_finder
;
//定義接縫線尋找器
//seam_finder = new NoSeamFinder(); //無需尋找接縫線
//seam_finder = new VoronoiSeamFinder(); //逐點法
//seam_finder = new DpSeamFinder(DpSeamFinder::COLOR); //動態規範法,損失函式基於灰度
//seam_finder = new DpSeamFinder(DpSeamFinder::COLOR_GRAD);//動態規範法,損失函式基於灰度梯度
//圖割法
seam_finder
=
new
GraphCutSeamFinder
(
GraphCutSeamFinder
::
COST_COLOR
);
//seam_finder = new GraphCutSeamFinder(GraphCutSeamFinder::COST_COLOR_GRAD);
vector
<
UMat
>
images_warped_f
(
num_images
);
for
(
int
i
=
0
;
i
<
num_images
;
++
i
)
//影象資料型別轉換
images_warped
[
i
]。
convertTo
(
images_warped_f
[
i
],
CV_32F
);
images_warped
。
clear
();
//清記憶體
//得到接縫線的掩碼影象masks_seam
seam_finder
->
find
(
images_warped_f
,
corners
,
masks_seam
);
常用的
融合演算法
有羽化融合和拉普拉斯融合演算法,羽化融合就是對拼縫附近的位置根據與接縫的距離求出權重,加權融合,而拉普拉斯融合演算法相當於求出影象不同頻率的分量,然後按頻率進行融合,顯然拉普拉斯融合演算法效果更好,但計算複雜度也更高。OpenCV程式碼為:
blender
->
prepare
(
corners
,
sizes
);
//生成全景影象區域
//在融合的時候,最重要的是在接縫線兩側進行處理,而上一步在尋找接縫線後得到的掩碼的邊界就是接縫線處,因此需要在接縫線使用膨脹演算法在兩側開闢一塊區域用於融合處理。
vector
<
Mat
>
dilate_img
(
num_images
);
vector
<
Mat
>
masks_seam_new
(
num_images
);
Mat
tem
;
Mat
element
=
getStructuringElement
(
MORPH_RECT
,
Size
(
20
,
20
));
//定義結構元素
for
(
int
k
=
0
;
k
<
num_images
;
k
++
)
{
images_warped_f
[
k
]。
convertTo
(
images_warped_s
[
k
],
CV_16S
);
//改變資料型別
dilate
(
masks_seam
[
k
],
masks_seam_new
[
k
],
element
);
//膨脹運算
//對映變換圖的掩碼和膨脹後的掩碼相“與”,從而使擴充套件的區域僅僅限於接縫線兩側,其他邊界處不受影響
masks_warped
[
k
]。
copyTo
(
tem
);
masks_seam_new
[
k
]
=
masks_seam_new
[
k
]
&
tem
;
blender
->
feed
(
images_warped_s
[
k
],
masks_seam_new
[
k
],
corners
[
k
]);
//初始化資料
cout
<<
“處理完成”
<<
k
<<
“圖片”
<<
endl
;
}
masks_seam
。
clear
();
//清記憶體
images_warped_s
。
clear
();
masks_warped
。
clear
();
images_warped_f
。
clear
();
Mat
result
,
result_mask
;
//完成融合操作,得到全景影象result和它的掩碼result_mask
blender
->
blend
(
result
,
result_mask
);
imwrite
(
“result。jpg”
,
result
);
//儲存全景影象
2.7 其他
2.7.1 使用CMake編譯程式
CMake是一個跨平臺的生成makefile的一個工具,它可以讀入所有原始檔,自動生成makefile,從而在linux系統便捷的實現C++程式的編譯和執行,其特別適用於大型C++程式的管理,CMake的所有語句都寫在一個叫CMakeLists。txt的檔案中,然後執行cmake相應指令,生成linux系統下的可執行程式碼,本次實驗的CMakeLists。txt如下
# 指定cmake的最低版本
cmake_minimum_required(VERSION 2。8)
# 該工程名字
project( imageStich )
# 支援C++11特性
add_definitions(-std=c++11)
# 尋找安裝的OpenCV庫
find_package( OpenCV REQUIRED )
# 新增OpenCV的標頭檔案
include_directories( ${OpenCV_INCLUDE_DIRS} )
# 生成可執行檔案imageStitch
add_executable(imageStich imageStich。cpp)
# 連結OpenCV的庫
target_link_libraries( imageStich ${OpenCV_LIBS} )
在寫完了CMakeLists。txt後,可以透過以下指令編譯執行:
cmake 。 // 生成相應的makefile檔案
make // 編譯生成可執行檔案
。/imageStich // 執行程式碼
2.7.2 完整的拼接程式為
#include
#include
#include
#include
“opencv2/opencv_modules。hpp”
#include
#include
“opencv2/imgcodecs。hpp”
#include
“opencv2/highgui。hpp”
#include
“opencv2/stitching/detail/autocalib。hpp”
#include
“opencv2/stitching/detail/blenders。hpp”
#include
“opencv2/stitching/detail/timelapsers。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/warpers。hpp”
#include
“opencv2/stitching/warpers。hpp”
#include
using
namespace
std
;
using
namespace
cv
;
using
namespace
cv
::
detail
;
int
main
(
int
argc
,
char
**
argv
)
{
vector
<
Mat
>
imgs
;
ifstream
fin
(
“。。/img。txt”
);
string
img_name
;
while
(
getline
(
fin
,
img_name
))
{
Mat
img
=
imread
(
img_name
);
imgs
。
push_back
(
img
);
}
int
num_images
=
imgs
。
size
();
//影象數量
cout
<<
“影象數量為”
<<
num_images
<<
endl
;
cout
<<
“影象讀取完畢”
<<
endl
;
Ptr
<
FeaturesFinder
>
finder
;
//定義特徵尋找器
finder
=
new
SurfFeaturesFinder
();
//應用SURF方法尋找特徵
//finder = new OrbFeaturesFinder(); //應用ORB方法尋找特徵
vector
<
ImageFeatures
>
features
(
num_images
);
//表示影象特徵
for
(
int
i
=
0
;
i
<
num_images
;
i
++
)
(
*
finder
)(
imgs
[
i
],
features
[
i
]);
//特徵檢測
cout
<<
“特徵提取完畢”
<<
endl
;
vector
<
MatchesInfo
>
pairwise_matches
;
//表示特徵匹配資訊變數
BestOf2NearestMatcher
matcher
(
false
,
0。35f
,
6
,
6
);
//定義特徵匹配器,2NN方法
matcher
(
features
,
pairwise_matches
);
//進行特徵匹配
cout
<<
“特徵匹配完畢”
<<
endl
;
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
;
}
cout
<<
“相機引數預測完畢”
<<
endl
;
for
(
size_t
i
=
0
;
i
<
cameras
。
size
();
++
i
)
{
cout
<<
“第”
<<
i
<<
“焦距為”
<<
cameras
[
i
]。
focal
<<
endl
;
}
// 在一部可以計算重對映誤差,想辦法讓他可以輸出出來
Ptr
<
detail
::
BundleAdjusterBase
>
adjuster
;
//光束平差法,精確相機引數
//adjuster->setRefinementMask();
adjuster
=
new
detail
::
BundleAdjusterReproj
();
//重對映誤差方法
//adjuster = new detail::BundleAdjusterRay(); //射線發散誤差方法
adjuster
->
setConfThresh
(
1。0f
);
//設定匹配置信度,該值設為1
(
*
adjuster
)(
features
,
pairwise_matches
,
cameras
);
//精確評估相機引數
vector
<
Mat
>
rmats
;
for
(
size_t
i
=
0
;
i
<
cameras
。
size
();
++
i
)
//複製相機的旋轉引數
rmats
。
push_back
(
cameras
[
i
]。
R
。
clone
());
waveCorrect
(
rmats
,
WAVE_CORRECT_HORIZ
);
//進行波形校正
for
(
size_t
i
=
0
;
i
<
cameras
。
size
();
++
i
)
//相機引數賦值
cameras
[
i
]。
R
=
rmats
[
i
];
rmats
。
clear
();
//清變數
cout
<<
“利用光束平差法進行相機矩陣更新”
<<
endl
;
vector
<
Point
>
corners
(
num_images
);
//表示對映變換後圖像的左上角座標
vector
<
UMat
>
masks_warped
(
num_images
);
//表示對映變換後的影象掩碼
vector
<
UMat
>
images_warped
(
num_images
);
//表示對映變換後的影象
vector
<
Size
>
sizes
(
num_images
);
//表示對映變換後的影象尺寸
vector
<
UMat
>
masks
(
num_images
);
//表示源圖的掩碼
for
(
int
i
=
0
;
i
<
num_images
;
++
i
)
//初始化源圖的掩碼
{
masks
[
i
]。
create
(
imgs
[
i
]。
size
(),
CV_8U
);
//定義尺寸大小
masks
[
i
]。
setTo
(
Scalar
::
all
(
255
));
//全部賦值為255,表示源圖的所有區域都使用
}
Ptr
<
WarperCreator
>
warper_creator
;
//定義影象對映變換創造器
warper_creator
=
new
cv
::
SphericalWarper
();
//warper_creator = makePtr
//warper_creator = new cv::CylindricalWarper(); //柱面投影
//warper_creator = new cv::SphericalWarper(); //球面投影
//warper_creator = new cv::FisheyeWarper(); //魚眼投影
//warper_creator = new cv::StereographicWarper(); //立方體投影
//定義影象對映變換器,設定對映的尺度為相機的焦距,所有相機的焦距都相同
vector
<
double
>
focals
;
for
(
size_t
i
=
0
;
i
<
cameras
。
size
();
++
i
)
{
cout
<<
“第”
<<
i
<<
“焦距為”
<<
cameras
[
i
]。
focal
<<
endl
;
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
]);
else
warped_image_scale
=
static_cast
<
float
>
(
focals
[
focals
。
size
()
/
2
-
1
]
+
focals
[
focals
。
size
()
/
2
])
*
0。5f
;
Ptr
<
RotationWarper
>
warper
=
warper_creator
->
create
(
static_cast
<
float
>
(
warped_image_scale
));
for
(
int
i
=
0
;
i
<
num_images
;
++
i
)
{
Mat_
<
float
>
K
;
cameras
[
i
]。
K
()。
convertTo
(
K
,
CV_32F
);
//轉換相機內參數的資料型別
//對當前影象映象投影變換,得到變換後的影象以及該影象的左上角座標
corners
[
i
]
=
warper
->
warp
(
imgs
[
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
]);
}
imgs
。
clear
();
//清變數
masks
。
clear
();
cout
<<
“影象對映完畢”
<<
endl
;
//建立曝光補償器,應用增益補償方法
Ptr
<
ExposureCompensator
>
compensator
=
ExposureCompensator
::
createDefault
(
ExposureCompensator
::
GAIN
);
compensator
->
feed
(
corners
,
images_warped
,
masks_warped
);
//得到曝光補償器
for
(
int
i
=
0
;
i
<
num_images
;
++
i
)
//應用曝光補償器,對影象進行曝光補償
{
compensator
->
apply
(
i
,
corners
[
i
],
images_warped
[
i
],
masks_warped
[
i
]);
}
cout
<<
“影象曝光完畢”
<<
endl
;
//在後面,我們還需要用到對映變換圖的掩碼masks_warped,因此這裡為該變數新增一個副本masks_seam
vector
<
UMat
>
masks_seam
(
num_images
);
for
(
int
i
=
0
;
i
<
num_images
;
i
++
)
masks_warped
[
i
]。
copyTo
(
masks_seam
[
i
]);
Ptr
<
SeamFinder
>
seam_finder
;
//定義接縫線尋找器
//seam_finder = new NoSeamFinder(); //無需尋找接縫線
//seam_finder = new VoronoiSeamFinder(); //逐點法
//seam_finder = new DpSeamFinder(DpSeamFinder::COLOR); //動態規範法
//seam_finder = new DpSeamFinder(DpSeamFinder::COLOR_GRAD);
//圖割法
seam_finder
=
new
GraphCutSeamFinder
(
GraphCutSeamFinder
::
COST_COLOR
);
//seam_finder = new GraphCutSeamFinder(GraphCutSeamFinder::COST_COLOR_GRAD);
vector
<
UMat
>
images_warped_f
(
num_images
);
for
(
int
i
=
0
;
i
<
num_images
;
++
i
)
//影象資料型別轉換
images_warped
[
i
]。
convertTo
(
images_warped_f
[
i
],
CV_32F
);
images_warped
。
clear
();
//清記憶體
//得到接縫線的掩碼影象masks_seam
seam_finder
->
find
(
images_warped_f
,
corners
,
masks_seam
);
for
(
size_t
i
=
0
;
i
<
num_images
;
i
++
)
{
namedWindow
(
“mask_cut”
,
WINDOW_NORMAL
);
imshow
(
“mask_cut”
,
masks_seam
[
i
]);
waitKey
(
0
);
}
cout
<<
“拼縫最佳化完畢”
<<
endl
;
vector
<
Mat
>
images_warped_s
(
num_images
);
Ptr
<
Blender
>
blender
;
//定義影象融合器
blender
=
Blender
::
createDefault
(
Blender
::
NO
,
false
);
//簡單融合方法
//羽化融合方法
// blender = Blender::createDefault(Blender::FEATHER, false);
// //dynamic_cast多型強制型別轉換時候使用
// FeatherBlender* fb = dynamic_cast
// fb->setSharpness(0。005); //設定羽化銳度
// blender = Blender::createDefault(Blender::MULTI_BAND, false); //多頻段融合
// MultiBandBlender* mb = dynamic_cast
// mb->setNumBands(8); //設定頻段數,即金字塔層數
blender
->
prepare
(
corners
,
sizes
);
//生成全景影象區域
cout
<<
“生成全景影象區域”
<<
endl
;
vector
<
Mat
>
dilate_img
(
num_images
);
vector
<
Mat
>
masks_seam_new
(
num_images
);
Mat
tem
;
Mat
element
=
getStructuringElement
(
MORPH_RECT
,
Size
(
20
,
20
));
//定義結構元素
for
(
int
k
=
0
;
k
<
num_images
;
k
++
)
{
images_warped_f
[
k
]。
convertTo
(
images_warped_s
[
k
],
CV_16S
);
//改變資料型別
dilate
(
masks_seam
[
k
],
masks_seam_new
[
k
],
element
);
//膨脹運算
//對映變換圖的掩碼和膨脹後的掩碼相“與”,從而使擴充套件的區域僅僅限於接縫線兩側,其他邊界處不受影響
masks_warped
[
k
]。
copyTo
(
tem
);
masks_seam_new
[
k
]
=
masks_seam_new
[
k
]
&
tem
;
blender
->
feed
(
images_warped_s
[
k
],
masks_seam_new
[
k
],
corners
[
k
]);
//初始化資料
cout
<<
“處理完成”
<<
k
<<
“圖片”
<<
endl
;
}
masks_seam
。
clear
();
//清記憶體
images_warped_s
。
clear
();
masks_warped
。
clear
();
images_warped_f
。
clear
();
Mat
result
,
result_mask
;
//完成融合操作,得到全景影象result和它的掩碼result_mask
blender
->
blend
(
result
,
result_mask
);
imwrite
(
“result。jpg”
,
result
);
//儲存全景影象
return
0
;
}