草庐IT

【OpenCV 例程 300篇】251. 特征匹配之暴力匹配

youcans_ 2023-05-22 原文

『youcans 的 OpenCV 例程300篇 - 总目录』


【youcans 的 OpenCV 例程 300篇】251. 特征匹配之暴力匹配


特征匹配是特征检测和特征描述的基本应用,在在图像拼接、目标识别、三维重建等领域的应用非常广泛。
基于特征描述符的特征点匹配是通过对两幅图像的特征点集合内的关键点描述符的相似性比对来实现的。分别对参考图像(Reference image)和检测图像(Observation image)建立关键点描述符集合,采用某种距离测度作为关键点描述向量的相似性度量。当参考图像中的关键点描述符R_i与检测图像中的关键点描述符S_j的距离测度d(R_i,S_j )满足设定条件时,判定(R_i,S_j )是配对的关键点描述符。


1. 暴力匹配(Brute-force matcher)

暴力匹配(Brute-force matcher)是最简单的二维特征点匹配方法。对于从两幅图像中提取的两个特征描述符集合,对第一个集合中的每个描述符 R i R_i Ri,从第二个集合中找出与其距离最小的描述符 S j S_j Sj作为匹配点。
暴力匹配显然会导致大量错误的匹配结果,还会出现一配多的情况。通过交叉匹配或设置比较阈值筛选匹配结果的方法可以改进暴力匹配的质量。
如果参考图像中的描述符 R i R_i Ri与检测图像中的描述符 S j S_j Sj的互为最佳匹配,则称 ( R i , S j ) (R_i,S_j ) (Ri,Sj)为一致配对。交叉匹配通过删除非一致配对来筛选匹配结果,可以避免出现一配多的错误。
比较阈值筛选是指对于参考图像的描述符 R i R_i Ri,从检测图像中找到距离最小的描述符 S j 1 S_j1 Sj1和距离次小的描述符 S j 2 S_j2 Sj2。设置比较阈值 t ∈ [ 0.5 , 0.9 ] t∈[0.5,0.9] t[0.5,0.9],只有当最优匹配距离与次优匹配距离满足阈值条件 d ( R i , S j 1 ) ⁄ d ( R i , S j 2 ) < t d(R_i,S_j1) ⁄ d(R_i,S_j2) <t d(Ri,Sj1)d(Ri,Sj2)<t时,表明匹配描述符 S j 1 S_j1 Sj1具有显著性,才接受匹配结果 ( R i , S j 1 ) (R_i,S_j1) (Ri,Sj1)


2. OpenCV 的 BFMatcher类

在OpenCV中提供了cv::BFMatcher类实现暴力匹配。
在Python语言中通过接口函数cv.BFMatcher_create或cv.BFMatcher.create实例化BFMatcher类,创建BFMatcher对象。通过成员函数bf.match对两个描述符集合进行暴力匹配,函数bf.knnMatch对两个描述符集合进行K近邻匹配。

函数原型

cv.BFMatcher.create([, normType, crossCheck]) → retval
cv.BFMatcher_create([, normType, crossCheck]) → retval
bf.match(queryDescriptors, trainDescriptors[, mask]) → matches
bf.knnMatch(queryDescriptors, trainDescriptors, k[, mask, compactResult]) → matches

参数说明

  • normType:距离类型,可选项,默认为欧式距离NORM_L2。
    • NORM_L1:L1范数,曼哈顿距离。
    • NORM_L2:L2范数,欧式距离。
    • NORM_HAMMING:汉明距离。
    • NORM_HAMMING2:汉明距离2,对每2个比特相加处理。
  • crossCheck:交叉匹配选项,可选项,默认为False。
  • queryDescriptors:描述符的查询点集,即参考图像的特征描述符的集合。
  • trainDescriptors:描述符的训练点集,即检测图像的特征描述符的集合。
  • mask:匹配点集的掩码,列表。
  • k:返回匹配点的数量。
  • matches:匹配结果,列表或元组,长度为成功的匹配数量。

注意问题

  • ⑴ 匹配结果是DMatch数据结构,包括:查询点集索引_queryIdx、训练点集索引_trainIdx和匹配距离_distance,可以用如match._distance的方式获取相应的属性值。
  • ⑵ 暴力匹配函数bf.match只返回最优匹配点,返回值matches是列表类型,列表长度为成功的匹配数量,列表元素matches[i]为DMatch数据结构。
  • ⑶ K近邻匹配函数bf.knnMatch对每个特征点返回k个最优的匹配结果,返回值matches是形如(N,k)的元组,N为特征点数量。元组元素matches[i,k]为DMatch数据结构。
  • ⑷ 交叉匹配选项crossCheck默认为False,不进行交叉匹配,返回所有特征匹配结果;True表示只返回互为最佳匹配的结果,删除非一致配对的结果。
  • ⑸ 对于SIFT、SURF描述符,推荐选择欧氏距离L1和L2范数;对于ORB、BRISK、BRIEF描述符,推荐选择汉明距离NORM_HAMMING;对于ORB描述符,当WTA_K=3或4时,推荐使用汉明距离NORM_HAMMING2。

3. 绘制匹配关系函数 cv.drawMatches

在OpenCV中还提供了函数cv.drawMatches,绘制从两个图像中找到的关键点匹配项。

函数原型

cv.drawMatches(img1, keypoints1, img2, keypoints2, matches1to2, outImg[, matchColor, singlePointColor, matchesMask, flags) → outImage
cv.drawMatchesKnn(img1, keypoints1, img2, keypoints2, matches1to2, outImg[, matchColor, singlePointColor, matchesMask, flags] → outImage

参数说明

  • img1、img2:输入图像1、输入图像2。
  • keypoints1、keypoints2:输入图像1、输入图像2中的关键点。
  • matches1to2:绘制的匹配关系,列表,列表元素是匹配结果DMatch。
  • outImg:输出匹配图像,包括图像1、图像2和表示匹配关系的连线。
  • flags:绘制内容的选项标志。
    • DRAW_MATCHES_FLAGS_DEFAULT,默认值,创建匹配输出图像,包括输入图像、匹配关系和单个的关键点。
    • DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS,不绘制没有匹配成功的单个关键点。

4. 例程:特征匹配之暴力匹配(BFMatcher)

本例程先使用SIFT算法特征检测和特征描述,再使用BFMatcher进行特征匹配。例程给出了使用交叉匹配和设置比较阈值筛选匹配结果的实现方法。

# 【1708】特征匹配之暴力匹配(BFMatcher)
import cv2 as cv
from matplotlib import pyplot as plt

if __name__ == '__main__':
    # (1) 读取参考图像
    imgRef = cv.imread("../images/Fig1703a.png", flags=1)
    refer = cv.cvtColor(imgRef, cv.COLOR_BGR2GRAY)  # 参考图像
    height, width = imgRef.shape[:2]  # 图片的高度和宽度
    # 读取或构造检测图像
    imgObj = cv.imread("../images/Fig1703b.png", flags=1)
    object = cv.cvtColor(imgObj, cv.COLOR_BGR2GRAY)  # 目标图像
    # (2) 构造 SIFT 对象,检测关键点,计算特征描述向量
    sift = cv.SIFT.create()  # sift 实例化对象
    kpRef, desRef = sift.detectAndCompute(refer, None)  # 参考图像关键点检测
    kpObj, desObj = sift.detectAndCompute(object, None)  # 检测图像关键点检测
    print("Keypoints: RefImg {}, ObjImg {}".format(len(kpRef), len(kpObj)))  # 2238/1675

    # (3) 特征点匹配,暴力匹配+交叉匹配筛选,返回最优匹配结果
    bf1 = cv.BFMatcher(crossCheck=True)  # 构造 BFmatcher 对象,设置交叉匹配
    matches = bf1.match(desRef, desObj)  # 对描述子 desRef, desObj 进行匹配
    # matches = sorted(matches, key=lambda x: x.distance)
    imgMatches1 = cv.drawMatches(imgRef, kpRef, imgObj, kpObj, matches[:300], None, matchColor=(0,255,0))
    print("(1) bf.match with crossCheck: {}".format(len(matches)))
    print(type(matches), type(matches[0]))
    print(matches[0].queryIdx, matches[0].trainIdx, matches[0].distance)  # DMatch 的结构和用法

    # (4) 特征点匹配,KNN匹配+比较阈值筛选
    bf2 = cv.BFMatcher()  # 构造 BFmatcher 对象
    matches = bf2.knnMatch(desRef, desObj, k=2)  # KNN匹配,返回最优点和次优点 2个结果
    goodMatches = []  # 筛选匹配结果
    for m, n in matches:  # matches 是元组
        if m.distance < 0.7 * n.distance:  # 最优点距离/次优点距离 之比小于阈值0.7
            goodMatches.append([m])  # 保留显著性高度匹配结果
    # good = [[m] for m, n in matches if m.distance<0.7*n.distance]  # 单行嵌套循环遍历
    imgMatches2 = cv.drawMatchesKnn(imgRef, kpRef, imgObj, kpObj, goodMatches, None, matchColor=(0,255,0))
    print("(2) bf.knnMatch:{}, goodMatch:{}".format(len(matches), len(goodMatches)))
    print(type(matches), type(matches[0]), type(matches[0][0]))
    print(matches[0][0].distance)

    plt.figure(figsize=(9, 6))
    plt.subplot(211), plt.axis('off'), plt.title("1. BF MinDistMatch")
    plt.imshow(cv.cvtColor(imgMatches1, cv.COLOR_BGR2RGB))
    plt.subplot(212), plt.axis('off'), plt.title("2. BF KnnMatch")
    plt.imshow(cv.cvtColor(imgMatches2, cv.COLOR_BGR2RGB))
    plt.tight_layout()
    plt.show()

运行结果

Keypoints: RefImg 1058, ObjImg 1015
(1) bf.match with crossCheck: 363
(2) bf.knnMatch:1058, goodMatch:123

程序说明
⑴ 对不同方位和距离拍摄的照片,用SIFT算法进行特征检测和构造特征描述符,特征匹配结果如图17-10所示。
⑵ 子图1是暴力匹配的结果,使用交叉测试进行了筛选。子图2是K近邻匹配的结果,使用阈值测试进行了筛选。图中的连线表示匹配的特征点,单独圆圈表示不匹配的特征点。
⑶ 子图1和子图2的大部分匹配结果都是正确的,但也都存在少数错误的匹配。子图2中的匹配准确率比子图1更高。
⑷ 参考图像中检测出1058个特征点,检测图像中检测出1015个特征点。使用交叉配对测试,得到363组配对;使用比较阈值测试,得到123组配对。阈值测试方法更加严格,准确性也更高。




【本节完】

版权声明:
youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/128253435)
Copyright 2022 youcans, XUPT
Crated:2022-12-10

有关【OpenCV 例程 300篇】251. 特征匹配之暴力匹配的更多相关文章

  1. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  2. ruby - 匹配未转义的平衡定界符对 - 2

    如何匹配未被反斜杠转义的平衡定界符对(其本身未被反斜杠转义)(无需考虑嵌套)?例如对于反引号,我试过了,但是转义的反引号没有像转义那样工作。regex=/(?!$1:"how\\"#expected"how\\`are"上面的正则表达式不考虑由反斜杠转义并位于反引号前面的反斜杠,但我愿意考虑。StackOverflow如何做到这一点?这样做的目的并不复杂。我有文档文本,其中包括内联代码的反引号,就像StackOverflow一样,我想在HTML文件中显示它,内联代码用一些spanMaterial装饰。不会有嵌套,但转义反引号或转义反斜杠可能出现在任何地方。

  3. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  4. ruby - 匹配大写字母并用后续字母填充,直到一定的字符串长度 - 2

    我有一个驼峰式字符串,例如:JustAString。我想按照以下规则形成长度为4的字符串:抓取所有大写字母;如果超过4个大写字母,只保留前4个;如果少于4个大写字母,则将最后大写字母后的字母大写并添加字母,直到长度变为4。以下是可能发生的3种情况:ThisIsMyString将产生TIMS(大写字母);ThisIsOneVeryLongString将产生TIOV(前4个大写字母);MyString将生成MSTR(大写字母+tr大写)。我设法用这个片段解决了前两种情况:str.scan(/[A-Z]/).first(4).join但是,我不太确定如何最好地修改上面的代码片段以处理最后一种

  5. ruby-on-rails - Rails 3,嵌套资源,没有路由匹配 [PUT] - 2

    我真的为这个而疯狂。我一直在搜索答案并尝试我找到的所有内容,包括相关问题和stackoverflow上的答案,但仍然无法正常工作。我正在使用嵌套资源,但无法使表单正常工作。我总是遇到错误,例如没有路线匹配[PUT]"/galleries/1/photos"表格在这里:/galleries/1/photos/1/edit路线.rbresources:galleriesdoresources:photosendresources:galleriesresources:photos照片Controller.rbdefnew@gallery=Gallery.find(params[:galle

  6. Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异) - 2

    之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m

  7. ruby - rbenv 安装 ruby​​ 校验和不匹配 osx - 2

    我已经在mountainlion上成功安装了rbenv和ruby​​build。运行rbenvinstall1.9.3-p392结束于:校验和不匹配:ruby-1.9.3-p392.tar.gz(文件已损坏)预期f689a7b61379f83cbbed3c7077d83859,得到1cfc2ff433dbe80f8ff1a9dba2fd5636它正在下载的文件看起来没问题,如果我使用curl手动下载文件,我会得到同样不正确的校验和。有没有人遇到过这个?他们是如何解决的? 最佳答案 tl:博士;使用浏览器从http://ftp.rub

  8. ruby - 正则表达式将非英文字母匹配为非单词字符 - 2

    @raw_array[i]=~/[\W]/非常简单的正则表达式。当我用一些非拉丁字母(具体来说是俄语)尝试时,条件是错误的。我能用它做什么? 最佳答案 @raw_array[i]=~/[\p{L}]/使用西里尔字符进行测试。引用:http://www.regular-expressions.info/unicode.html#prop 关于ruby-正则表达式将非英文字母匹配为非单词字符,我们在StackOverflow上找到一个类似的问题: https://

  9. 微信小程序通过字典表匹配对应数据 - 2

    前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立

  10. ruby - 如何遍历 Ruby 中所有正则表达式匹配的字符串? - 2

    我们有一个字符串:“”这个正则表达式://i如何从当前字符串中获取所有匹配项? 最佳答案 "".scan(//)参见scan在ruby​​-docs上 关于ruby-如何遍历Ruby中所有正则表达式匹配的字符串?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/6857852/

随机推荐