opencv-python 小白笔记(23)

今天写一个全景图像拼接的小项目,步骤大致如下:

  1. 分别计算待拼接图像的关键点和特征向量
  2. 建立蛮力匹配器,匹配特征向量
  3. 用筛选后的匹配对计算变换矩阵
  4. 对待处理图像进行透视变换
  5. 将处理后的图像进行拼接

(一)环境配置

需要注意的是,因为这个项目需要使用opencv中的SIFT(尺度不变特征变换)算法,而这个模块在opencv3.4.2以上版本后,就被申请专利了。所以如果你想使用该模块,就必须对你的opencv版本进行降级。这里的话,如果你将版本降到3.4.2或之下后,如果害不能使用,那么可以试着安装opencv的扩展模块(opencv-contrib-python),方法很简单。

pip install opencv-contrib-python -i https://pypi.douban.com/simple/

(二)获取特征点和特征向量(SIFT)

  1. 首先使用cv2.xfeatures2d.SIFT_create()实例化的sift函数
  2. kp = sift.detect(gray, None) 找出图像中的关键点,kp表示生成的关键点,gray表示输入的灰度图,
  3. 这里使用 ret = cv2.drawKeypoints(gray, kp, img) 在图中画出关键点(当然也可以不画),其中gray表示输入图片, kp表示关键点,img表示输出的图片
  4. 最后kp, dst = sift.compute(kp) 计算关键点对应的sift特征向量,其中返回值kp表示输入的关键点,dst表示输出的sift特征向量,通常是128维的
  5. 当然你也可以使用sift.detectAndCompute()函数,该函数的作用相当于sift.detect和sift.compute的合体版,其返回值是kp和dst

看看代码

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()

在这里插入图片描述

(三)特征匹配(cv2.BFMatcher)

蛮力匹配器很简单。它使用第一组中一个特征的描述符,并使用一些距离计算将其与第二组中的所有其他特征匹配。并返回最接近的一个。
对于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:代表匹配的特征点描述符的欧式距离,数值越小也就说明俩个特征点越相近。

具体使用方法如下:

  1. 首先使用SIFT得到图像的特征向量
  2. 创建BFMatcher对象
  3. 使用BFMatcher进行匹配

代码如下:

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()

效果如下:
在这里插入图片描述

(四)计算变换矩阵(cv2.findHomography)

我们之前已经找到其中的一些特征点,并且通过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检测来自AB图的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]))

# 初始化可视化图片,将AB图左右连接到一起
(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:如果有什么错误的地方,还请大家批评指正

最后,希望小伙伴们都能有所收获。码字不易,喜欢的话,关注一波在走吧
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43810267/article/details/112643580