OpenCv学习笔记14--图像描述符匹配算法、以及目标匹配

版权声明:本样板的所有内容,包括文字、图片,均为原创。如有问题可以邮箱[email protected] https://blog.csdn.net/qq_29893385/article/details/84403375

此opencv系列博客只是为了记录本人对<<opencv3计算机视觉-pyhton语言实现>>的学习笔记,所有代码在我的github主页https://github.com/RenDong3/OpenCV_Notes.

欢迎star,不定时更新...

在前面的一些小节中,我们已经使用到的图像描述符匹配相关的函数,在OpenCV中主要提供了暴力匹配、以及FLANN匹配函数库。

暴力匹配以及优化(交叉匹配、KNN匹配)

暴力匹配即两两匹配。该算法不涉及优化,假设从图片A中提取了mm个特征描述符,从B图片提取了nn个特征描述符。对于A中mm个特征描述符的任意一个都需要和B中的nn个特征描述符进行比较。每次比较都会给出一个距离值,然后将得到的距离进行排序,取距离最近的一个作为匹配点。这种方法简单粗暴,其结果也是显而易见的,通过前几节的演示案例,我们知道有大量的错误匹配,这就需要使用一些机制来过滤掉错误的匹配。比如我们对匹配点按照距离来排序,并指定一个距离阈值,过滤掉一些匹配距离较远的点

OpenCV专门提供了一个BFMatcher对象来实现匹配,并且针对匹配误差做了一些优化:

cv2.BFMatcher_create([,normType[,crossCheck]])

参数说明:

  •  normType:它是用来指定要使用的距离测试类型默认值为cv2.Norm_L2。这很适合SIFT和SURF等(c2.NORM_L1也可)。对于使用二进制描述符的ORB、BRIEF和BRISK算法等,要使用cv2.NORM_HAMMING,这样就会返回两个测试对象之间的汉明距离。如果ORB算法的参数设置为WTA_K==3或4,normType就应该设置成cv2.NORM_HAMMING2。
  • crossCheck:针对暴力匹配,可以使用交叉匹配的方法来过滤错误的匹配。默认值为False。如果设置为True,匹配条件就会更加严格,只有到A中的第i个特征点与B中的第j个特征点距离最近,并且B中的第j个特征点到A中的第i个特征点也是最近时才会返回最佳匹配(i,j),即这两个特征点要互相匹配才行。

BFMatcher对象有两个方法BFMatcher.match()和BFMatcher.knnMatch()

  • 第一个方法会返回最佳匹配,上面我们说过,这种匹配效果会出现不少误差匹配点。我们使用cv2.drawMatches()来绘制匹配的点,它会将两幅图像先水平排列,然后在最佳匹配的点之间绘制直线。
  • 第二个方法为每个关键点返回kk个最佳匹配,其中k是由用户设定的。我们使用函数cv2.drawMatchsKnn为每个关键点和它的kk个最佳匹配点绘制匹配线。如果要选择性绘制就要给函数传入一个掩模。

注意:k近邻匹配,在匹配的时候选择k个和特征点最相似的点,如果这k个点之间的区别足够大,则选择最相似的那个点作为匹配点,通常选择k=2,也就是最近邻匹配。对每个匹配返回两个最近邻的匹配,如果第一匹配和第二匹配距离比率足够大(向量距离足够远),则认为这是一个正确的匹配,比率的阈值通常在2左右。

完整程序如下:

# -*- coding:utf-8 -*-
import os
import cv2
import numpy as np

'''
        created on 09:05:10 2018-11-22
        @author ren_dong

        使用KNN即K近邻算法进行特征匹配
        ORB算法

'''

img1 = cv2.imread('orb1.jpg',0)  #测试图像img1
# img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2 = cv2.imread('orb2.jpg',0)  ##训练图像img2
# img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)

bf = cv2.BFMatcher(cv2.NORM_HAMMING,crossCheck = False)
#对每个匹配选择两个最佳的匹配
matches = bf.knnMatch(des1, des2, k=2)

print(type(matches), len(matches), matches[0])

# 获取img1中的第一个描述符即[0][]在img2中最匹配即[0][0]的一个描述符  距离最小
dMatch0 = matches[0][0]

# 获取img1中的第一个描述符在img2中次匹配的一个描述符  距离次之
dMatch1 = matches[0][1]
print('knnMatches', dMatch0.distance, dMatch0.queryIdx, dMatch0.trainIdx)
print('knnMatches', dMatch1.distance, dMatch1.queryIdx, dMatch1.trainIdx)
# 将不满足的最近邻的匹配之间距离比率大于设定的阈值匹配剔除。

img3 = None
img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches, img3, flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT)
img3 = cv2.resize(img3,(1000, 400))
cv2.imshow('KNN',img3)
cv2.waitKey()
cv2.destroyAllWindows()

运行结果如下:

指定knn的参数k=2,也就是说A图中的一个特征描述符会在B图中找到两个对应的特征描述符,一个是最佳匹配,距离最小,另一次次之,我们在程序中输出了一组匹配结果:

    bf = cv2.BFMatcher()
    knnMatches = bf.knnMatch(des1,des2, k=2) 
    print(type(knnMatches),len(knnMatches),knnMatches[0])
    #获取img1中的第一个描述符在img2中最匹配的一个描述符  距离最小
    dMatch0 = knnMatches[0][0]
    #获取img1中的第一个描述符在img2中次匹配的一个描述符  距离次之
    dMatch1 = knnMatches[0][1]
    print('knnMatches',dMatch0.distance,dMatch0.queryIdx,dMatch0.trainIdx)
    print('knnMatches',dMatch1.distance,dMatch1.queryIdx,dMatch1.trainIdx)

可以看到dMatch0和dMatch1是DMatch类型,这个类型主要包括以下几个属性:

  • DMatch.distance - Distance between descriptors. The lower, the better it is.
  • DMatch.trainIdx - Index of the descriptor in train descriptors;训练描述符就是我们程序中的img2的描述符;
  • DMatch.queryIdx - Index of the descriptor in query descriptors;测试描述符就是我们程序中的img1的描述符;
  • DMatch.imgIdx - Index of the train image.

然后我们遍历每一组匹配结果,我们设置最小比率为1/3,过滤掉匹配距离较为相近的。最后只剩下21组,匹配效果如上图所示。我们可以误匹配明显少了很多,基本看不到误匹配点。(实际上,比率设置为0.7,大概就可以过滤掉90%的误匹配点)

二 FLANN匹配

FLANN英文全称Fast Libary for Approximate Nearest Neighbors,FLANN是一个执行最近邻搜索的库,官方网站http://www.cs.ubc.ca/research/flann。它包含一组算法,这些算法针对大型数据集中的快速最近邻搜索和高维特征进行了优化,对于大型数据集,它比BFMatcher工作得更快。经验证、FLANN比其他的最近邻搜索软件快10倍。

在GitHub上可以找到FLANN,网址为https://github.com/mariusmuja/flann。根据作者的经验,基于FLANN的匹配非常准确、快速、使用起来也很方便。

# -*- coding:utf-8 -*-
import os
import cv2
import numpy as np

'''
        created on 09:05:10 2018-11-22
        @author ren_dong

        使用FLANN特征匹配

'''
def flann_test():
    '''
    FLANN匹配
    '''
    # 加载图片  灰色
    img1 = cv2.imread('orb1.jpg')
    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
    img2 = cv2.imread('orb2.jpg')
    img2 = cv2.resize(img2, dsize=(450, 300))
    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
    queryImage = gray1.copy()
    trainImage = gray2.copy()

    # 创建SIFT对象
    sift = cv2.xfeatures2d.SIFT_create(100)
    # SIFT对象会使用DoG检测关键点,并且对每个关键点周围的区域计算特征向量。该函数返回关键点的信息和描述符
    keypoints1, descriptor1 = sift.detectAndCompute(queryImage, None)
    keypoints2, descriptor2 = sift.detectAndCompute(trainImage, None)
    print('descriptor1:', descriptor1.shape, 'descriptor2', descriptor2.shape)
    # 在图像上绘制关键点
    queryImage = cv2.drawKeypoints(image=queryImage, keypoints=keypoints1, outImage=queryImage, color=(255, 0, 255),
                                   flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
    trainImage = cv2.drawKeypoints(image=trainImage, keypoints=keypoints2, outImage=trainImage, color=(255, 0, 255),
                                   flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
    # 显示图像
    # cv2.imshow('sift_keypoints1',queryImage)
    # cv2.imshow('sift_keypoints2',trainImage)
    # cv2.waitKey(20)

    # FLANN匹配
    FLANN_INDEX_KDTREE = 0
    indexParams = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
    searchParams = dict(checks=50)
    flann = cv2.FlannBasedMatcher(indexParams, searchParams)
    #indexParams, searchParams
    matches = flann.knnMatch(descriptor1, descriptor2, k=2)

    print(type(matches), len(matches), matches[0])
    # 获取queryImage中的第一个描述符在trainingImage中最匹配的一个描述符  距离最小
    dMatch0 = matches[0][0]
    # 获取queryImage中的第一个描述符在trainingImage中次匹配的一个描述符  距离次之
    dMatch1 = matches[0][1]
    print('knnMatches', dMatch0.distance, dMatch0.queryIdx, dMatch0.trainIdx)
    print('knnMatches', dMatch1.distance, dMatch1.queryIdx, dMatch1.trainIdx)

    # 设置mask,过滤匹配点    作用和上面那个一样
    matchesMask = [[0, 0] for i in range(len(matches))]

    minRatio = 1 / 3
    for i, (m, n) in enumerate(matches):
        if m.distance / n.distance < minRatio:
            matchesMask[i] = [1, 0]  # 只绘制最优匹配点

    drawParams = dict(  # singlePointColor=(255,0,0),matchColor=(0,255,0),
        matchesMask=matchesMask,
        flags=0)
    resultImage = cv2.drawMatchesKnn(queryImage, keypoints1, trainImage, keypoints2, matches,
                                     None, **drawParams)

    cv2.imshow('matche', resultImage)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    flann_test()

其中FLANN匹配对象接收两个参数:indexParams和searchParams。这两个参数在python中以字典形式进行参数传递(在C++中以结构体形式进行参数传递),为了计算匹配,FALNN内部会决定如何处理索引和搜索对象。

 flann = cv2.FlannBasedMatcher(indexParams,searchParams)

1、indexParams

对于像SIFT,SURF等算法,您可以传递以下内容:

 indexParams = dict(algorithm = FLANN_INDEX_KDTREE,trees = 5)

使用ORB时,您可以传递以下内容:

indexParams= dict(algorithm = FLANN_INDEX_LSH,
                   table_number = 6, # 12
                   key_size = 12,     # 20
                   multi_probe_level = 1) #2

参数algorithm用来指定匹配所使用的算法,可以选择的有LinearIndex、KTreeIndex、KMeansIndex、CompositeIndex和AutotuneIndex,这里选择的是KTreeIndex(使用kd树实现最近邻搜索)。KTreeIndex配置索引很简单(只需要指定待处理核密度树的数量,最理想的数量在1~16之间),并且KTreeIndex非常灵活(kd-trees可被并行处理)。

2、searchParams

SearchParams它指定索引数倍遍历的次数。 值越高,精度越高,但也需要更多时间。 如果要更改该值,请传递:

  searchParams = dict(checks = 50)

实际上、匹配效果很大程度上取决于输入。5 kd-trees和50 checks总能取得具有合理精度的结果,而且能够在很短的时间内完成匹配。

三 FLANN的单应性匹配

首先解释一下单应性,单应性是一个条件,该条件表明当两幅图像中的一副出现投影畸变(prespective distortion)时,他们还能彼此匹配.

上面我们已经介绍到,在图像queryImage中找到了一些特征点,在另一个图像trainImage找到了该图像中的特征点,我们发现它们之间的最佳匹配。我们可以利用这些匹配点来查找图像queryImage到图像trainImage的映射变换。为此,我们可以使用来自calib3d模块的函数,即cv2.findHomography()。如果我们从两个图像中传递几组匹配点(它需要至少四组正确的匹配点来找到转换),它将找到该对象的相应变换,即单应性矩阵。然后我们可以使用cv2.perspectiveTransform()来查找对象。
但是我们如何保证传入的匹配点都是正确的呢?在之前我们已经看到匹配时可能存在一些可能的错误,这可能会影响结果。为了解决这个问题,算法使用RANSAC或LEAST_MEDIAN(由标志决定)。cv2.findHomography()该函数返回一个3×3的单应性变换矩阵和一个mask,该mask是ndarray类型,长度为匹配点的对数,每一个元素表示我们在计算单应性变换时是否使用到当前索引所对应的匹配点,换句话说如果当前索引处值为0,表示该匹配点是误匹配,我们抛弃它,如果是1,表示是正确匹配,可以用来计算单应性变换矩阵。

# -*- coding: utf-8 -*-
"""
Created on Sat Sep 15 13:22:10 2018

@author: zy
"""

'''
单应性匹配
'''

import numpy as np
import cv2


def flann_hom_test():
    # 加载图像
    img1 = cv2.imread('orb1.jpg')  # queryImage
    img2 = cv2.imread('orb2.jpg')  # trainImage
    gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
    gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

    # img2 = cv2.resize(img2,dsize=(450,600))

    MIN_MATCH_COUNT = 10

    '''
    1.使用SIFT算法检测特征点、描述符
    '''
    sift = cv2.xfeatures2d.SIFT_create(100)
    kp1, des1 = sift.detectAndCompute(gray1, None)
    kp2, des2 = sift.detectAndCompute(gray2, None)
    # 在图像上绘制关键点
    # img1 = cv2.drawKeypoints(image=img1,keypoints = kp1,outImage=img1,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
    # img2 = cv2.drawKeypoints(image=img2,keypoints = kp2,outImage=img2,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
    # 显示图像
    # cv2.imshow('sift_keypoints1',img1)
    # cv2.imshow('sift_keypoints2',img2)
    # cv2.waitKey(20)

    '''
    2、FLANN匹配 
    '''
    FLANN_INDEX_KDTREE = 0
    indexParams = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
    searchParams = dict(checks=50)
    flann = cv2.FlannBasedMatcher(indexParams, searchParams)
    matches = flann.knnMatch(des1, des2, k=2)

    # 将不满足的最近邻的匹配之间距离比率大于设定的阈值匹配剔除。
    goodMatches = []
    minRatio = 0.7
    for m, n in matches:
        if m.distance / n.distance < minRatio:
            goodMatches.append(m)  # 注意 如果使用drawMatches 则不用写成List类型[m]

    '''
    3、单应性变换
    '''
    # 确保至少有一定数目的良好匹配(理论上,计算单应性至少需要4对匹配点,实际上会使用10对以上的匹配点)
    if len(goodMatches) > MIN_MATCH_COUNT:
        # 获取匹配点坐标
        src_pts = np.float32([kp1[m.queryIdx].pt for m in goodMatches]).reshape(-1, 2)
        dst_pts = np.float32([kp2[m.trainIdx].pt for m in goodMatches]).reshape(-1, 2)

        print('src_pts:', len(src_pts), src_pts[0])
        print('dst_pts:', len(dst_pts), dst_pts[0])

        # 获取单应性:即一个平面到另一个平面的映射矩阵
        M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
        # print('M:',M,type(M))   #<class 'numpy.ndarray'> [3,3]
        matchesMask = mask.ravel().tolist()  # 用来配置匹配图,只绘制单应性图片中关键点的匹配线
        # 由于使用的是drawMatches绘制匹配线,这里list
        # 每个元素也是一个标量,并不是一个list
        print('matchesMask:', len(matchesMask), matchesMask[0])

        # 计算原始图像img1中书的四个顶点的投影畸变,并在目标图像img2上绘制边框
        h, w = img1.shape[:2]
        # 源图片中书的的四个角点
        pts = np.float32([[55, 74], [695, 45], [727, 464], [102, 548]]).reshape(-1, 1, 2)
        print('pts:', pts.shape)
        dst = cv2.perspectiveTransform(pts, M)
        print('dst:', dst.shape)
        # 在img2上绘制边框
        img2 = cv2.polylines(img2, [np.int32(dst)], True, (0, 255, 0), 2, cv2.LINE_AA)

    else:
        print("Not enough matches are found - %d/%d" % (len(goodMatches), MIN_MATCH_COUNT))
        matchesMask = None

    '''
    绘制显示效果
    '''
    draw_params = dict(matchColor=(0, 255, 0),  # draw matches in green color
                       singlePointColor=None,
                       matchesMask=matchesMask,  # draw only inliers
                       flags=2)

    img3 = cv2.drawMatches(img1, kp1, img2, kp2, goodMatches, None, **draw_params)
    cv2.imshow('matche', img3)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    flann_hom_test()

OK,今天到此结束,有兴趣的可以去大神的博客围观哦,下面给出地址:

https://www.cnblogs.com/zyly/p/9646201.html

猜你喜欢

转载自blog.csdn.net/qq_29893385/article/details/84403375
今日推荐