一、圖像拼接原理介紹
- 圖像拼接技術簡單的理解就是將數張有重疊部分的圖像拼成一幅無縫的全景圖或高分辨率圖像的技術。這些有重疊部分的圖像可能是不同時間、不同視角或者不同傳感器獲得的圖像。目前,圖像拼接在醫學成像、計算機視覺、衛星數據、軍事目標自動識別等領域具有重要意義。圖像拼接的輸出是兩個輸入圖像的並集。
- 圖像配准和圖像融合是圖像拼接的兩個關鍵技術。圖像配准是圖像融合的基礎,而且圖像配准算法的計算量一般非常大,因此圖像拼接技術的發展很大程度上取決於圖像配准技術的創新。早期的圖像配准技術主要采用點匹配法,這類方法速度慢、精度低,而且常常需要人工選取初始匹配點,無法適應大數據量圖像的融合。圖像拼接的方法很多,不同的算法步驟會有一定差異,但大致的過程是相同的。
- 在圖像拼接中首先利用SIFT算法提取圖像特征進而進行特征匹配,繼而使用RANSAC算法對特征匹配的結果進行優化,接着利用圖像變換結構進行圖像映射,最終進行圖像融合。
- 在圖像拼接過程中,運用SIFT局部描述算子檢測圖像中的關鍵點和特征,SIFT特征是基於物體上的一些局部外觀的興趣點而與影像的大小和旋轉無關。對於光線、噪聲、些微視角改變的容忍度也相當高,所以用來檢測要拼接圖像的特征及關鍵點就很有優勢。而接下來即步驟三是找到重疊的圖片部分,連接所有圖片之后就可以形成一個基本的全景圖了。匹配圖片最常用的方式是采用RANSAC( 隨機抽樣一致),用此排除掉不符合大部分幾何變換的匹配。之后利用這些匹配的點來估算單應矩陣”,也就是將其中一張圖像通過關聯性和另一張匹配。
二、RANSAC算法講解
- RANSAC算法便是用來找到正確模型來擬合帶有噪聲數據的迭代方法。此基礎上便可以分離內群與離群數據。簡單來說就是一般來講觀測的數據里經常會出現很多噪音,比如說像SIFT匹配有時就會因為不同地方有類似的圖案導致匹配錯誤。RANSAC通過反復取樣,也就是從整個觀測數據中隨機抽一些數據估算模型參數之后看和所有數據誤差有多大,然后取誤差最小視為最好以及分離內群與離群數據。基本的思想是,數據中包含正確的點和噪聲點,合理的模型應該能夠在描述正確數據點的同時摒棄噪聲點。
三、圖像拼接步驟
- 對每幅圖進行特征點提取
- 對對特征點進行匹配
- 進行圖像配准
- 把圖像拷貝到另一幅圖像的特定位置
- 對重疊邊界進行特殊處理,采用APAP之類的算法,對齊特征點,即就是圖像配准。
- 通過圖割方法,自動選取拼接縫。
- 根據blending策略實現融合。
四、圖像融合(去裂縫處理)
- 圖像融合是圖像拼接的兩個關鍵技術。圖像配准是圖像融合的基礎,而且圖像配准算法的計算量一般非常大,因此圖像拼接技術的發展很大程度上取決於圖像配准技術的創新。早期的圖像配准技術主要采用點匹配法,這類方法速度慢、精度低,而且常常需要人工選取初始匹配點,無法適應大數據量圖像的融合。圖像拼接的方法很多,不同的算法步驟會有一定差異,但大致的過程是相同的。圖像拼接完會發現在拼接的交界處有明顯的銜接痕跡,存在邊緣效應,因為光照色澤的原因使得圖片交界處的過渡很糟糕,所以需要特定的處理解決這種不自然。那么這時候可以采用multi-band blending方法。multi-band blending是目前圖像融和方面比較好的方法,拼接之后的圖像進行融合的效果是很好的。
五、實驗內容
1.視差變化小的場景
- 針對固定點位拍攝多張圖片,以中間圖片為中心,實現圖像的拼接融合
成功案例
數據集:
特征匹配結果:
全景拼接結果:
分析:
- 從全景拼接結果中可以看出拼接圖片沒有縫隙,很平滑,也沒有明顯的曝光,和原景對比,位置是正確的。
- 在拍攝圖片的時候,樓房前面的樹枝是沒有拍整齊的,但是在通過拼接后也拼接上了。
- 但是圖片有被拉伸,左側也有明顯的向右傾斜。可能是因為位移量不夠,所以拉伸了圖片進行填充。左側向右傾斜,主要是與我粗糙的拍攝手法有關。
- 圖片旁出現了黑色部分,是因為圖片無法填充滿,可以試試拍攝時角度變化大一些。效果會更好。
失敗案例
數據集:
全景拼接結果:
分析:
- 運行的結果很不理想,扭曲拉伸嚴重。
- 拼接結果出現了歪斜,縫隙和黑幕。
- 失敗的原因可能是我拍攝的角度太單一。
- 拍攝圖片有大量相同的特征點,而且重疊部分過少,范圍過小。導致每張圖片之間的重疊部分過多,導致每張圖片都有大量的特征點匹配,圖片重疊嚴重,從而造成缺失。建議圖片范圍取大一些,不易失敗。
2. 視差變化大的場景
- 即 針對同一場景,選取視差變化大的場景,也就是有近景目標,更換拍攝位置,分析拼接結果
數據集:
特征匹配結果:
全景拼接結果:
分析:
- 從以上全景拼接圖中可以看出遠景中的建築物也被拼接進了近景中的圖片,位置是正確的,但是拼接效果並不好!
- 兩圖的拼接並不自然,原因就在於拼接圖的交界處,兩圖因為光照色澤的原因使得兩圖交界處的過渡也很糟糕。這里的處理思路是加權融合,在重疊部分由前一幅圖像慢慢過渡到第二幅圖像,即將圖像的重疊區域的像素值按一定的權值相加合成新的圖像。
- 存在有曝光的痕跡,也存有縫隙,左側存在大塊黑幕
- 出現黑幕的主要原因是拍攝視角小,導致拼接后無法填充滿。
- 拼接的過程中有較明顯的拼接縫。可能是因為圖片的曝光率相差過大,因此拼接縫會更明顯。
- 導致該拼接結果不夠好的原因除了與算法本身有關的情況外,我覺得與拍攝角度也是有很大的關系。
四、實驗總結
- 所選圖像有比較多的重疊部分是拼接的基本要求。
- 拍攝時曝光不均很容易導致畫面分割明顯
- 正確的特征點對全景拼接十分關鍵,只有尋找到正確且數量較多的特征點,才能更好的完成后續的拼接,讓圖像拼接的更自然。
五、代碼展示
# -*- coding: utf-8 -*- 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. """ # 設置數據文件夾的路徑 featname = ['D:\\計算機視覺\\SIFT特征匹配圖片庫\\' + str(i + 1) + '.sift' for i in range(5)] imname = ['D:\\計算機視覺\\SIFT特征匹配圖片庫\\' + str(i + 1) + '.jpg' for i in range(5)] # 提取特征並匹配使用sift算法 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]) # 可視化匹配 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) # 將匹配轉換成齊次坐標點的函數 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 # 估計單應性矩陣 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 # 扭曲圖像 delta = 2000 # 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()