基於RANSAC的圖像全景拼接
RANSAC算法
RANSAC是一種迭代算法,用來從觀測數據中估算出數學模型的參數,此基礎上便可以分離內群與離群數據。簡單來說就是一般來講觀測的數據里經常會出現很多噪音,比如說像SIFT匹配有時就會因為不同地方有類似的圖案導致匹配錯誤。而RANSAC就是通過反復取樣,也就是從整個觀測數據中隨機抽一些數據估算模型參數之后看和所有數據誤差有多大,然后取誤差最小視為最好以及分離內群與離群數據。
全景拼接原理介紹
- 針對某個場景拍攝多張/序列圖像
- 通過匹配特征(sift匹配)計算下一張圖像與上一張圖像之間的變換結構。
- 圖像映射,將下一張圖像疊加到上一張圖像的坐標系中
- 變換后的融合/合成
- 重復上述步驟
全景圖像拼接最重要的兩個步驟就是:
- 特征點匹配
這部分主要采用SIFT算法實現,之前的博客有介紹就不再詳細介紹了,主要是為了找到兩幅圖像相同的特征點並將其進行匹配。
- 圖片匹配
圖片匹配就是找到圖像之間所有重疊的部分,將其拼接后就能得到一幅全景圖。
圖像配准
圖像配准是對圖像進行變換,使變換后的圖像能夠在很好的拼接在上一張圖片的坐標系。為了能夠進行圖像對比和更精細的圖像分析,圖像配准是一步非常重要的操作因為圖片存在歪斜或兩張圖片的平面與平面之間景深不同(近大遠小),直接將兩張圖片進行映射變換會導致圖片中部分物體有重影現象(鬼影)。為了盡量減小這種情況,圖像配准算法將圖片划分成小塊的區域,分別在小塊區域中進行圖片的匹配和映射
全局優化和無縫銜接
- 全景圖矯直:矯正拍攝圖片時相機的相對3D旋轉,主要原因是拍攝圖片時相機很可能並不在同一水平線上,並且存在不同程度的傾斜,略過這一步可能導致全景圖變成波浪形狀。
- 圖像均衡補償:全局平衡所有圖片的光照和色調。
- 圖像頻段融合:步驟4之后仍然會存在圖像之間銜接邊緣、暈影效果(圖像的外圍部分的亮度或飽和度比中心區域低)、視差效果(因為相機透鏡移動導致)。
測試代碼:
# -*- 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 = ['E:\\test_pic\\pinjie\\' + str(i + 1) + '.sift' for i in range(5)] imname = ['E:\\test_pic\\pinjie\\' + 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()
數據集合圖片(5張)
特征匹配結果:
結果出現了歪斜,縫隙和黑幕,第一次運行的結果不理想
可以發現,圖片的拼接順序出現了錯誤,最右側的圖片與左側的圖片直接相連接,調過來中間的圖片,導致了三張圖片堆積在左邊,而右側缺失了兩張圖片
大致猜想是因為5張圖片的范圍過小,導致每張圖片之間的重疊部分過多,導致每張圖片都有大量的特征點匹配,導致圖片重疊,造成缺失。
建議圖片范圍取大一些,不要有太多的重疊部分。
調整照片后再次進行拼接:
圖片沒有縫隙,很平滑,也沒有明顯的曝光,對比原圖,位置是正確的,廣告牌也拼接上了,但是左側圖片被拉伸了,雖然拉伸程度不大。可能是因為位移量不夠,所以拉伸了圖片進行填充
重新拍照后測試:
沒有發生圖片錯位,但是扭曲拉伸嚴重,大體上完成了拼接,不過效果一般,存才鬼影現象,比較模糊,提升原圖清晰度可以有效改善此結果
遠近不同圖片的拼接
測試圖片(2張)
遠景:
近景:
運行結果:
遠景中的建築物也被拼接進了近景中的圖片,位置也是正確的,且沒有明顯的曝光的痕跡,相較於上一組實驗,這組實驗的照片較為穩定和清晰,特征點匹配的准確,拼接的順滑,沒有縫隙
總結
- 圖像配准雖然能夠較好地完成配准,但非常依賴於特征點對。若圖像高頻信息較少,特征點對過少,配准將完全失效,並且對大尺度的圖像進行配准,其效果也不是很好,特征點對的數量對拼接效果有很大的影響。
- 拍攝時曝光不均導致畫面分割明顯
- 拼接圖像部分歪斜,可能是因為拍攝時角度的偏差,導致當該圖像填充時目標圖像中點的坐標也變化了。
- 原始圖片的像素值很大程度上決定了特征點的尋找,像素值太低,將導致尋找到的特征點過少,不利於后續的拼接效果,而像素值過高會導致代碼的運行時間過長,加重算法的負荷,運行時間長達5分鍾...個人建議別超過1M,不然真的運行太慢了,當然要是電腦好就當我沒說
-
全景拼接的一個關鍵點就是sift特征點的匹配,只有尋找到正確且數量較多的特征點,在后續的拼接中才能更好的完成,拼接的更自然。
-
基於RANSAC算法的圖像拼接存在的問題可以從上面的拼接結果看出來,不同圖像之間的連接部分出現不連貫以及由於圖像曝光不同而出現的邊緣效應的情況。而且還可以看出RANSAC算法只是將圖像進行簡單的扭曲(從矩形變成四邊形),導致圖像拼接得非常生硬
- 最好不要拍攝對稱性很高的建築物,以為兩邊的的特征點長的幾乎一樣的,這樣會使算法的匹配出現失誤,第一組實驗就是間隔太小,導致最右側的圖片拼接到了最左邊。
報錯處理
若代碼出現次錯誤,進入到PCV\geometry\warp.py文件中,
1.將第一行的
import matplotlib.delaunay as md
替換成
from scipy.spatial import Delaunay
2.把以下代碼
triangulate_points(x,y)
里面的代碼替換成
tri = Delaunay(np.c_[x,y]).simplices
這樣就能運行了