Python计算机视觉编程_03
基于SIFT算法的全景拼接
- 前言
- 1.单应性变换
- 2.RANSAC算法
- 3.Multi-Band Blending策略
- 4.代码实现
前言
请解释什么是全景拼接?简而言之,它是将多幅相互覆盖的图像整合为一个综合图像。

从下图可以看出,在处理过程中七幅各具特色的图像经过一系列处理后最终融合成一个整体的画面。那么关键的问题就转化为如何实现这一效果了?
1.单应性变换
如果是最简单的图像拼接,则显然只需对其进行一次平移处理,并对重叠区域进行叠加运算即可轻松获得拼接后的图像。然而,在实际应用中,默认情况下两幅原始图象之间可能存在多种相互关系涵盖但不限于:平移操作、旋转操作、尺度变换操作、仿射变换操作以及透视映射运算等多种类型。


左边的图像是右边两幅图像的组合。可以看出,在这两幅图像之间不仅包含平移操作,并且还包含旋转以及拉伸等其他变形。将这两副图进行处理后得到目标平面图像的过程就是单应性变换。

该表格展示了各种变换类型所涉及的未知参数数量;为了更好地理解这一过程,请举平移为例说明两张相互通过平移操作关联的图像该如何拼接。

如图所示,在两幅图像之间如何建立映射关系?在理论层面而言,只要特征点匹配完全准确,则只需一对匹配即可确定映射关系。例如取点对(25,25)与(50,50),将右图置于左图之上,并通过平移操作将其重合。具体而言,在此过程中需依次执行以下操作:首先将右图置于左图之上;接着向左平移25像素单位;随后向下移动25像素单位即可完成两张图像的拼接过程。然而,在实际应用中完全正确的特征点配准通常仅存在于理想模型中。因此我们需要引入一个评价标准——误差函数——来衡量每对映射的质量并选择最优配准方案。而这个评价标准采用的是广为人知的最小二乘法原理。至于最小二乘法的具体实现细节这里就不展开了有兴趣的朋友可以自行查阅相关资料很多大牛都有非常清晰的讲解

显然,在数量上而言,在我们面临8个位置参数的情况下(即具体来说是8个),为了能够建立起一种映射关系(即建立这种对应关系),如果不做进一步优化的话(即即便不做进一步优化),这将会导致计算资源的巨大消耗(即会占用大量的计算资源)。因此为了提高效率并减少资源消耗(为了实现这一目标),我们选择了采用RANSAC算法来进行优化求解(即采用该算法来进行求解)。
2.RANSAC算法
RANSAC通过优化方法降低了计算复杂度,并通过缩小匹配点范围进一步提升了映射函数的精度。具体流程如下:
- 随机抽取四组特征点对
- 应用直接线性变换解法DLT计算单应矩阵H(唯一解)
- 对所有匹配点计算映射误差ε=||pi’, Hpi|| ,例如在A和B图像中将A图中的a点经过H变换得到a’后与B图中的b对应点相减得到对应误差。
- 依据设定误差阈值筛选inliers(如误差在3-5像素范围内)
- 根据最大inliers集合重新计算单应矩阵H
例如在实际应用中正确的与错误的比例通常为10:1甚至更高。
3.Multi-Band Blending策略
尽管使用RANSAC算法能够迅速获得理想的映射策略,但在实际应用中,由于时间与空间的差异性,不同图像之间的光照程度存在明显差异,从而导致明显的分界线——拼接缝十分明显.这一界限通常被称为拼接缝,其产生原因虽各不相同但基本上归因于同一原因——光照.
以下是Multi-Band Blending的具体流程:
1.计算两幅图像的拉普拉斯金字塔
2.计算高斯金字塔(用于拼接左右两幅图像),其具备尺度不变性
3.计算拼接结果得到resultLapPyr的结果金字塔
4.通过逐层插值放大并累加的方式构建 blend 图像(reconstructImgFromLapPyramid)
拉普拉斯金字塔可被视为高斯金字塔的一种逆向过程





4.代码实现
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
np.seterr(invalid='ignore')
"""
This is the panorama example from section 3.3.
"""
# 设置数据文件夹的路径
featname = ['D:\ vscode\ python\ .vscode\ jpg\ ' + str(i + 1) + '.sift' for i in range(3)]
imname = ['D:\ vscode\ python\ .vscode\ jpg\ ' + str(i + 1) + '.jpg' for i in range(3)]
# 使用sift算法提取特征并匹配
l = {}
d = {}
for i in range(3):
sift.process_image(imname[i], featname[i])
l[i], d[i] = sift.read_features_from_file(featname[i])
matches = {}
for i in range(2):
matches[i] = sift.match(d[i + 1], d[i])
# 可视化匹配
for i in range(2):
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
# 扭曲图像
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)
figure()
imshow(array(im_02, "uint8"))
axis('off')
show()
该代码依赖于PCV库, 而该库的原始版本是以python2语言开发的. 目前尚未有python3版本的更新, 因此, 若要使用python3版本, 则需寻找适合安装使用的Python3版本PCV库(或自行进行修改). 这里提供了一个由大佬编写的适合安装使用的Python3版本PCV库链接:https://github.com/Li-Shu14/PCV. 解压后将位于Python3/PyCharm环境中运行, 将位于Python2/PyCharm环境中的PyCharm项目放置到与当前项目处于同一级目录中即可. 效果如图所示:



在拍照时,并未特意为拼接而拍摄;因此显得不够自然;然而却成功地完成了拼接;今后计划补充一组效果更为出色的图片。
