Opencv使用Stitcher類圖像拼接生成全景圖像
Opencv中自帶的Stitcher類可以實作全景圖像,效果不錯。下邊的例子是Opencv Samples中的stitching.cpp的簡化,源檔案可以在這個路徑裡找到:
\opencv\sources\samples\cpp\stitching.cpp
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
圖1:
圖2:
圖3:
圖4:
圖5:
5個圖檔的拍攝角度合起來在180°左右,沒有經過壓縮的,下載下傳下來可以直接測試使用,傳入順序随意,Stitcher會自動排列。全景拼接效果很贊:
下面是python的:
一、拼接介紹
在同一位置拍攝的兩幅或多幅圖像是單應性相關的。我們可以使用該限制将很多圖像拼接起來,拼成一幅大的圖像來建立全景圖像
全景圖像拼接最重要的兩個步驟就是:
(一)特征點比對
這部分主要采用SIFT算法實作,之前的部落格有介紹就不再詳細介紹了,主要是為了找到兩幅圖像相同的特征點并将其進行比對。
(二)圖檔比對
圖檔比對就是找到圖像之間所有重疊的部分,将其拼接後就能得到一幅全景圖。
基本原理
1.單應性矩陣
定義:在計算機視覺領域,空間同一平面的任意兩幅圖像被單應矩陣聯系着(假設在針孔相機模型中),即一個相機拍攝空間同一平面的兩張圖像,這兩張圖像之間的映射關系可以用單應矩陣表示。
在兩視幾何中,也可以這樣了解,兩架相機拍同一空間上得到兩幅圖像A、B,其中圖像A到圖像B存在一種變換,而且這種變換是一一對應的關系,這個變換矩陣用單應矩陣表示。OpenCV中可以用函數findHomography計算得到單應矩陣H。
要實作兩張圖檔的簡單拼接,其實隻需找出兩張圖檔中相似的點 (至少四個,因為 homography 矩陣的計算需要至少四個點), 計算一張圖檔可以變換到另一張圖檔的變換矩陣 (homography 單應性矩陣),用這個矩陣把那張圖檔變換後放到另一張圖檔相應的位置 ( 就是相當于把兩張圖檔中定好的四個相似的點給重合在一起)。如此,就可以實作簡單的全景拼接。當然,因為拼合之後圖檔會重疊在一起,是以需要重新計算圖檔重疊部分的像素值,否則結果會很難看。
2.RANSAC算法
RANSAC(Random Sample Consensus)即随機采樣一緻性,該方法是用來找到正确模型來拟合帶有噪聲資料的疊代方法。給定一個模型,例如點集之間的單應性矩陣,RANSAC的基本思想在于,找到正确資料點的同時摒棄噪聲點。
3.利用RANSAC算法求解單應性矩陣
雖然SIFT是具有很強穩健性的描述子,當這方法仍遠非完美,還會存在一些錯誤的比對。而單應性矩陣需要選取4對特征點計算,萬一選中了不正确的比對點,那麼計算的單應性矩陣肯定是不正确的。是以,為了提高計算結果的魯棒性,我們下一步就是要把這些不正确的比對點給剔除掉,獲得正确的單應性矩陣。在這裡使用了RANSAC算法:随機抽取不同的4對特征比對坐标(在圖1中随機抽取4個特征坐标,以及這4個特征坐标在圖2中比對的4個特征坐标,組成4對特征比對坐标),利用這4對特征比對坐标計算出矩陣H1(3x3的一個矩陣,圖2經過矩陣變換後,可以把圖2映射到圖1的坐标空間中,再将圖2進行簡單的平移即可與圖1實作無縫拼接),再将圖2中所有特征比對點經過該透視矩陣H1映射到圖1的坐标空間,然後與圖1比對點實際坐标求歐氏距離(就是為了驗證計算出來的這個H1矩陣是否滿足絕大多數特征比對點);之後重複上面内容,再随機抽取不同的四組特征比對坐标,再計算透視矩陣H2,再求歐式距離,如此重複多次。最後以歐式距離最小的那個透視矩陣(表示這個特征矩陣H滿足最多的特征比對點,它最優秀)作為最終計算結果。
4.圖檔融合
在用計算出的變換矩陣對其中一張圖做變換,然後把變換的圖檔與另一張圖檔重疊在一起,并重新計算重疊區域新的像素值。對于計算重疊區域的像素值,其實可以有多種方法去實作一個好的融合效果,這裡就用最簡單粗暴的但效果也不錯的方式。直白來說就是實作一個圖像的線性漸變,對于重疊的區域,靠近左邊的部分,讓左邊圖像内容顯示的多一些,靠近右邊的部分,讓右邊圖像的内容顯示的多一些。用公式表示就是,假設 alpha 表示像素點橫坐标到左右重疊區域邊界橫坐标的距離,新的像素值就為 newpixel = 左圖像素值 × (1 - alpha) + 右圖像素值 × alpha 。這樣就可以實作一個簡單的融合效果。
2代碼及運作結果
from pylab import *
from numpy import *
from PIL import Image
# If you have PCV installed, these imports should work
from PCV.geometry import homography, warp
from PCV.localdescriptors import sift
"""
This is the panorama example from section 3.3.
"""
# set paths to data folder
featname = ['C:\\Users\DELL\Desktop\PCV\jmu\panorama/z0'+str(i+1)+'.sift' for i in range(5)]
imname = ['C:\\Users\DELL\Desktop\PCV\jmu\panorama/z0'+str(i+1)+'.jpg' for i in range(5)]
# extract features and match
l = {}
d = {}
for i in range(5):
sift.process_image(imname[i],featname[i])
l[i],d[i] = sift.read_features_from_file(featname[i])
matches = {}
for i in range(4):
matches[i] = sift.match(d[i+1],d[i])
# visualize the matches (Figure 3-11 in the book)
'''
for i in range(4):
im1 = array(Image.open(imname[i]))
im2 = array(Image.open(imname[i+1]))
figure()
sift.plot_matches(im2,im1,l[i+1],l[i],matches[i],show_below=True)
'''
# function to convert the matches to hom. points
def convert_points(j):
ndx = matches[j].nonzero()[0]
fp = homography.make_homog(l[j+1][ndx,:2].T)
ndx2 = [int(matches[j][i]) for i in ndx]
tp = homography.make_homog(l[j][ndx2,:2].T)
# switch x and y - TODO this should move elsewhere
fp = vstack([fp[1],fp[0],fp[2]])
tp = vstack([tp[1],tp[0],tp[2]])
return fp,tp
# estimate the homographies
model = homography.RansacModel()
fp,tp = convert_points(1)
H_12 = homography.H_from_ransac(fp,tp,model)[0] #im 1 to 2
fp,tp = convert_points(0)
H_01 = homography.H_from_ransac(fp,tp,model)[0] #im 0 to 1
tp,fp = convert_points(2) #NB: reverse order
H_32 = homography.H_from_ransac(fp,tp,model)[0] #im 3 to 2
tp,fp = convert_points(3) #NB: reverse order
H_43 = homography.H_from_ransac(fp,tp,model)[0] #im 4 to 3
# warp the images
delta = 500 # for padding and translation
im1 = array(Image.open(imname[1]), "uint8")
im2 = array(Image.open(imname[2]), "uint8")
im_12 = warp.panorama(H_12,im1,im2,delta,delta)
im1 = array(Image.open(imname[0]), "f")
im_02 = warp.panorama(dot(H_12,H_01),im1,im_12,delta,delta)
im1 = array(Image.open(imname[3]), "f")
im_32 = warp.panorama(H_32,im1,im_02,delta,delta)
im1 = array(Image.open(imname[4]), "f")
im_42 = warp.panorama(dot(H_32,H_43),im1,im_32,delta,2*delta)
figure()
imshow(array(im_42, "uint8"))
axis('off')
show()
因為相機和光照強度的差異,會造成一幅圖像内部,以及圖像之間亮度的不均勻,拼接後的圖像會出現明暗交替,這樣給觀察造成極大的不便。室外場景的拼接效果比室内的要好很多,多圖拼接對圖檔的要求也比較高,差異性大或太小(幾乎相同)的拼接效果都很差。