OpenCV完美实现两张图片的全景拼接(详细教程)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

1主要步骤

1.1  导入需要的包和模块并读取两张待拼接的图片这里我们假设它们为 left.jpg 和 right.jpg。

1.2  创建SIFT检测器

1.3 创建一个基于 FLANN 的匹配器

1.4  筛选过程删除掉一些不合适的匹配点只保留最好的匹配点

1.5透视变换

1.6  消除重叠的效果对两张图片进行加权处理

2代码展示

3效果展示


应用场景主要有两个方面

  1. 风景或建筑物的拍摄

对于一些风景或建筑物的拍摄有时候需要的画面宽度超出了单张图片所能提供的视野范围。这时可以通过拍摄多张图片并将它们拼接成一张更加宽阔的全景图来达到所需的效果。

  1. 科学研究

在一些科学研究中需要对一定的区域进行高精度测量例如地形测量、海洋测量等。这时候就需要一些宽视野相机来实现拍摄。但是由于一张图片所能覆盖的区域有限因此通常还需要将多张图片拼接成一张更大的全景图像方便科学家们进行研究和分析。

1主要步骤

  1. 读入待拼接的图片并调整大小
  2. 使用 SIFT 或 SURF 算法提取图片的关键点和描述符
  3. 使用基于 FLANN 的匹配器进行关键点匹配并筛选出较好的匹配点
  4. 计算视角变换矩阵并使用透视变换对右边的图片进行变换
  5. 消除重叠的效果对两张图片进行加权处理
  6. 输出拼接后的结果。

1.1  导入需要的包和模块并读取两张待拼接的图片这里我们假设它们为 left.jpgright.jpg

左视图

右视图

1.2  创建SIFT检测器

cv2.xfeatures2d.SIFT_create() 创建一个 SIFT 检测器。

也可以选择使用 cv2.SIFT_create()

不过前者是更新的版本可能会更好一些

然后在两张图片上分别使用这个检测器进行关键点检测和特征提取获得关键点集合和描述符集合。

surf=cv2.xfeatures2d.SIFT_create()#可以改为SIFT
#sift = cv2.SIFT_create()
sift = cv2.xfeatures2d.SIFT_create()

kp1,descrip1 = sift.detectAndCompute(imageA,None)
kp2,descrip2 = sift.detectAndCompute(imageB,None)

1.3 创建一个基于 FLANN 的匹配器

调用 cv2.FlannBasedMatcher() 创建一个基于 FLANN 的匹配器并使用 knnMatch() 处理两张图片的特征描述符得到最佳匹配。

indexParams = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
searchParams = dict(checks=50)
flann=cv2.FlannBasedMatcher(indexParams,searchParams)
match=flann.knnMatch(descrip1,descrip2,k=2)
good=[]

1.4  筛选过程删除掉一些不合适的匹配点只保留最好的匹配点

for i,(m,n) in enumerate(match):
    if(m.distance<0.75*n.distance):
        good.append(m)

1.5透视变换

判断满足条件的匹配点数量是否大于阈值 MIN如果大于则进行视角变换矩阵的计算将右边的图片 imageB 对其进行透视变换得到 warpImg

if len(good) > MIN:
    src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)
    ano_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
    M,mask = cv2.findHomography(src_pts,ano_pts,cv2.RANSAC,5.0)
    warpImg = cv2.warpPerspective(imageB, np.linalg.inv(M), (imageA.shape[1]+imageB.shape[1], imageB.shape[0]))
    direct=warpImg.copy()
    direct[0:imageA.shape[0], 0:imageB.shape[1]] =imageA
    simple=time.time()

show('res',warpImg)

同时将左边的图片覆盖在变换后的图片上得到 direct。最后显示结果。

print(rows)
print(cols)
for col in range(0,cols):
    # 开始重叠的最左端
    if imageA[:, col].any() and warpImg[:, col].any():
        left = col
        print(left)
        break

for col in range(cols-1, 0, -1):
    #重叠的最右一列
    if imageA[:, col].any() and warpImg[:, col].any():
        right = col
        print(right)
        break

1.6  消除重叠的效果对两张图片进行加权处理

根据图片相对位置的不同左边的图片和右边的图片有可能会在某些列出现重叠部分为了消除这种不自然的效果需要实现像素级的混合。首先找到左右图片开始重叠的位置和结束的位置然后对两张图片进行加权处理最后将加权后的图片输出。

#加权处理
res = np.zeros([rows, cols, 3], np.uint8)
for row in range(0, rows):
    for col in range(0, cols):
        if not imageA[row, col].any():  # 如果没有原图用旋转的填充
            res[row, col] = warpImg[row, col]
        elif not warpImg[row, col].any():
            res[row, col] = imageA[row, col]
        else:
            srcImgLen = float(abs(col - left))
            testImgLen = float(abs(col - right))
            alpha = srcImgLen / (srcImgLen + testImgLen)
            res[row, col] = np.clip(imageA[row, col] * (1 - alpha) + warpImg[row, col] * alpha, 0, 255)

warpImg[0:imageA.shape[0], 0:imageA.shape[1]]=res
show('res',warpImg)
final=time.time()
print(final-starttime)

2代码展示

import cv2
import numpy as np
from matplotlib import pyplot as plt
import time
def show(name,img):
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
MIN = 10
FLANN_INDEX_KDTREE = 0
starttime = time.time()
img1 = cv2.imread('left.jpg') #query
img2 = cv2.imread('right.jpg') #train
imageA = cv2.resize(img1,(0,0),fx=0.2,fy=0.2)
imageB = cv2.resize(img2,(0,0),fx=0.2,fy=0.2)
surf=cv2.xfeatures2d.SIFT_create()#可以改为SIFT
#sift = cv2.SIFT_create()
sift = cv2.xfeatures2d.SIFT_create()

kp1,descrip1 = sift.detectAndCompute(imageA,None)
kp2,descrip2 = sift.detectAndCompute(imageB,None)
#创建字典
indexParams = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
searchParams = dict(checks=50)
flann=cv2.FlannBasedMatcher(indexParams,searchParams)
match=flann.knnMatch(descrip1,descrip2,k=2)
good=[]
#过滤特征点
for i,(m,n) in enumerate(match):
    if(m.distance<0.75*n.distance):
        good.append(m)

# 当筛选后的匹配对大于10时计算视角变换矩阵
if len(good) > MIN:
    src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)
    ano_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
    M,mask = cv2.findHomography(src_pts,ano_pts,cv2.RANSAC,5.0)
    warpImg = cv2.warpPerspective(imageB, np.linalg.inv(M), (imageA.shape[1]+imageB.shape[1], imageB.shape[0]))
    direct=warpImg.copy()
    direct[0:imageA.shape[0], 0:imageB.shape[1]] =imageA
    simple=time.time()

show('res',warpImg)
rows,cols=imageA.shape[:2]
print(rows)
print(cols)
for col in range(0,cols):
    # 开始重叠的最左端
    if imageA[:, col].any() and warpImg[:, col].any():
        left = col
        print(left)
        break

for col in range(cols-1, 0, -1):
    #重叠的最右一列
    if imageA[:, col].any() and warpImg[:, col].any():
        right = col
        print(right)
        break
#加权处理
res = np.zeros([rows, cols, 3], np.uint8)
for row in range(0, rows):
    for col in range(0, cols):
        if not imageA[row, col].any():  # 如果没有原图用旋转的填充
            res[row, col] = warpImg[row, col]
        elif not warpImg[row, col].any():
            res[row, col] = imageA[row, col]
        else:
            srcImgLen = float(abs(col - left))
            testImgLen = float(abs(col - right))
            alpha = srcImgLen / (srcImgLen + testImgLen)
            res[row, col] = np.clip(imageA[row, col] * (1 - alpha) + warpImg[row, col] * alpha, 0, 255)

warpImg[0:imageA.shape[0], 0:imageA.shape[1]]=res
show('res',warpImg)
final=time.time()
print(final-starttime)

3效果展示

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6