【CV】Image Stitching

已实现的功能简述及运行简要说明

功能简述

​ 对输入的多张彩色图像,可以自动拼接成全景图像,同时对拼接边缘部分进行融合处理。

运行简要说明

img1img2,… 存储读取图像的路径,生成的全景图像将在result窗口中显示,并存储为result.jpg在与代码相同目录下。


开发与运行环境

​ 操作系统:Windows 10,64位
​ 开发环境:Python 3.7.3
​ 库环境:numpy 1.16.4, cv2 3.4.2


算法基本思路、原理

SIFT特征提取

​ SIFT即Scale Invariant Feature Transform,尺度不变特征变换。主要包含尺度空间的极值检测、特征点定位、特征方向赋值、特征点描述四个步骤。

​ 首先由不同高斯卷积得到图像的高斯尺寸空间

image-20191221213624451

​ 其中,$G(x,y,σ)$是高斯核函数,$σ$代表尺度空间因子,是高斯正态分布的标准差,反应了被模糊的程度,其值越大,模糊程度越高,尺度也就越大。$L(x,y,σ)$代表高斯尺度空间,构建好后就可以检测出在不同尺度下的特征点。特征点的检测可以使用LoG算子,但是LoG的运算量过于大,通常使用的DoG,即差分高斯。

image-20191221213738519

​ DoG是由两个相邻的高斯空间图像相减得到的。所以需要得到一系列的高斯空间尺度,可以在对图像平滑和向下取样得到结果的基础上加上高斯滤波,即对向下取样的每层图像使用不同的尺度空间因子$σ$进行模糊。通过这样的方式,通过相邻两层相减就可以得到DoG的集合。

​ 为了寻找尺度空间的极值点,每个像素点要和其图像域(同一尺度空间)和尺度域(相邻的尺度空间)的所有相邻点进行比较,当其大于(或者小于)所有相邻点时,该点就是极值点。

image-20191221213900801

​ 利用特征点邻域像素的梯度分布特性来确定其方向参数,再利用图像的梯度直方图求取关键点局部结构的稳定方向。

image-20191221220450416

特征点匹配

​ 通过K邻近算法将之前使用SIFT算法得到的特征点以及相应特征描述进行匹配。使用KNN算法找到最近邻的两个数据点,如果这两个点的距离比值小于指定的值,那么我们就可以认为这两个点是最接近的,并认为这两个点是好的匹配点加入到我们的列表中。

图像变换

​ 通过Homography矩阵进行图像变换。使用之前得到的匹配点计算Homography矩阵。通过矩阵的表达式可以看出,需要求解这六个未知数需要用到三对匹配点。而这些点的选择将通过RANSAC算法来进行。

image-20191221221017082

​ RANSAC算法即随机抽样一致算法(Random Sample Consensus)。将从数据中随机选择的点定位内点,拟合模型,然后将其他点带入模型中,如果Loss在指定阈值之内,则将这些点标为内点。如果内点数量足够多则可以认为模型比较理想。重复以上步骤得到最理想的模型。

图像融合

​ 一种图像融合的方式是,使用高斯金字塔和拉普拉斯金字塔,对尺寸相同但是内容不同的两张图片进行无缝连接。算法及示例如下:

image-20191221222509555

image-20191221222537361

image-20191221222639642

​ 在本项目的实现,即全景图片的拼接中,由于拼接部分内容相似,只是可能存在色差等问题,因此采用更为简便的方法。即根据当前像素点与待融合部分左边缘的距离,选择递增的权重,在待融合部分进行渐变显示。
$$
output\,[i,\,j] = img_left\,[i,\,j] \times (1-\alpha) \; + \; img_right\,[i,\,j] \times \alpha
$$

$$
\alpha = \frac{j-leftend}{rightend-leftend}
$$


具体实现

特征提取

​ 直接调用opencv中的SIFT相关函数进行特征匹配,实现方式如下:

1
2
3
4
5
def sift_compute(img):
sift = cv2.xfeatures2d.SIFT_create()
kp, des = sift.detectAndCompute(img, None)
kp_img = cv2.drawKeypoints(img, kp, None)
return kp_img, kp, des

特征点匹配

​ 调用knnMatch函数进行特征点匹配,如果距离相近,则认为是好的匹配

1
2
3
4
5
6
7
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
k = 0.75
goodMatch = []
for (i, j) in matches:
if i.distance < k * j.distance:
goodMatch.append(i)

图像变换

​ 调用函数,利用RANSAC算法计算Homography矩阵,并进行图像变换

1
2
3
4
5
6
#fitting transformation
ptsA= np.float32([kp1[m.queryIdx].pt for m in goodMatch]).reshape(-1, 1, 2)
ptsB = np.float32([kp2[m.trainIdx].pt for m in goodMatch]).reshape(-1, 1, 2)
ransacReprojThreshold = 4
#compute Homography matrix by RANSAC
H, status =cv2.findHomography(ptsA,ptsB,cv2.RANSAC,ransacReprojThreshold)

图像融合

​ 将位于左边的图片作为要叠加的图片,位于右边的图片(之前进行变换的图片)作为被叠加的图片。为了除去黑色边缘,检测左边图片的像素点,若不在黑边区域则进行显示,否则不显示。此外,若检测得到当前位置为两张图片的重叠区域,则进行融合处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#blending
mask_start = 0 #the left index of blending
mask_end = img_left.shape[1] #the right index of blending
for i in range(img_left.shape[1]):
if imgOutput[0,i,:].any() != 0:
mask_start = i
break
#to avoid black edges, if the pixel is black, don't show it
for i in range(img_left.shape[0]):
for j in range(img_left.shape[1]-1):
if img_left[i,j+1,:].any() != 0:
#in the overlap of two pictures, if the pixel is not so different, apply blending
if imgOutput[i,j,:].any() != 0 and not isdist(imgOutput[i,j], img_left[i,j]):
alpha = (j-mask_start) / (mask_end-mask_start)
imgOutput[i,j] = img_left[i,j] * (1-alpha) + imgOutput[i,j] * alpha
else:
imgOutput[i,j] = img_left[i,j]

显示处理

​ 去除拼接后图像空白的部分

1
2
3
4
5
rows, cols = np.where(imgIn[:,:,0] !=0)
min_row, max_row = min(rows), max(rows) +1
min_col, max_col = min(cols), max(cols) +1
imgOut = imgIn[min_row:max_row,min_col:max_col,:]
return imgOut


实验结果与分析

yosemite-test

特征点图

image-20191221224226332

特征点匹配图

image-20191221224253606

Homography图像变换

image-20191221224530256

最终拼接结果

image-20191221224632376

其他测试图

​ 使用自己拍摄的图像进行拼接,结果如下:

image-20191222190616426

​ 可以看出来结果不尽如人意。分析原因可能有以下几点:

  1. 使用手机进行拍摄,在拍摄时就可以发现,镜头边缘变形非常严重,这可能导致了在图像进行拼接时,由于边缘变形导致Homography矩阵出现偏差,这种变形进行累加将导致待拼接的图像数量越多,整体变形越严重
  2. 在本项目的拼接处理中,以最左边的图片作为基准,对其他图片进行变换并融合叠加,这在一定程度上加大了最右边图像所需要做的变形。猜测如果以最中间图片作为基准,对两边图片进行变换,将会有更好的效果

​ 为了验证以上两点猜想,用手机全景照相功能拍摄了一张全景图像,裁剪成多张图片之后再进行拼接,得到结果如下:

image-20191222190047773

​ 可以看出来效果符合预期,说明之前不符合预期的情况确实可能是由于以上原因。