python实现图像全景拼接

(编辑:jimmy 日期: 2024/11/12 浏览:2)

图像的全景拼接包括三大部分:特征点提取与匹配、图像配准、图像融合。

1、基于SIFT的特征点的提取与匹配

利用Sift提取图像的局部特征,在尺度空间寻找极值点,并提取出其位置、尺度、方向信息。

具体步骤:

1). 生成高斯差分金字塔(DOG金字塔),尺度空间构建

2). 空间极值点检测(关键点的初步查探)

3). 稳定关键点的精确定位

4). 稳定关键点方向信息分配

5). 关键点描述

6). 特征点匹配

2、图像配准

图像配准是一种确定待拼接图像间的重叠区域以及重叠位置的技术,它是整个图像拼接的核心。本节采用的是基于特征点的图像配准方法,即通过匹配点对构建图像序列之间的变换矩阵,从而完成全景图像的拼接。

变换矩阵H求解是图像配准的核心,其求解的算法流程如下。

1)检测每幅图像中特征点。

2)计算特征点之间的匹配。

3)计算图像间变换矩阵的初始值。

4)迭代精炼H变换矩阵。

5)引导匹配。用估计的H去定义对极线附近的搜索区域,进一步确定特征点的对应。

6)重复迭代4)和5)直到对应点的数目稳定为止。

设图像序列之间的变换为投影变换

可用4组最佳匹配计算出H矩阵的8 个自由度参数hi=( i=0,1,...,7),并以此作为初始值。

为了提高图像配准的精度,本节采用RANSAC算法对图像变换矩阵进行求解与精炼,达到了较好的图像拼接效果。RANSAC算法的思想简单而巧妙:首先随机地选择两个点,这两个点确定了一条直线,并且称在这条直线的一定范围内的点为这条直线的支撑。这样的随机选择重复数次,然后,具有最大支撑集的直线被确认为是样本点集的拟合。在拟合的误差距离范围内的点被认为是内点,它们构成一致集,反之则为外点。根据算法描述,可以很快判断,如果只有少量外点,那么随机选取的包含外点的初始点集确定的直线不会获得很大的支撑,值得注意的是,过大比例的外点将导致RANSAC算法失败。在直线拟合的例子中,由点集确定直线至少需要两个点;而对于透视变换,这样的最小集合需要有4个点。

3、图像融合

因为相机和光照强度的差异,会造成一幅图像内部,以及图像之间亮度的不均匀,拼接后的图像会出现明暗交替,这样给观察造成极大的不便。 亮度与颜色均衡处理,通常的处理方式是通过相机的光照模型,校正一幅图像内部的光照不均匀性,然后通过相邻两幅图像重叠区域之间的关系,建立相邻两幅图像之间直方图映射表,通过映射表对两幅图像做整体的映射变换,最终达到整体的亮度和颜色的一致性。

具体实现:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

if __name__ == '__main__':
top, bot, left, right = 100, 100, 0, 500
img1 = cv.imread('1.jpg')
img2 = cv.imread('2.jpg')
srcImg = cv.copyMakeBorder(img1, top, bot, left, right, cv.BORDER_CONSTANT, value=(0, 0, 0))
testImg = cv.copyMakeBorder(img2, top, bot, left, right, cv.BORDER_CONSTANT, value=(0, 0, 0))
img1gray = cv.cvtColor(srcImg, cv.COLOR_BGR2GRAY)
img2gray = cv.cvtColor(testImg, cv.COLOR_BGR2GRAY)
sift = cv.xfeatures2d_SIFT().create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1gray, None)
kp2, des2 = sift.detectAndCompute(img2gray, None)
# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)

# Need to draw only good matches, so create a mask
matchesMask = [[0, 0] for i in range(len(matches))]

good = []
pts1 = []
pts2 = []
# ratio test as per Lowe's paper
for i, (m, n) in enumerate(matches):
if m.distance < 0.7*n.distance:
good.append(m)
pts2.append(kp2[m.trainIdx].pt)
pts1.append(kp1[m.queryIdx].pt)
matchesMask[i] = [1, 0]

draw_params = dict(matchColor=(0, 255, 0),
singlePointColor=(255, 0, 0),
matchesMask=matchesMask,
flags=0)
img3 = cv.drawMatchesKnn(img1gray, kp1, img2gray, kp2, matches, None, **draw_params)
plt.imshow(img3, ), plt.show()

rows, cols = srcImg.shape[:2]
MIN_MATCH_COUNT = 10
if len(good) > MIN_MATCH_COUNT:
src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)
warpImg = cv.warpPerspective(testImg, np.array(M), (testImg.shape[1], testImg.shape[0]), flags=cv.WARP_INVERSE_MAP)

for col in range(0, cols):
if srcImg[:, col].any() and warpImg[:, col].any():
left = col
break
for col in range(cols-1, 0, -1):
if srcImg[:, col].any() and warpImg[:, col].any():
right = col
break

res = np.zeros([rows, cols, 3], np.uint8)
for row in range(0, rows):
for col in range(0, cols):
if not srcImg[row, col].any():
res[row, col] = warpImg[row, col]
elif not warpImg[row, col].any():
res[row, col] = srcImg[row, col]
else:
srcImgLen = float(abs(col - left))
testImgLen = float(abs(col - right))
alpha = srcImgLen / (srcImgLen + testImgLen)
res[row, col] = np.clip(srcImg[row, col] * (1-alpha) + warpImg[row, col] * alpha, 0, 255)

# opencv is bgr, matplotlib is rgb
res = cv.cvtColor(res, cv.COLOR_BGR2RGB)
# show the result
plt.figure()
plt.imshow(res)
plt.show()
else:
print("Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT))
matchesMask = None

实验结果:

1、室内场景:

python实现图像全景拼接

原图1

python实现图像全景拼接

原图2

拼接后:

python实现图像全景拼接

2、室外场景:

场景1:

python实现图像全景拼接

原图1

python实现图像全景拼接

原图2

拼接后:

python实现图像全景拼接

场景2:

python实现图像全景拼接

原图1

python实现图像全景拼接

原图2

拼接后:

python实现图像全景拼接

场景3:

python实现图像全景拼接

原图1

python实现图像全景拼接

原图2

拼接后:

python实现图像全景拼接

总结:

本文分别针对室内和室外两种情况对两张图像做全景拼接,发现室内情况下拼接的效果较为好。在室外场景1情况下,两张图像有近景和远景结合,两张图像拼接后近景的图像被放大并有一定程度的倾斜;在场景2中,两张图像都是远景,拼接后的效果还不错但是在拼接后图像的中上方出现了拼接缝;场景3是在不同明亮程度下图像的拼接可以发现拼接后的图像出现明显的明暗差距,并且拼接缝明显两张图像没有很好的拼接在一起,出现很多没有重合的地方。

本实验最初是用opencv-contrib3.4.5版本,但是由于sift的专利限制无法使用,随后用opencv-contriv3.4.2代码可以运行,不会出现问题。方法:先卸载当前版本的opencv并安装:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-contrib-python==3.4.2.16

本文已被收录到专题《python图片处理操作》 ,欢迎大家点击学习更多精彩内容。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

一句话新闻
一文看懂荣耀MagicBook Pro 16
荣耀猎人回归!七大亮点看懂不只是轻薄本,更是游戏本的MagicBook Pro 16.
人们对于笔记本电脑有一个固有印象:要么轻薄但性能一般,要么性能强劲但笨重臃肿。然而,今年荣耀新推出的MagicBook Pro 16刷新了人们的认知——发布会上,荣耀宣布猎人游戏本正式回归,称其继承了荣耀 HUNTER 基因,并自信地为其打出“轻薄本,更是游戏本”的口号。
众所周知,寻求轻薄本的用户普遍更看重便携性、外观造型、静谧性和打字办公等用机体验,而寻求游戏本的用户则普遍更看重硬件配置、性能释放等硬核指标。把两个看似难以相干的产品融合到一起,我们不禁对它产生了强烈的好奇:作为代表荣耀猎人游戏本的跨界新物种,它究竟做了哪些平衡以兼顾不同人群的各类需求呢?