计算机视觉-全景图像拼接

1. 全景图像拼接

1.1 基本介绍

全景图像拼接简单的说就是将多张存在重叠部分图片拼成一幅无缝的全景图或高分辨率图像。也就是将两幅存在一定重合部分的图像,通过特征匹配算法(如SIFT)进行特征点匹配从而得到对应点,将两张图像得到的对应点重合并保留两张图片的未重合部分,便可以得到两张图像拼接的结果,以此类推可以得到多张图像拼接而成的全景图。

1.2 图像拼接整体流程

  • 根据给定图像/集,实现特征匹配
  • 通过匹配特征计算图像之间的变换结构
  • 利用图像变换结构,实现图像映射
  • 针对叠加后的图像,采用APAP之类的算法,对齐特征点
  • 通过图割方法,自动选取拼接缝
  • 根据multi-band blending策略实现融合

1.3 基本原理

1.3.1 特征匹配算法

在之前的博文SIFT算法-图像特征匹配、地理标记图像匹配中已经对SIFT算法已及Harris角点检测有较详细的介绍,这里不再赘述。

1.3.1 RANSAC算法

RANSAC算法即RANdom SAmple Consensus,随机抽样一致算法,采用迭代的方式从一组包含离群的被观测数据中估算出数学模型的参数。

RANSAC算法假设数据中包含正确数据和异常数据(或称为噪声)。正确数据记为内点(inliers),异常数据记为外点(outliers)。同时RANSAC也假设,给定一组正确的数据,存在可以计算出符合这些数据的模型参数的方法。

在利用SIFT算法进行特征点匹配时,我们会发现常常存在特征点匹配错误的现象,这些匹配错误的点将会对图像拼接的效果产生很大的影响,所以我们需要利用一定的方法剔除匹配错误的特征点,我们常会用到RANSAC算法来筛选SIFT匹配的特征点以减少误差,这个算法现在在图像配准以及拼接上得到了广泛的应用。

1.3.1.1 RANSAC算法基本思想
  • 随机选择两个点
  • 根据随机选取的两个点构造方程y=ax+b
  • 将所有的数据点套到这个模型中计算误差。
  • 给定阈值,计算inliers数量
  • 不断重复上述过程,直到达到一定迭代次数后,选择inliers数量最多的直线方程,作为问题的解。

在这里插入图片描述

1.3.1.2 RANSAC 求解单应矩阵

· RANSAC loop:

  1. 随机选择四对匹配特征
  2. 根据DLT计算单应矩阵 H H H(唯一解)
  3. 对所有匹配点,计算映射误差ε = ∥ p i p_i pi ′ , H H H p i p_i pi
  4. 根据误差阈值,确定inliers(例如3-5像素)
  5. 针对最大inliers集合,重新计算单应矩阵 H H H

1.3.2 APAP算法

在这里插入图片描述
在图像拼接融合的过程中,受客观因素的影响,拼接融合后的图像可能会存在上图中的“鬼影现象”以及图像间过度不连续等问题。2013年,Julio Zaragoza等人发表了一种新的图像配准算法APAP(As-Projective-As-Possible Image Stitching with Moving DLT)解决鬼影现象可以采用APAP算法。

1.3.2.1算法流程:

1.提取两张图片的SIFT特征点
2.对两张图片的特征点进行匹配
3.匹配后,使用RANSAC算法进行特征点对的筛选,排除错误点。筛选后的特征点基本能够一一对应。
4.使用DLT算法,将剩下的特征点对进行透视变换矩阵的估计。
5.因为得到的透视变换矩阵是基于全局特征点对进行的,即一个刚性的单应性矩阵完成配准。为提高配准的精度,APAP将图像切割成无数多个小方块,对每个小方块进行单应性矩阵变换。

APAP虽然能够较好地完成配准,但非常依赖于特征点对。若图像高频信息较少,特征点对过少,配准将完全失效,并且对大尺度的图像进行配准,其效果也不是很好,一切都决定于特征点对的数量。

1.3.3 寻找最佳拼接缝(Seam Finding)

若在两张图像的重叠区域选择一条最佳拼接缝,拼接缝左边的图像为第一张图像的内容,拼接缝右边的图像为第二张图像的内容,并且拼接缝上两幅图像的差异应该尽可能小,这样不仅可以有效的解决鬼影问题且拼接的效果会更好。
在这里插入图片描述

1.3.3.1 使用最大流最小割算法寻找拼接缝

如何找到最佳的拼接缝,使两张图片完美的重合在一起便是我们要解决的问题。最大流最小割作为解决图论问题的经典算法,可以应用于寻找最佳拼接缝。
在这里插入图片描述
伪代码如下:
在这里插入图片描述

1.3.4 根据multi-band bleing策略实现图像融合

图像拼接完成后会发现在拼接的交界处有明显的衔接痕迹,可能由于图像光线不同的缘故使两张图像的交界处过度的不好,所以我们需要特定的处理解决这种不自然。multi-band bleing策略采用Laplacian(拉普拉斯)金字塔,通过对相邻两层的高斯金字塔进行差分,将原图分解成不同尺度的子图,对每一个之图进行加权平均,得到每一层的融合结果,最后进行金字塔的反向重建,得到最终融合效果过程,融合之后可以得到较好的拼接效果。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 具体实现

2.1 实验代码

# -*- coding=utf-8 -*-
# name: nan chen
# date: 2021/4/17 10:53

# ch3_panorama_test.py
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 = [r'D:\project3image\lib00' + str(i + 1) + '.sift' for i in range(5)]
imname = [r'D:\project3image\lib00' + 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 = 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')
savefig("example5.png", dpi=300)
show()


2.2 实验结果及分析

2.2.1 单平面(景深落差小)

- 5张图像拼接

数据集:
在这里插入图片描述
在这里插入图片描述

实验结果:

在这里插入图片描述
在这里插入图片描述

结果分析:
  1. 第一组照片为嘉庚图书馆内部宣传栏的拼接,从整体来看效果还可以,宣传栏的边框几乎保持水平状态,放大之后可以看到中间有一条明显的拼接缝,并且有些许的错位,没有发生"鬼影现象",总的来说有不错的效果。
  2. 第二组位于中山纪念馆前拍摄的图像,图像的中间及左边效果都蛮不错,但是右边的楼可以明显的看出倾斜,该算法是以中山纪念馆的图像为中心,应该是在拼接过程中最右的图像进行了仿射变换,导致了楼的扭曲。

2.2.2 多平面(景深落差大)

- 5张图像拼接

数据集:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实验结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结果分析:
  1. 可以看出上面的上面三组照片的拼接大致看效果是还可以的,但是光影效果的变换导致天空颜色的不同让三组照片都可以明显的看出拼接的痕迹。
  2. 第一组湖边拍摄的照片,放大来看,建筑的纹理有些许变形,且位于图像右边的建筑有些许的倾斜现象并且湖边的原本平滑的长石椅可以看出有些许拼接错位,和上述嘉庚图书馆前的地面相似,拼接图象时一些平行线的对齐是图像拼接过程中值得注意的一个问题,可以看出在景深落差大且图像较为复杂的图像拼接中该算法效果没有那么好。
  3. 第二组照片则可以很明显的看出近处的两个柱子,有一个柱子有明显的变形,数据集中视角的变化导致柱子的宽度也有明显的差别且柱子的每一处特征点都较为相似,这可能导致叠加后的图像在对齐特征点时发生偏差,可以看出单应性矩阵变换在多平面景深变化大的场景下进行拼接会发生一定的扭曲。
  4. 第三组为五张拍摄于集美大学嘉庚图书馆外部的图像拼接后的效果,由于光影的影响可以明显的看到拼接的痕迹,可以明显的看到原本平行的地板拼接错位。嘉庚图书馆楼的拼接效果还蛮不错,但是近处地面的效果就不是很好。拍摄这组照片的时候我是固定嘉庚图书馆为中心点,然后分别向左右偏转一定的角度进行拍摄,角度的偏转可能导致图像在特征点匹配的时候对远处的楼有更好的匹配效果,而近处的地面不能很好的匹配,导致地板的错位。

3. 实验小结

  1. 对于景深落差小的平面,该算法可以得到不错的效果,有些许的错位现象,且对于平行线的拼接存在明显的错位。
  2. 对于景深变化较大的多平面场景,该算法能在部分区域较好的完成图像的拼接,但是对于其余的部分区域,可能有较为明显的扭曲现象。在选取中间的图像为中心点进行拼接时,位于两侧的建筑物可能发生倾斜现象。
  3. 在使用上面的代码进行图像拼接时,一定要保证图像的从右往左的顺序,若有一张图像的顺序出现错误,则图像的拼接就会发生很大的错乱QAQ。
  4. 在图像拼接的过程中,若直接使用原图,则需要很长很长的时间(也可能是机器太垃圾了),为了节省一定的时间,最好还是将图像压缩后再进行拼接。
  5. 代码中的delta参数为拍摄图像时你相对平移的距离的变量,当拍摄的图像为近景时候,这个参数尽量该小,远景相反。dpi是图像精细度的变量,可以通过修改来改变图像的分辨率,数值越大表示图像越精细,即分辨率越高。

3. 遇到的问题

1.出现 ValueError

ValueError: all the input arrays must have same number of dimensions

在这里插入图片描述
这是由于图像的大小不同产生的错误,把所有要拼接的图像大小调为相同后,错误解决。

2.拼接效果糟糕
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
一开始进行实验的时候几组图像得到的结果都十分的糟糕,原本以为是拍摄的图片不行,后来才发现图像的顺序是要从右往左,而我一直是从左往右的顺序,导致拼接效果糟糕。

  • 5
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
压缩包中包含的具体内容: 对给定数据中的6个不同场景图像,进行全景图拼接操作,具体要求如下: (1) 寻找关键点,获取关键点的位置和尺度信息(DoG检测子已由KeypointDetect文件夹中的detect_features_DoG.m文件实现;请参照该算子,自行编写程序实现Harris-Laplacian检测子)。 (2) 在每一幅图像中,对每个关键点提取待拼接图像的SIFT描述子(编辑SIFTDescriptor.m文件实现该操作,运行EvaluateSIFTDescriptor.m文件检查实现结果)。 (3) 比较来自两幅不同图像的SIFT描述子,寻找匹配关键点(编辑SIFTSimpleMatcher.m文件计算两幅图像SIFT描述子间的Euclidean距离,实现该操作,运行EvaluateSIFTMatcher.m文件检查实现结果)。 (4) 基于图像中的匹配关键点,对两幅图像进行配准。请分别采用最小二乘方法(编辑ComputeAffineMatrix.m文件实现该操作,运行EvaluateAffineMatrix.m文件检查实现结果)和RANSAC方法估计两幅图像间的变换矩阵(编辑RANSACFit.m 文件中的ComputeError()函数实现该操作,运行TransformationTester.m文件检查实现结果)。 (5) 基于变换矩阵,对其中一幅图像进行变换处理,将其与另一幅图像进行拼接。 (6) 对同一场景的多幅图像进行上述操作,实现场景的全景图拼接(编辑MultipleStitch.m文件中的makeTransformToReferenceFrame函数实现该操作)。可以运行StitchTester.m查看拼接结果。 (7) 请比较DoG检测子和Harris-Laplacian检测子的实验结果。图像拼接的效果对实验数据中的几个场景效果不同,请分析原因。 已经实现这些功能,并且编译运行均不报错!
全景图拼接是将多张拍摄自同一地点、视角不同的照片拼接成一张大的全景图像。OpenCV是一个非常强大的计算机视觉库,可以用来实现全景图拼接。 下面是实现全景图拼接的基本步骤: 1. 加载图片。使用OpenCV的cv2.imread()函数加载图片。 2. 特征点检测。使用OpenCV的SIFT、SURF、ORB等算法检测每张图片的特征点。 3. 特征点匹配。使用OpenCV的FLANN或者Brute-Force算法对特征点进行匹配。 4. 计算单应性矩阵。使用OpenCV的findHomography函数计算单应性矩阵,将当前图片与上一张图片进行拼接。 5. 图像拼接。使用OpenCV的warpPerspective函数将当前图片进行透视变换,然后将图片拼接到上一张图片上。 6. 重复步骤2-5,直到所有图片拼接完成。 下面是一个基于OpenCV实现全景图拼接的示例代码: ```python import cv2 import numpy as np # 加载图片 img1 = cv2.imread('img1.jpg') img2 = cv2.imread('img2.jpg') # 特征点检测 sift = cv2.xfeatures2d.SIFT_create() kp1, des1 = sift.detectAndCompute(img1, None) kp2, des2 = sift.detectAndCompute(img2, None) # 特征点匹配 bf = cv2.BFMatcher() matches = bf.knnMatch(des1, des2, k=2) good_matches = [] for m, n in matches: if m.distance < 0.75 * n.distance: good_matches.append(m) src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2) # 计算单应性矩阵 M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) # 图像拼接 result = cv2.warpPerspective(img1, M, (img1.shape[1] + img2.shape[1], img1.shape[0])) result[0:img2.shape[0], 0:img2.shape[1]] = img2 cv2.imshow('result', result) cv2.waitKey(0) cv2.destroyAllWindows() ``` 这段代码实现了两张图片的拼接。你可以使用这个基本的框架,将多张图片进行拼接,从而实现全景图拼接
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值