文章目录
一、简述二、全景图像拼接方案:三、算法实现步骤3.1 计算关键点和描述符3.2 BFMatcher特征匹配与特征筛选3.3 变换矩阵的计算与图片映射 四、整体代码实现五、效果
一、简述
技术的飞速发展使我们能够以前所未有的广阔视角来捕捉世界。在这一过程中,全景图像拼接技术应运而生,它通过将多张图像无缝拼接,创造出宽广的全景画面。这项技术在旅游摄影、风景拍摄和建筑摄影等领域得到广泛应用,为观众呈现了比单张图像更为辽阔的视野,下面我将介绍如何通过opencv实现全景图像拼接。
二、全景图像拼接方案:
为了实现全景图像拼接,我们首先需要获取一系列拍摄于同一场景的图像,并确保这些图像之间有足够的覆盖区域。然后,使用特征检测算法(如SIFT)在每张图像中检测出关键点,并计算出这些关键点的描述符。接着,使用描述符匹配算法(如BFMatcher)在两张图像的描述符之间找到匹配点。为了提高拼接质量,我们需要筛选出质量较高的匹配点,这通常通过比较匹配点的距离来实现,只保留那些距离差异较大的匹配点。接下来,使用筛选出的匹配点,我们可以计算出两张图像之间的变换矩阵,使用计算出的变换矩阵,我们可以将第二张图像变换到第一张图像的坐标系中。然后,将变换后的第二张图像与第一张图像拼接在一起,即可形成全景图像。
三、算法实现步骤
3.1 计算关键点和描述符
计算关键点和描述符选择的方法是SIFT方法,SIFT(尺度不变特征变换)算法,由David Lowe在1999年提出,是一种强大的图像特征检测和描述技术。该算法能够检测和描述图像中的关键点,这些关键点在图像的缩放、旋转和光照变化下保持不变,这使得SIFT在计算机视觉领域有着广泛的应用,尤其是在图像拼接、物体识别和图像检索等任务中。
SIFT算法的主要特点包括:
● 尺度不变性:它能够检测出在不同尺度下的特征点,这些点在图像缩放时保持不变。
● 旋转不变性:算法检测出的特征点在图像旋转后仍然保持不变,适应了图像旋转的变化。
● 光照不变性:即使图像亮度发生变化,SIFT算法检测出的特征点依然保持不变,适应了光照变化。
● 高检测率:算法能够检测出大量特征点,为后续的图像处理提供了丰富的信息。
● 描述符计算:SIFT不仅检测特征点,还能为这些点计算出独特的描述符,这些描述符用于图像匹配和识别。
SIFT算法会首先在图像中找到关键点,然后为每个关键点计算一个128维的描述符向量。这些描述符向量可以被用来在多张图像之间找到匹配的关键点对,从而实现图像的拼接。
实现代码:
sift = cv2.SIFT_create()# 计算关键点和描述符kp1, desc1 = sift.detectAndCompute(gray1, None)kp2, desc2 = sift.detectAndCompute(gray2, None)
3.2 BFMatcher特征匹配与特征筛选
BFMatcher(Brute-Force Matcher)是一种在计算机视觉和图像处理中用于描述符匹配的算法。它通过比较描述符之间的汉明距离来找到匹配点。BFMatcher的主要特点包括:
汉明距离:BFMatcher使用描述符之间的汉明距离来进行匹配。汉明距离是两个等长字符串之间对应位置不同字符的数量。匹配方式:BFMatcher可以通过多种方式进行匹配,包括最近邻匹配(k-Nearest Neighbors, k-NN)和交叉匹配(Cross-Check)。效率:由于BFMatcher需要比较所有描述符对之间的汉明距离,因此其计算量较大,匹配速度相对较慢。适用场景:尽管BFMatcher的匹配速度较慢,但它可以找到高质量的匹配点,因此在需要高匹配精度的场景中仍然得到广泛应用。实现代码:
bf = cv2.BFMatcher(crossCheck=False)# 匹配描述符matches = bf.knnMatch(desc1, desc2, k=2)
bf = cv2.BFMatcher(crossCheck=False)
在特征匹配之后,需要对描述符进行筛选过滤掉低质量的匹配对,只保留那些最近邻匹配与次近邻匹配距离差异较大的匹配对,从而提高图像拼接的准确性。
实现代码:
good = []# 遍历匹配列表中的每个匹配对for m, n in matches: # 检查最近邻匹配的距离是否小于次近邻匹配距离的0.7倍 if m.distance < 0.7 * n.distance: # 如果满足条件,则将最近邻匹配添加到good列表中 good.append(m)# 根据最近邻匹配的距离对good列表中的描述符进行排序good = sorted(good, key=lambda x: x.distance)# 选择排序后good列表中的前1/3的点good = good[:len(good)//3]
3.3 变换矩阵的计算与图片映射
在计算好特征描述符之后,将特征描述符所代表的点取出来,进行计算单应性矩阵,单位性矩阵它描述了从第二张图像到第一张图像的变换
代码:
pt1 = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)pt2 = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)#这两行代码使用列表推导式来获取good列表中每个匹配对的两组匹配点的位置。#kp1和kp2分别是第一张和第二张图像的关键点列表,m.queryIdx和m.trainIdx分别是从good列表中获取的匹配对中的索引。# 计算单应性矩阵H, mask = cv2.findHomography(pt2, pt1, cv2.RANSAC, ransacReprojThreshold=4.0)
计算好单应性矩阵之后即可将第二张图片映射到第一章图片所在的空间中,我们需要创建一个足够包含映射后的第二张图像以及第一张图像的画布,然后将映射好的图像和第一张图像融合起来。
代码:
# 计算画布的高度和宽度height = img1.shape[0] + img2.shape[0]width = img1.shape[1] + img2.shape[1]# 创建一个画布,其大小为两张图像高度之和和宽度之和,初始化为全零canvas = np.zeros((height, width, 3), np.uint8)# 使用单应性矩阵H将第二张图像变换到第一张图像的坐标系中canvas = cv2.warpPerspective(img2, H, (canvas.shape[1], canvas.shape[0]))
最后将画布与第一章图像拼接
final_image = image_stitching(canvas, img1)
image_stitching实现方法为:
def image_stitching(img1, img2): gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 创建一个全零数组,大小与输入图像相同 img = np.zeros_like(img1) # 找到img1的非零像素位置 ind1 = np.where(gray1 != 0) img[ind1] = img1[ind1] # 找到img2的非零像素位置,并将其添加到img上 ind2 = np.where(gray2 != 0) img[ind2] = img2[ind2] return img
代码的工作流程如下:
首先,将输入的彩色图像img1和img2转换为灰度图像。然后,创建一个全零数组img,其大小与img1相同。接下来,使用np.where函数找到gray1和gray2中非零像素的位置。最后,将img1和img2的非零像素值复制到img中,从而得到拼接后的图像。四、整体代码实现
import cv2import numpy as npdef image_stitching(img1, img2): gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 创建一个全零数组,大小与输入图像相同 img = np.zeros_like(img1) # 找到img1的非零像素位置 ind1 = np.where(gray1 != 0) img[ind1] = img1[ind1] # 找到img2的非零像素位置,并将其添加到img上 ind2 = np.where(gray2 != 0) img[ind2] = img2[ind2] return img# 读取两张图片img1 = cv2.imread(r'C:\Users\zhw\Downloads\panorama-main\images/img02.jpg')img2 = cv2.imread(r'C:\Users\zhw\Downloads\panorama-main\images/img03.jpg')# 转换为灰度并计算关键点和描述符gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)# 创建SIFT对象sift = cv2.SIFT_create()# 计算关键点和描述符kp1, desc1 = sift.detectAndCompute(gray1, None)kp2, desc2 = sift.detectAndCompute(gray2, None)# 创建BFMatcher对象bf = cv2.BFMatcher(crossCheck=False)# 匹配描述符matches = bf.knnMatch(desc1, desc2, k=2)# 筛选好的匹配good = []for m, n in matches: if m.distance < 0.7 * n.distance: good.append(m)# 根据距离排序匹配,并取前15个最佳匹配good = sorted(good, key=lambda x: x.distance)[:len(good)//3]# 获取匹配点的位置pt1 = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)pt2 = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)# 计算单应性矩阵H, mask = cv2.findHomography(pt2, pt1, cv2.RANSAC, ransacReprojThreshold=4.0)# 使用单应性矩阵变换第二张图片height = img1.shape[0] + img2.shape[0]width = img1.shape[1] + img2.shape[1]canvas = np.zeros((height, width, 3), np.uint8)canvas = cv2.warpPerspective(img2, H, (canvas.shape[1], canvas.shape[0]))# 拼接两张图片final_image = image_stitching(canvas, img1)# 显示拼接结果cv2.imshow('Stitched Image', final_image)cv2.waitKey(0)cv2.destroyAllWindows()
五、效果
原图:
拼接好的图: