今天写一个全景图像拼接的小项目,步骤大致如下:
需要注意的是,因为这个项目需要使用opencv中的SIFT(尺度不变特征变换)算法,而这个模块在opencv3.4.2以上版本后,就被申请专利了。所以如果你想使用该模块,就必须对你的opencv版本进行降级。这里的话,如果你将版本降到3.4.2或之下后,如果害不能使用,那么可以试着安装opencv的扩展模块(opencv-contrib-python),方法很简单。
pip install opencv-contrib-python -i https://pypi.douban.com/simple/
看看代码
import cv2
img = cv2.imread('left.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
# 找出关键点
kp = sift.detect(gray, None)
print(type(kp),kp)
# 对关键点进行绘图
ret = cv2.drawKeypoints(gray, kp, img)
# 使用关键点找出sift特征向量
kp,des = sift.compute(gray, kp)
print(des)
cv2.imshow('ret', ret)
cv2.waitKey(0)
cv2.destroyAllWindows()
也可以这样
import cv2
img = cv2.imread('left.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
kp, des = sift.detectAndCompute(gray,None)#直接返回关键点和特征向量
print(type(kp),kp,des)
# 对关键点进行绘图
ret = cv2.drawKeypoints(gray, kp, img)
cv2.imshow('ret', ret)
cv2.waitKey(0)
cv2.destroyAllWindows()
蛮力匹配器很简单。它使用第一组中一个特征的描述符,并使用一些距离计算将其与第二组中的所有其他特征匹配。并返回最接近的一个。
对于BF匹配器,首先我们必须使用cv2.BFMatcher()创建BFMatcher对象,它需要两个可选参数。第一个是normType,它指定要使用的距离测量。默认情况下为 cv.NORM_L2 。对于SIFT,SURF等(也有 cv2.NORM_L1 )很有用。 对于基于二进制字符串的描述符,例如ORB,BRIEF,BRISK等,应使用 cv.NORM_HAMMING ,该函数使用汉明距离作为度量。如果ORB使用 WTA_K == 3或 4 ,则应使用cv.NORM_HAMMING2。第二个参数是布尔变量,即crossCheck,默认情况下为false。如果为true,则Matcher仅返回具有值(i,j)的那些匹配项,以使集合A中的第i个描述符具有集合B中的第j个描述符为最佳匹配,反之亦然。
其返回值为是一个DMatch,DMatch是一个匹配之后的集合。
DMatch中的每个元素含有三个参数:queryIdx:测试图像的特征点描述符的下标(第几个特征点描述符),同时也是描述符对应特征点的下标。trainIdx:样本图像的特征点描述符下标,同时也是描述符对应特征点的下标。distance:代表匹配的特征点描述符的欧式距离,数值越小也就说明俩个特征点越相近。
具体使用方法如下:
代码如下:
import cv2
img1 = cv2.imread("test.jpg")
img2 = cv2.imread("test1.jpg")
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
bf = cv2.BFMatcher(crossCheck=True)
matches = bf.match(des1, des2)
# 按照相近程度 进行排序
matches = sorted(matches, key=lambda x: x.distance)
#这里取前11个匹配对连接
out_put = cv2.drawMatches(img1, kp1, img2, kp2, matches[:11], None,flags=2)
cv2.imshow("img1", img1)
cv2.imshow("img2", img2)
cv2.imshow("out_put", out_put)
cv2.waitKey(0)
cv2.destroyAllWindows()
效果如下:
KNNMatch
knn 匹配可以返回k个最佳的匹配项,匹配过程中很可能发生错误的匹配,错误的匹配主要有两种:匹配的特征点是错误的,图像上的特征点无法匹配。常用的删除错误的匹配有
交叉过滤法:
如果第一幅图像的一个特征点和第二幅图像的一个特征点相匹配,则进行一个相反的检查,即将第二幅图像上的特征点与第一幅图像上相应特征点进行匹配,如果匹配成功,则认为这对匹配是正确的。
比率测试:
KNNMatch,可设置K = 2 ,即对每个匹配返回两个最近邻描述符,仅当第一个匹配与第二个匹配之间的距离足够小时,才认为这是一个匹配。
Knnmatch与match的返回值类型一样,只不过一组返回的两个DMatch类型,这两个DMatch数据类型是两个与原图像特征点最接近的两个特征点(match返回的是最匹配的)只有这俩个特征点的欧式距离小于一定值的时候才会认为匹配成功。
import cv2
img1 = cv2.imread("test.jpg")
img2 = cv2.imread("test1.jpg")
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
result = []
for m, n in matches:
if m.distance < 0.75 * n.distance:#这里使用0.75进行筛选过滤
result.append([m])
out_put = cv2.drawMatchesKnn(img1,kp1,img2,kp2,result[:11],None,flags=2)
cv2.imshow("img1", img1)
cv2.imshow("img2", img2)
cv2.imshow("out_put", out_put)
cv2.waitKey(0)
cv2.destroyAllWindows()
效果如下:
我们之前已经找到其中的一些特征点,并且通过BFMatcher获得了特征点的匹配对。简单说就是我们在一组图像里找到了另一幅图像的某个部分的位置,现在我们只需将匹配对传入到cv2.findHomography,就可获得图像的变换矩阵了
H,S=cv2.findHomography(srcPoints,dstPoints,Threshold,mask,maxIters,confidence)
参数 | 解释 |
---|---|
srcPoints | 目标中的特征点 |
dstPoints | 与目标图片相匹配的特征点 |
method | 计算单应矩阵所使用的方法,不同的方法对应不同的参数,具体如下: 0 - 利用所有点的常规方法,RANSAC - RANSAC-基于RANSAC的鲁棒算法,LMEDS - 最小中值鲁棒算法,RHO - PROSAC-基于PROSAC的鲁棒算法 |
Threshold | 若srcPoints和dstPoints是以像素为单位的,则该参数通常设置在1到10的范围内 |
mask | 可选输出掩码矩阵,通常由鲁棒算法(RANSAC或LMEDS)设置。 请注意,输入掩码矩阵是不需要设置的 |
maxIters | RANSAC算法的最大迭代次数,默认值为2000 |
confidence | 可信度值,取值范围为0到1. |
其返回值有两个,一个是变换矩阵H,另一个是status向量,用来删除错误的匹配,到了这一步,基本已经大功告成了,下面就是图像拼接了
代码如下:
import numpy as np
import cv2
ratio=0.75#为啥是0.75,就是这样用的
Thresh=4.0
# 读取拼接图片
imageA = cv2.imread("right.jpg")
imageB = cv2.imread("left.jpg")
cv2.imshow('imageA', imageA)
cv2.imshow('imageB', imageB)
cv2.waitKey(0)
# 建立SIFT生成器
descriptor = cv2.xfeatures2d.SIFT_create()
# 检测SIFT特征点,并计算描述子
(kpsA, featuresA) = descriptor.detectAndCompute(imageA, None)
(kpsB, featuresB) = descriptor.detectAndCompute(imageB, None)
#print(kpsA)
# 将结果转换成NumPy数组
kpsA = np.float32([kp.pt for kp in kpsA])
kpsB = np.float32([kp.pt for kp in kpsB])
#print(kpsA)
# 匹配两张图片的所有特征点,返回匹配结果
# 建立暴力匹配器
matcher = cv2.BFMatcher()
# 使用KNN检测来自A、B图的SIFT特征匹配对,K=2
Matches = matcher.knnMatch(featuresA, featuresB, 2)
matches = []
for m in Matches:
# 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对
if m[0].distance < m[1].distance * ratio:#ratio=0.75
# 存储两个点在featuresA, featuresB中的索引值
matches.append((m[0].trainIdx, m[0].queryIdx))
H=[]
status=[]
# 当筛选后的匹配对大于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, Thresh)
result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageB.shape[0]))
# 初始化可视化图片,将A、B图左右连接到一起
(hA, wA) = imageA.shape[:2]
(hB, wB) = imageB.shape[:2]
temp = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
temp[0:hB, 0:wB] = imageB
temp[0:hA, wB:] = imageA
# 联合遍历,画出匹配对
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(temp, ptA, ptB, (0, 255, 0), 1)
cv2.imshow('temp', temp)
cv2.waitKey(0)
cv2.imshow('result', result)
cv2.waitKey(0)
output=result.copy()
# 将图片B传入result图片最左端
output[0:imageB.shape[0], 0:imageB.shape[1]] = imageB
cv2.imshow('output', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
待拼接的两张图片
这里是用线将匹配好的特征点连接来
这是对上面imageA(也就是右边的图片进行变换的结果):
这是最后拼接好的结果:
学习opencv有很多的方法,我的建议是你可以加一些群,可以充分利用B站,CSDN,和百度。
在我的博客中,我不会讲解opencv的算法实现(当然我也不太会),我只会讲解一些函数的调用,不理解就多改一些参数,多尝试尝试,慢慢你就理解来。相信你总有一天可以说opencv不过“Ctrl+C,Crtl+V”
PS:如果有什么错误的地方,还请大家批评指正