在同一位置(即图像的照相机位置相同)拍摄的两幅图片或者多幅图片是单应性相关的。我们经常使用该约束将很多图像缝补起来,拼成一个大的图像来创建全景图像。
具体步骤
实现全景拼接,本文使用RANSAC方法,其基本思想是,数据中包含正确的点和噪声点,合理的模型应该能够在描述正确数据点的同时摒弃噪声点。该方法能解决错配问题。创建全景图像步骤大致分为以下几点:
1.在连续图像对间使用sift特征寻找匹配对应点对
2.进行可视化匹配,匹配两幅图像之间的特征
3.将匹配转换成齐次坐标点的函数,估计单应性矩阵
4.通过估计出图像间的单应性矩阵,将所有的图像扭曲到一个公共的图像平面上
其中sift特征点匹配在前面博客已经说过,我们主要实现稳健的单应性矩阵估计和图像拼接。
稳健的单应性矩阵估计
我们使用RANSAC算法来求解单应性矩阵。使用由ransac.py选择的4个对应点对(4个点对是计算单应性矩阵所需的最少数目,因为 homography 矩阵的计算需要至少四个点),然后拟合成一个单应性矩阵。对每个对应点对使用该单应性矩阵,然后返回相应的平方距离之和。因此RANSAC算法能够判定哪些点对是正确的,哪些是错误的。
图像拼接
估计出图像间的单应性矩阵,我们需要将所有的图像扭曲到一个公共的图像平面上。我们可以将中心图像左边或者右边的区域填充为0,以便为扭曲的图像腾出空间。
以下是代码:
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 = ['Univ'+str(i+1)+'.sift' for i in range(5)]
imname = ['Univ'+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()
以下是运行结果:
在实现代码过程中,首先找到 PCV\PCV-master\PCV\tools 文件夹下的ransac.py文件,打开并找到90行,在print后加括号
然后重装PCV(在PCV\PCV-master的文件夹下找到setup.py文件,按下左shift键和鼠标右键,选择打开命令窗口,输入python setup.py install重装pcv包)。
正常情况下,代码都可以运行。
实验小结
1.RANSAC方法是用来找到正确模型来拟合带有噪声数据的迭代方法,可以解决错配问题。但当特征点概念达到非常低的情况下,这个算法基本算不出来。
2.代码运行时间会比较长,若想要运行时间短的话,可以相对调低下图片的分辨率。
3.实验中的五张图片是站在同一个点拍的,若图片的拉伸程度较大且特征建筑物比较远的话,图片拼接的效果不太好。