Opencv使用Stitcher类图像拼接生成全景图像
Opencv中自带的Stitcher类可以实现全景图像,效果不错。下边的例子是Opencv Samples中的stitching.cpp的简化,源文件可以在这个路径里找到:
\opencv\sources\samples\cpp\stitching.cpp
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
图1:
图2:
图3:
图4:
图5:
5个图片的拍摄角度合起来在180°左右,没有经过压缩的,下载下来可以直接测试使用,传入顺序随意,Stitcher会自动排列。全景拼接效果很赞:
下面是python的:
一、拼接介绍
在同一位置拍摄的两幅或多幅图像是单应性相关的。我们可以使用该约束将很多图像拼接起来,拼成一幅大的图像来创建全景图像
全景图像拼接最重要的两个步骤就是:
(一)特征点匹配
这部分主要采用SIFT算法实现,之前的博客有介绍就不再详细介绍了,主要是为了找到两幅图像相同的特征点并将其进行匹配。
(二)图片匹配
图片匹配就是找到图像之间所有重叠的部分,将其拼接后就能得到一幅全景图。
基本原理
1.单应性矩阵
定义:在计算机视觉领域,空间同一平面的任意两幅图像被单应矩阵联系着(假设在针孔相机模型中),即一个相机拍摄空间同一平面的两张图像,这两张图像之间的映射关系可以用单应矩阵表示。
在两视几何中,也可以这样理解,两架相机拍同一空间上得到两幅图像A、B,其中图像A到图像B存在一种变换,而且这种变换是一一对应的关系,这个变换矩阵用单应矩阵表示。OpenCV中可以用函数findHomography计算得到单应矩阵H。
要实现两张图片的简单拼接,其实只需找出两张图片中相似的点 (至少四个,因为 homography 矩阵的计算需要至少四个点), 计算一张图片可以变换到另一张图片的变换矩阵 (homography 单应性矩阵),用这个矩阵把那张图片变换后放到另一张图片相应的位置 ( 就是相当于把两张图片中定好的四个相似的点給重合在一起)。如此,就可以实现简单的全景拼接。当然,因为拼合之后图片会重叠在一起,所以需要重新计算图片重叠部分的像素值,否则结果会很难看。
2.RANSAC算法
RANSAC(Random Sample Consensus)即随机采样一致性,该方法是用来找到正确模型来拟合带有噪声数据的迭代方法。给定一个模型,例如点集之间的单应性矩阵,RANSAC的基本思想在于,找到正确数据点的同时摒弃噪声点。
3.利用RANSAC算法求解单应性矩阵
虽然SIFT是具有很强稳健性的描述子,当这方法仍远非完美,还会存在一些错误的匹配。而单应性矩阵需要选取4对特征点计算,万一选中了不正确的匹配点,那么计算的单应性矩阵肯定是不正确的。因此,为了提高计算结果的鲁棒性,我们下一步就是要把这些不正确的匹配点给剔除掉,获得正确的单应性矩阵。在这里使用了RANSAC算法:随机抽取不同的4对特征匹配坐标(在图1中随机抽取4个特征坐标,以及这4个特征坐标在图2中匹配的4个特征坐标,组成4对特征匹配坐标),利用这4对特征匹配坐标计算出矩阵H1(3x3的一个矩阵,图2经过矩阵变换后,可以把图2映射到图1的坐标空间中,再将图2进行简单的平移即可与图1实现无缝拼接),再将图2中所有特征匹配点经过该透视矩阵H1映射到图1的坐标空间,然后与图1匹配点实际坐标求欧氏距离(就是为了验证计算出来的这个H1矩阵是否满足绝大多数特征匹配点);之后重复上面内容,再随机抽取不同的四组特征匹配坐标,再计算透视矩阵H2,再求欧式距离,如此重复多次。最后以欧式距离最小的那个透视矩阵(表示这个特征矩阵H满足最多的特征匹配点,它最优秀)作为最终计算结果。
4.图片融合
在用计算出的变换矩阵对其中一张图做变换,然后把变换的图片与另一张图片重叠在一起,并重新计算重叠区域新的像素值。对于计算重叠区域的像素值,其实可以有多种方法去实现一个好的融合效果,这里就用最简单粗暴的但效果也不错的方式。直白来说就是实现一个图像的线性渐变,对于重叠的区域,靠近左边的部分,让左边图像内容显示的多一些,靠近右边的部分,让右边图像的内容显示的多一些。用公式表示就是,假设 alpha 表示像素点横坐标到左右重叠区域边界横坐标的距离,新的像素值就为 newpixel = 左图像素值 × (1 - alpha) + 右图像素值 × alpha 。这样就可以实现一个简单的融合效果。
2代码及运行结果
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 = ['C:\\Users\DELL\Desktop\PCV\jmu\panorama/z0'+str(i+1)+'.sift' for i in range(5)]
imname = ['C:\\Users\DELL\Desktop\PCV\jmu\panorama/z0'+str(i+1)+'.jpg' for i in range(5)]
# extract features and 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
# warp the images
delta = 500 # 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()
因为相机和光照强度的差异,会造成一幅图像内部,以及图像之间亮度的不均匀,拼接后的图像会出现明暗交替,这样给观察造成极大的不便。室外场景的拼接效果比室内的要好很多,多图拼接对图片的要求也比较高,差异性大或太小(几乎相同)的拼接效果都很差。