全景图像拼接是指将同一场景中具有重叠区域的多幅图像进行缝合,最后构成一幅宽视野大幅图像的图像处理过程,它在无人机连续图像拍摄监控、遥感图像信息处理、航空军事侦察、大面积城市广场管控等领域都有重要应用。、
首先以两幅图像的拼接入手,以SIFT(Scale Invariant Feature Transformation)为特征点主要研究特征点的快速匹配策略,再通过RANSAC算法减少误匹配出现的次数等策略进行改进。
在进行图像拼接时,首先要解决的是找到图像之间的匹配的对应点。之前采用了SIFT算法来实现特征点的匹配,因为SIFT算子具有很强稳健性的描述算子,能够比其他描述算子,例如Harris角点,产生更少的错误匹配,但是该方法还没达到完美状态。
SIFT算法的具体内容参照之前的文章:SIFT算法详解。非完美的情况下,我们仍然需要用一种算法对SIFT算法产生的特征描述符进行剔除误匹配点,而RANSAN就是可以剔除错误匹配点的算法。
RANSAC算法的基本假设是样本中包含正确数据(inliers,可以被模型描述的数据),也包含异常数据(outliers,偏离正常范围很远、无法适应数学模型的数据),即数据集中含有噪声,合理的模型应该能够在描述正确数据点的同时摒弃噪声点。
一个简单的例子是从一组观测数据中找出合适的2维直线。假设观测数据中包含局内点和局外点,其中局内点近似的被直线所通过,而局外点远离于直线。简单的最小二乘法不能找到适应于局内点的直线,原因是最小二乘法尽量去适应包括局外点在内的所有点。相反,RANSAC能得出一个仅仅用局内点计算出模型,并且概率还足够高。但是,RANSAC并不能保证结果一定正确,为了保证算法有足够高的合理概率,我们必须小心的选择算法的参数。
RANSAC算法在SIFT特征筛选中的主要流程是:
(1) 从样本集中随机抽选一个RANSAC样本,即4个匹配点对。
(2) 根据这4个匹配点对计算变换矩阵M。
(3) 根据样本集,变换矩阵M,和误差度量函数计算满足当前变换矩阵的一致集consensus,并返回一致集中元素个数。
(4) 根据当前一致集中元素个数判断是否最优(最大)一致集,若是则更新当前最优一致集。
(5) 更新当前错误概率p,若p大于允许的最小错误概率则重复(1)至(4)继续迭代,直到当前错误概率p小于最小错误概率。
图像拼接代码:
# ch3_panorama_test.py
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 = ['../data/bed' + str(i + 1) + '.sift' for i in range(2)]
imname = ['../data/bed' + str(i + 1) + '.jpg' for i in range(2)]
# extract features and match
l = {}
d = {}
for i in range(2):
sift.process_image(imname[i], featname[i])
l[i], d[i] = sift.read_features_from_file(featname[i])
matches = {}
for i in range(1):
matches[i] = sift.match(d[i + 1], d[i])
# visualize the matches (Figure 3-11 in the book)
for i in range(1):
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)
这部分代码是进行SIFT的特征匹配,找出两种图片的对应匹配点,保存匹配点信息并且描述出匹配对应的信息。
# 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(0)
H_01 = homography.H_from_ransac(fp, tp, model)[0] # im 0 to 1
# warp the images
delta = 2000 # for padding and translation
im1 = array(Image.open(imname[0]), "uint8")
im2 = array(Image.open(imname[1]), "uint8")
im_12 = warp.panorama(H_01, im1, im2, delta, delta)
figure()
imshow(array(im_12, "uint8"))
axis('off')
savefig("example5.png", dpi=300)
show()
homography.H_from_ransac函数是求解两种图片之间的单应性矩阵,函数允许提供阈值和最小期望的点对数目。最重要的参数是最大迭代次数:程序退太早可能得到一个坏解;迭代次数太多会占用时间。函数返回结果是单应性矩阵和对应单应性矩阵的正确点对。
Sample1:中山纪念馆(室外、景深较小)
原图:
匹配图:
拼接图:
可以看出,在室外景深小的图像间,拼接得比较完美,该图除了后期的色差还未融合,其他地方的衔接都比较完美,效果较好。
Sample2:可乐与体育场(室外、景色较大)
原图:
在这组室外景色较大的图中可以看出,拼接的比较完美,但是还是可以明显看出裂缝,并且在裂缝的左右两边衔接部分点并不连续,而且裂缝一般不会出现在近景物体上,因为比起远景,近景物体的会因为角度变化产生比较大的形变,如果裂缝选在这个区域,会使拼接效果变差。
Sample3:宿舍的床(室内、景深小)
原图:
可以看到,在拼接图中,原图的左下部分已经消失了,这是因为在狭小的空间里,人眼和相机的视差都比较小,所以不同角度下的图细节差异比较大,导致拼接的时候左图扭曲到右图上必须舍弃一部分的细节,否则很难达到完整的规则图形。