opencv库是python中重要的图像处理库,也被称为计算机视觉开发库,这篇文章我们用利用opencv库来实现全景图像的拼接,总体上来说包含sift匹配和ransac误匹配剔除两大模块,那么话不多说,我们开始今天的正题。
为了大家开始就有一个清晰的认知,首先介绍一下整个程序的架构,整个程序总体来说包含两个py文件,主文件作用是实现两张图像的匹配并且拼接起来,副文件作用是将图像文件读取进来,然后直接调用主文件中的函数,最后将各个步骤的结果展示出来,下面的图可以很好的表示出来。
sift特征点匹配是图像匹配发展过程中的一座里程碑,在这个方法被提出之后,图像匹配的正确性与准确性都得到了显著的提高,时隔多年,在这个人工智能与深度学习炙手可热的时代,这个经典的方法仍然是主流方法之一。与其他匹配方法一样,从总体上该方法包含特征点检测
,特征点描述
,特征点匹配
三大步骤。
1.特征点检测
2.特征点描述
RANASC算法全称为随机采样一致性方法,在sift特征匹配后,在正确匹配占据多数的情况下,使用此算法进行误匹配的剔除,从而计算sift匹配的正确率。
具体步骤为:
Step1:N对特征点钟随机取3个点,计算仿射矩阵H
Step2:验证其余的N-3对点是否符合H变换,如果符合,记为内点,否则记为外点
Step3:返回STEP1,不断迭代
Step4:达到迭代次数,结束迭代,比较Nk(Nk为第k次迭代内点数目)
Step5:选择Nk最大时对应的Hk,去掉此次迭代的外点,视其为错误点
首先来介绍一下代码中用到的主要函数,其实这些函数uu们对计算机视觉有一定的了解的话已经是常客了,不了解的uu不用着急,我来一一介绍。
函数名 | 作用 | 需要参数 | 返回值 |
---|---|---|---|
imread | 读取图像 | 图像存储的路径 | 彩色图像为三维矩阵,灰白图像为一维矩阵 |
cvtcolor | 将图像灰度化 | 彩色图像矩阵 | 灰白图像一维矩阵,值在0-255之间 |
SIFT_create | 建立sift实例化对象 | 各个初始参数,例如边缘检测阈值edgeThreshold,高斯金字塔初始尺度值noctavelayers | sift对象 |
dedectAndcompute | 探测图像特征向量 | 图像矩阵 | 第一个参数kp是特征点的位置坐标,第二个参数des是对应的特征向量,是一个32*1维的向量 |
drawkeypoints | 将特征点的位置画出一个小圆圈 | 参数1:指定绘制在那副图像上,参数2:特征点的坐标矩阵kp,参数3:特征点绘制的画布图像,可选参数圆圈大小和圆圈颜色 | 无返回值 |
drawMatches | 将匹配后的同名点连线 | 原始图像1,图像1特征点,原始图像2,图像2特征点,线的眼色(默认为随机彩色) | 无返回值 |
接着介绍一下重要的实现过程。
第一就是在得到两幅图像的特征点之后,进行knn暴力匹配,并且将同名点特征不明显的点对进行去消除。
matcher = cv2.BFMatcher()
# 使用KNN检测来自A、B图的SIFT特征匹配对,K=2
rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
matches = []
for m in rawMatches:
# 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对
if len(m) == 2 and m[0].distance < m[1].distance * ratio:
# 存储两个点在featuresA, featuresB中的索引值
matches.append((m[0].trainIdx, m[0].queryIdx))
第二是如何根据特征点匹配结果将两幅图像拼接起来,我们知道两幅图像的一般拍摄视角不同,我需要至少四对同名点来计算变换矩阵M,M的形式如下:
这个变换矩阵后将A图像乘以M便可以将两幅图像变换到同一个视角,之后将B图片根据实际位置传入A图像最右端或者最左端就可以实现原始图像的拼接。
具体的实现代码如下:
# H是4x4视角变换矩阵
(matches, H, status) = M
# 将图片A进行视角变换,result是变换后图片
result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))
self.cv_show('result', result)
# 将图片B传入result图片最左端
result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB
最终的代码是这样的:
主文件py
import numpy as np
import cv2
class Stitcher:
# 拼接函数
def stitch(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):
# 获取输入图片
(imageB, imageA) = images
# 检测A、B图片的SIFT关键特征点,并计算特征描述子
(kpsA, featuresA) = self.detectAndDescribe(imageA)
(kpsB, featuresB) = self.detectAndDescribe(imageB)
# 匹配两张图片的所有特征点,返回匹配结果
M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)
# 如果返回结果为空,没有匹配成功的特征点,退出算法
if M is None:
return None
# 否则,提取匹配结果
# H是4x4视角变换矩阵
(matches, H, status) = M
# 将图片A进行视角变换,result是变换后图片
result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))
self.cv_show('result', result)
# 将图片B传入result图片最左端
result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB
self.cv_show('result', result)
# 检测是否需要显示图片匹配
if showMatches:
# 生成匹配图片
vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)
# 返回结果
return (result, vis)
# 返回匹配结果
return result
def cv_show(self, name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
def detectAndDescribe(self, image):
# 将彩色图片转换成灰度图
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 建立SIFT生成器
descriptor = cv2.xfeatures2d.SIFT_create()
# 检测SIFT特征点,并计算描述子
(kps, features) = descriptor.detectAndCompute(image, None)
# 将结果转换成NumPy数组
kps = np.float32([kp.pt for kp in kps])
# 返回特征点集,及对应的描述特征
return (kps, features)
def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh):
# 建立暴力匹配器
matcher = cv2.BFMatcher()
# 使用KNN检测来自A、B图的SIFT特征匹配对,K=2
rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
matches = []
for m in rawMatches:
# 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对
if len(m) == 2 and m[0].distance < m[1].distance * ratio:
# 存储两个点在featuresA, featuresB中的索引值
matches.append((m[0].trainIdx, m[0].queryIdx))
# 当筛选后的匹配对大于4时,计算视角变换矩阵
if len(matches) > 4:
# 获取匹配对的点坐标
ptsA = np.float32([kpsA[i] for (_, i) in matches])
ptsB = np.float32([kpsB[i] for (i, _) in matches])
# 计算视角变换矩阵
(H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh)
# 返回结果
return (matches, H, status)
# 如果匹配对小于4时,返回None
return None
def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):
# 初始化可视化图片,将A、B图左右连接到一起
(hA, wA) = imageA.shape[:2]
(hB, wB) = imageB.shape[:2]
vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
vis[0:hA, 0:wA] = imageA
vis[0:hB, wA:] = imageB
# 联合遍历,画出匹配对
for ((trainIdx, queryIdx), s) in zip(matches, status):
# 当点对匹配成功时,画到可视化图上
if s == 1:
# 画出匹配对
ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
cv2.line(vis, ptA, ptB, (0, 255, 0), 1)
# 返回可视化结果
return vis
从文件py
from Stitcher import Stitcher
import cv2
# 读取拼接图片
imageA = cv2.imread("left_01.png")
imageB = cv2.imread("right_01.png")
# 把图片拼接成全景图
stitcher = Stitcher()
(result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)
# 显示所有图片
cv2.imshow("Image A", imageA)
cv2.imshow("Image B", imageB)
cv2.imshow("Keypoint Matches", vis)
cv2.imshow("Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
这就是今天的全部内容了,当然对算法的具体原理和实现过程并没有过多的介绍,更多的是从程序实现的角度来进行的,如果想要具体了解算法实现原理,可以参考这篇文章非常详细的sift算法介绍,创作不易,喜欢的uu点个赞赞,让我们下篇文章再见!