图像拼接技术就是将数张有重叠部分的图像(可能是不同时间、不同视角或者不同传感器获得的)拼成一幅无缝的全景图或高分辨率图像的技术。
一般基于如下原理步骤:
利用Sift提取图像的局部特征,在尺度空间寻找极值点,并提取出其位置、尺度、方向信息。
具体步骤:
1). 生成高斯差分金字塔(DOG金字塔),尺度空间构建
2). 空间极值点检测(关键点的初步查探)
3). 稳定关键点的精确定位
4). 稳定关键点方向信息分配
5). 关键点描述
6). 特征点匹配
图像配准是一种确定待拼接图像间的重叠区域以及重叠位置的技术,它是整个图像拼接的核心。本节采用的是基于特征点的图像配准方法,即通过匹配点对构建图像序列之间的变换矩阵,从而完成全景图像的拼接。
变换矩阵H求解是图像配准的核心,其求解的算法流程如下。
1)检测每幅图像中特征点。
2)计算特征点之间的匹配。
3)计算图像间变换矩阵的初始值。
4)迭代精炼H变换矩阵。
5)引导匹配。用估计的H去定义对极线附近的搜索区域,进一步确定特征点的对应。
6)重复迭代4)和5)直到对应点的数目稳定为止。
设图像序列之间的变换为投影变换
可用4组最佳匹配计算出H矩阵的8 个自由度参数hi=( i=0,1,…,7),并以此作为初始值。
为了提高图像配准的精度,采用RANSAC算法对图像变换矩阵进行求解与精炼,达到了较好的图像拼接效果。RANSAC算法的思想简单而巧妙:首先随机地选择两个点,这两个点确定了一条直线,并且称在这条直线的一定范围内的点为这条直线的支撑。这样的随机选择重复数次,然后,具有最大支撑集的直线被确认为是样本点集的拟合。在拟合的误差距离范围内的点被认为是内点,它们构成一致集,反之则为外点。根据算法描述,可以很快判断,如果只有少量外点,那么随机选取的包含外点的初始点集确定的直线不会获得很大的支撑,值得注意的是,过大比例的外点将导致RANSAC算法失败。在直线拟合的例子中,由点集确定直线至少需要两个点;而对于透视变换,这样的最小集合需要有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
"""
This is the panorama example from section 3.3.
"""
# set paths to data folder
featname = ['F:/python/pic/' + 'a' + str(i + 1) + '.sift' for i in range(5)]
imname = ['F:/python/pic/' + 'a' + 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 = 700 # 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()
②:
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 = ['F:/python/pic/' + str(i + 1) + '.sift' for i in range(2)]
imname = ['F:/python/pic/' + 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)
# 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_12 = homography.H_from_ransac(fp, tp, model)[0] # im 1 to 2
# warp the images
delta = 700
im1 = array(Image.open(imname[0]), "uint8")
im2 = array(Image.open(imname[1]), "uint8")
im_12 = warp.panorama(H_12, im1, im2, delta, delta)
figure()
imshow(array(im_12, "uint8"))
axis('off')
show()
原图为an组
①:结果
②:分析
该组照片是拍摄于同一场景下角度不同的图片组,光照,远近一致,并未变化。通过观察得出的结果,发现在图片之间的特征匹配点提取基本上正确,但拼接的结果则不尽人意,仅有左部三张图片能较准确地拼上,右部两张完全扭曲,第三张图片被第二次拿来拼在右部中。原因可能是角度差异过大,不同平面之间的拼接效果较差,角度旋转过大会破坏拼接的结果。
原图为bn组
①:结果
②:分析
经过第一组的失败之后在同一场景中拍摄了角度变化较小的第二组图片,这次得到的结果无疑要好了许多,基本上五张图片能够较为精准的拼合,不过在右部的第二三张之间出现了微小的断层,在对比原图片之后可以推测同样是角度变化较大的原因导致拼接不够准确。如此,全景拼接有着明显的局限性和较大的优化空间。
原图为数字组两张
①:结果
②:分析
图片摄于同一视角下的不同视差,景深较为丰富,近景为黑色轿车,远景是小区楼房,对于轿车而言有一定的角度变化,同时光亮也有变化。通过观察结果可得,算法在远景中找到的特征匹配点要多于近景,匹配点中出现了少量错误,但对于图像的拼接并无太大的影响,最终得出的拼接结果较为优秀。在两张图片的拼接处基本都能够逐一对上,只有极小部分出现拼错,显然,在图片映射时未出现导致大断层或鬼影的偏差。
全景图像拼接是通过循环SIFT提取特征匹配点>RANSAC筛选得到透视矩阵>透视变换图像映射>两幅图像拼接的流程完成多图拼接,通过几组实验得出,全景拼接实验中影响最大的因素在于角度的变换,平面的变化,其他视差变化,光亮变化等因素影响较小。同一组图片下,拍摄角度平滑得出的结果会更加优秀。
ValueError: did not meet fit acceptance criteria
这是因为这段代码在计算单应性矩阵时,每个图像对中,匹配是从最右边的图像计算出来的,如果你的第二张与第一张图片没有共同点,会错乱拼接图片或者出现ValueError: did not meet fit acceptance criteria错误。所以你在给图片命名时,要按照从右到左的顺序命名。