图像拼接技术就是将数张有重叠部分的图像(可能是不同时间、不同视角或者不同传感器获得的)拼成一幅无缝的全景图或高分辨率图像的技术。
图像配准(image alignment)和图像融合是图像拼接的两个关键技术。图像配准是图像融合的基础,而且图像配准算法的计算量一般非常大,因此图像拼接技术的发展很大程度上取决于图像配准技术的创新。早期的图像配准技术主要采用点匹配法,这类方法速度慢、精度低,而且常常需要人工选取初始匹配点,无法适应大数据量图像的融合。图像拼接的方法很多,不同的算法步骤会有一定差异,但大致的过程是相同的。
全景图像拼接技术是数字图像处理领域的一个重要的研究方向,在文物保护(古籍或古文字资料保护)、计算机视觉、遥感图像处理、医学图像分析、计算机图形学等领域有着重要的应用价值。 一个完整的图像拼接过程主要包括图像获取,图像配准,投影,图像融合等几个步骤,而其中的图像配准和图像融合是关键,若是多幅图像的拼接,还得考虑图像的排序。
RANSAC 是“RANdom SAmple Consensus”(随机一致性采样)的缩写。该方法是用来找到正确模型来拟合带有噪声数据的迭代方法。给定一个模型,例如点集之间的单应性矩阵,RANSAC 基本的思想是,数据中包含正确的点和噪声点,合理的模型应该能够在描述正确数据点的同时摒弃噪声点。
RANSAC 的标准例子:用一条直线拟合带有噪声数据的点集。简单的最小二乘在该例子中可能会失效,但是 RANSAC 能够挑选出正确的点,然后获取能够正确拟合的直线。
OpenCV中滤除误匹配对采用RANSAC算法寻找一个最佳单应性矩阵H,矩阵大小为3×3。RANSAC目的是找到最优的参数矩阵使得满足该矩阵的数据点个数最多,通常令h33=1h33=1来归一化矩阵。由于单应性矩阵有8个未知参数,至少需要8个线性方程求解,对应到点位置信息上,一组点对可以列出两个方程,则至少包含4组匹配点对。
在图像拼接融合的过程中,受客观因素的影响,拼接融合后的图像可能会存在“鬼影现象”以及图像间过度不连续等问题。下图就是图像拼接的一种“鬼影现象”。解决鬼影现象可以采用APAP算法。
算法流程:
估计出图像间的单应性矩阵(使用 RANSAC 算法),现在我们需要将所有的图像扭曲到一个公共的图像平面上。通常,这里的公共平面为中心图像平面(否则,需要进行大量变形)。一种方法是创建一个很大的图像,比如图像中全部填充0,使其和中心图像平行,然后将所有的图像扭曲到上面。由于我们所有的图像是由照相机水平旋转拍摄的,因此我们可以使用一个较简单的步骤:将中心图像左边或者右边的区域填充0,以便为扭曲的图像腾出空间。
对于通用的geometric__transform()函数,我们需要指定能够描述像素到像素间映射的函数。transf()函数就是该指定的函数。该函数通过将像素和H相乘,然后对齐次坐标进行归一化来实现像素间的映射。通过查看H中的平移量,我们可以决定应该将该图像填补到左边还是右边。当该图像填补到左边时,由于目标图像中点的坐标也变化了,所以在“ 左边”情况中,需要在单应性矩阵中加入平移。简单起见,我们同样使用0像素的技巧来寻找alpha图。
图像曝光不同,在单个图像的边界上存在边缘效应。商业的创建全景图像软件里有额外的操作来对强度进行归一化,并对平移进行平滑场景转换,以使得结果看上去更好。
整体流程:
实验一:固定点位拍摄5张图片,以中间图片为中心,实现图像的拼接融合
实验结果:
下图为使用Photoshop软件拼接的图片:(与现实场景无较大差异)
小结:在第一次实验中,没有考虑到图片的顺序对拼接的结果产生的影响,导致拼接后的图片非常奇怪,之后发现图片应该是从右到左的顺序。通过两张图片的对比,可以很明显的看出他们的差别。实验得出的图片,建筑发生了略微的变形,植物部分缺失,一小部分建筑的位置发生变化,特别是右下角变化显著,图片与图片拼接的缝隙也非常明显。
实验二:针对同一场景,更换拍摄位置
实验结果:
下图为使用Photoshop软件拼接的图片:
小结:变换位置之后,可以明显发现图片的拼接效果变差,地板砖没有对齐,花盆也出现了两个,植物的叶子也参差不齐,由此可见位置的变换对全景图片的拼接有较大的影响。
实验三
实验结果:
小结:这次实验是两张远景的图片,实验结果较为理想,特别是在图片下方建筑的拼接上较为成功,但天空的地方两张图片的差异就比较明显。
代码
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 = ['D:/data/Univ'+str(i+1)+'.sift' for i in range(5)]
#imname = ['D:/data/Univ'+str(i+1)+'.jpg' for i in range(5)]
featname = ['C:/Users/Desktop/pi1/'+str(i+1)+'.sift' for i in range(5)]
imname = ['C:/Users/Desktop/pi1/'+str(i+1)+'.jpg' for i in range(5)]
# extract features and m
# 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
# 扭曲图像
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()
相对于实验二,实验一的实验结果较为理想,说明固定位置下的图像拼接比变换位置较为成功。实验一中也没有出现“鬼影”现象,只是在图像的拼接的拼接缝上还较为粗糙,有些会对不上,而为了使一些场景能够拼接,会使另一些场景被舍弃,从而发生变形。