栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

Python+OpenCV实用案例应用教程:使用图像描述符检索和搜索图像

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Python+OpenCV实用案例应用教程:使用图像描述符检索和搜索图像

类似于人的眼睛和大脑,OpenCV可以检测图像的主要特征并将这
些特征提取到所谓的图像描述符中。然后,可以将这些特征作为数据
库,支持基于图像的搜索。此外,我们可以使用关键点将图像拼接起
来,组成更大的图像。(想象一下把很多图片放到一起组成一幅360°
的全景图。)
本章将展示如何使用OpenCV检测图像中的特征,并利用这些特征
匹配和检索图像。在本章的学习过程中,我们会获取样本图像并检测
其主要特征,然后试着在另一幅图像中找到与样本图像匹配的区域。
我们还将找到样本图像和另一幅图像匹配区域之间的单应性或者空间
关系。
本章将介绍以下主题:
·利用任意一种算法(Harris角点、SIFT、SURF或者ORB)检测
关键点并提取关键点周围的局部描述符。
·使用蛮力算法或者FLANN算法匹配关键点。
·使用KNN和比率检验过滤糟糕的匹配结果。
·求两组匹配关键点之间的单应性。
·搜索一组图像,确定哪一幅图像包含参考图像的最佳匹配。
我们将通过构建一个“概念–验证”的司法鉴定应用程序来完成
本章的内容。给定一个文身的参考图像,我们将搜索一组人的图像,
以便找出与文身匹配的人。


6.1  技术需求
本章使用了Python、OpenCV以及NumPy。关于OpenCV,我们使用了
可选的opencv_contrib模块,包括关键点检测和匹配的附加算法。要
启用SIFT和SURF算法(拥有专利,商业使用不免费),我们必须在
CMake中配置具有OPENCV_ENABLE_NONFREE标志的opencv_contrib模
块。安装说明请参阅第1章。此外,如果你还没有安装Matplotlib,可
以运行$ pip install matplotlib(或$ pip3 install matplotlib,
取决于环境)安装Matplotlib。
本章的完整代码可以在本教程的GitHub库
(https://github.com/PacktPublishing/Learning-OpenCV-4-
Computer-Vision-with-Python-Third-Edition)的chapter06文件夹
中找到。示例图像可以在images文件夹中找到。


6.2  理解特征检测和匹配的类型
有许多算法可以用来检测和描述特征,本节将探讨其中一些算
法。OpenCV中最常用的特征检测和描述符提取算法如下:
·Harris:该算法适用于角点检测。
·SIFT:该算法适用于斑点检测。
·SURF:该算法适用于斑点检测。
·FAST:该算法适用于角点检测。
·BRIEF:该算法适用于斑点检测。
·ORB:它是Oriented FAST和Rotated BRIEF的联合缩写。ORB对
于角点和斑点的组合检测很有用。
可以通过下列方法进行特征匹配:
·蛮力匹配。
·基于FLANN的匹配。
可以通过单应性进行空间验证。
我们刚刚介绍了很多新的术语和算法。现在,我们来讨论它们的
基本定义。
特征定义
究竟什么是特征?为什么图像的某个特定区域可以归类为特征,
而其他区域则不能分类为特征呢?广义地说,特征是图像中独特或容
易识别的一个感兴趣区域。具有高密度纹理细节的角点和区域是好的
特征,而在低密度区域(如蓝天)不断重复出现的模式就不是好的特
征。边缘是好的特征,因为它们倾向于把图像分割成两个区域。斑点
(与周围区域有很大差别的图像区域)也是一个有趣的特征。
大多数特征检测算法都围绕着角点、边缘和斑点的识别展开,有
些还关注岭(ridge)的概念,其中岭可以概念化为细长物体的对称
轴。(例如,想象一下识别图像中的道路。)
有些算法更擅长识别和提取特定类型的特征,所以了解输入图像
是什么很重要,这样就可以利用OpenCV中的最佳工具了。


6.3  检测Harris角点
我们首先介绍Harris角点检测算法。我们通过一个示例来完成角
点检测任务。如果你在阅读本教程之后,还继续学习OpenCV,那么你会
发现棋盘是计算机视觉分析的一个常见主体,部分原因是棋盘模式适
用于多种类型的特征检测,还有部分原因是下棋是一种流行的消遣方
式,尤其是在俄罗斯——那里有许多OpenCV开发人员。
图6-1是棋盘和棋子的示例图像。

图6-1  棋盘和棋子示例图像

 OpenCV有一个名为cv2.cornerHarris的方便函数,用于检测图像
中的角点。在下面的基本示例中,我们可以看一下该函数的工作情
况:

我们来分析一下代码。常规导入之后,加载棋盘图像并将其转换
成灰度图像。接下来,调用cornerHarris函数: 

 这里最重要的参数是第3个参数,定义了索贝尔(Sobel)算子的
孔径或核大小。索贝尔算子通过测量邻域像素值之间的水平和垂直差
异来检测边缘,并使用核来实现这一任务。cv2.cornerHarris函数使
用的索贝尔算子的孔径由此参数定义。简单地说,这些参数定义了角
点检测的灵敏度。这个参数值必须是在3~31之间的奇数值。对于3这
样的低值(高灵敏度),棋盘上黑色方格中所有斜线接触到方格边界
时,都将记录为角点。对于23这样的高值(低灵敏度),只有每个方
格的角才会被检测为角点。
cv2.cornerHarris返回浮点格式的图像。该图像中的每个值表示
源图像对应像素的一个分值。中等的分值或者高的分值表明像素很可
能是一个角点。相反,分值最低的像素可以视为非角点。考虑下面的
代码行:

 这里,我们选取的像素的分值至少是最高分值的1%,并在原始图
像中将这些像素涂成红色,结果如图6-2所示。 

 

太棒了!几乎所有检测到的角点都标记为红色。标记的点包括棋
盘方格上几乎所有的角点。
如果调整cv2.cornerHarris中的第2个参数,我们将看到较小的
区域(对应于较小的参数值)或者较大的区域(对应于较大的参数
值)被检测为角点。这个参数称为块大小。
6.4  检测DoG特征并提取SIFT描述符
上述技术使用cv2.cornerHarris,能很好地检测角点且有明显的
优势,因为角就是角点,即使旋转图像也能检测到这些角点。但是,
如果将图像缩放到更小或者更大的尺寸,图像的某些部分可能丢失或
者获得高质量的角点。
例如,图6-3是F1意大利大奖赛赛道的一幅图像的角点检测结果。

 

图6-4是基于同一幅图像的一个更小版本的角点检测结果 

你会注意到角点是如何变得更紧凑的,可是,尽管我们获得了一
些角点,但是也丢失了一些角点!比如,我们来检查一下阿斯卡里
(Variante Ascari)减速弯道,这条位于从西北直到东南的赛道尽头
的弯道看起来像波形曲线。在大版本的图像中,双弯道的入口和顶端
都被检测为角点。在缩小版本的图像中,没有检测到这样的顶端。如
果进一步缩小图像,在某种程度上我们还会丢失弯道入口的角点。
这种特征的丢失引发了一个问题:我们需要一种算法,不管图像
大小都能工作。于是尺度不变特征变换(Scale-Invariant Feature
Transform,SIFT)登场了。虽然这个名字听起来有点神秘,但是既然
我们知道了要解决什么问题,它实际上就是有意义的。我们需要一个
函数(变换)来检测特征(特征变换),并且不会因图像尺度的不同
而输出不同的结果(尺度不变特征变换)。请注意,SIFT不检测关键
点(用高斯差分(Difference of Gaussian,DoG)来完成),而是通
过特征向量描述其周围的区域。
接下来快速浏览一下DoG。在第3章中,我们讨论过低通滤波器和
模糊运算,特别是cv2.GaussianBlur()函数。DoG是对同一幅图像应用
不同的高斯滤波器的结果。之前,我们将这类技术应用于边缘检测,
这里的思路也一样。DoG运算的最终结果包含感兴趣区域(关键点),
然后通过SIFT进行描述。
我们来看DoG和SIFT在图6-5中的表现,这幅图像充满了角点和特
征。 

这里使用了瓦雷兹(位于意大利的伦巴第)的漂亮全景图,此图
在计算机视觉领域作为一个主体而声名鹊起。下面是生成经过处理的
图像的代码: 

常规导入后,加载想要处理的图像。然后,把图像转换成灰度图
像。至此,你可能已经发现OpenCV中的很多方法都需要灰度图像作为
输入。下一步是创建SIFT检测对象,并计算灰度图像的特征和描述
符: 

 在后台,这些简单的代码行执行了一个复杂的过程:创建一个
cv2.SIFT对象,该对象使用DoG检测关键点,再计算每个关键点周围区
域的特征向量。正如detectAndCompute方法的名字清楚表明的那样,
该方法主要执行两项操作:特征检测和描述符计算。该操作的返回值
是一个元组,包含一个关键点列表和另一个关键点的描述符列表。
最后,用cv2.drawKeypoints函数在图像上绘制关键点,然后用常
规的cv2.imshow函数对其进行显示。作为其中一个参数,
cv2.drawKeypoints函数接受一个指定想要的可视化类型的标志。这
里,我们指定cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINT,以便绘
制出每个关键点的尺度和方向的可视化效果。
关键点剖析
每个关键点都是cv2.KeyPoint类的一个实例,具有以下属性:
·pt(点)属性包括图像中关键点的x和y坐标。
·size属性表示特征的直径。
·angle属性表示特征的方向,如前面处理过的图像中的径向线所
示。
·response属性表示关键点的强度。由SIFT分类的一些特征比其他
特征更强,response可以评估特征强度。
·octave属性表示发现该特征的图像金字塔层。我们简单回顾一下
5.2节中介绍过的图像金字塔的概念。SIFT算法的操作方式类似于人脸
检测算法,迭代处理相同的图像,但是每次迭代时都会更改输入。具
体来说,图像尺度是在算法每次迭代(octave)时都变化的一个参数。
因此,octave属性与检测到关键点的图像尺度有关。
·class_id属性可以用来为一个关键点或者一组关键点分配自定义
的标识符。
6.5  检测快速Hessian特征并提取SURF描述符
计算机视觉是计算机科学中相对较新的一个分支,因此许多著名
的算法和技术都是最近才出现的。实际上,SIFT是在1999年由David
Lowe发布的,才只有20多年的历史。
SURF是在2006年由Herbert Bay发布的一种特征检测算法。SURF要
比SIFT快几倍,而且是受到了SIFT的启发。
请注意,SIFT和SURF都是授权专利算法,因此,只有在
opencv_contrib构建中使用了OPENCV_ENABLE_NONFREE CMake标
志时才可用。
对于本教程来说,了解SURF的工作原理并不是特别重要,我们可以
将其应用到应用程序中并对其进行充分利用。重要的是理解cv2.SURF
是一个OpenCV类,用快速Hessian算法进行关键点检测,并用SURF进行
描述符提取,就像cv2.SIFT类一样(用DoG进行关键点检测,用SIFT进
行描述符提取)。
此外,好消息是OpenCV为它所支持的所有特征检测和描述符提取
算法提供了标准的API。因此,只需要做一些细微的改变,就可以修改
前面的代码示例来使用SURF(而不是SIFT)。下面是修改后的代码,
修改部分用粗体表示:

cv2.xfeatures2d.SURF_create的参数是快速Hessian算法的一个
阈值。通过增加阈值,可以降低保留下来的特征数量。阈值为8000
时,得到的结果如图6-6所示。 

试着调整阈值,看看阈值对结果的影响。作为练习,你可能希望
构建带有控制阈值的滑块的GUI应用程序。通过这种方式,用户可以调
整阈值,查看以反比的形式增加和减少的特征数量。在4.7节中,我们
构建了一个带有滑块的GUI应用程序,因此可以参考那部分内容。
接下来,我们将研究FAST角点检测器、BRIEF关键点描述符和
ORB(把FAST和BRIEF结合在一起使用)。


6.6  使用基于FAST特征和BRIEF描述符的ORB
如果说SIFT还很年轻,SURF更年轻,那么ORB就还处于婴儿期。
ORB首次发布于2011年,作为SIFT和SURF的一个快速代替品。
该算法发表在论文“ORB:an efficient alternative to SIFT or
SURF”上,可以在
http://www.willowgarage.com/sites/default/files/orb_final.pdf
处找到PDF格式的论文。
ORB融合了FAST关键点检测器和BRIEF关键点描述符,所以有必要
先了解一下FAST和BRIEF。接下来,我们将讨论蛮力匹配(用于特征匹
配的算法)并举一个特征匹配的例子。


6.6.1  FAST
加速分割测试的特征(Feature from Accelerated Segment
Test,FAST)算法是通过分析16个像素的圆形邻域来实现的。FAST算
法把邻域内每个像素标记为比特定阈值更亮或更暗,该阈值是相对于
圆心定义的。如果邻域包含若干标记为更亮或更暗的一系列连续像
素,那么这个邻域就被视为角点。
FAST还使用了一种高速测试,有时可以通过只检查2个或者4个像
素(而不是16个像素)来确定邻域不是角点。要了解这个测试如何工
作,我们来看一下图6-7(选自OpenCV文档)。

图6-7  一张选自OpenCV文档的示例图片

 在图6-7中两个不同的放大倍数下,我们可以看到一个16像素的邻
域。位于1、5、9和13处的像素对应于圆形邻域边缘的4个方位基点。
如果邻域是一个角点,那么预计在这4个像素中,刚好有3个像素或者1
个像素比阈值亮。(另一种说法是刚好有1个或者3个像素比阈值
暗。)如果其中刚好有2个比阈值亮,那么该领域是一条边,而不是一
个角点。如果其中刚好有4个或者0个比阈值亮,那么该领域相对一
致,既不是角点也不是边。
FAST是一个智能算法,但是它并不是没有缺点,为了弥补这些缺
点,从事图像分析的开发人员可以实现一种机器学习算法,以便为算
法提供一组(与给定应用程序相关的)图像,从而优化阈值等参数。
不管开发人员是直接指定参数,还是为机器学习方法提供一个训练
集,FAST都是对输入很敏感的一种算法,也许比SIFT更敏感。


6.6.2  BRIEF
另外,二值鲁棒独立基本特征(Binary Robust Independent
Elementary Feature,BRIEF)并非特征检测算法,而是一个描述符。
我们来更深入地研究一下描述符的概念,然后再来研究BRIEF。
在前面用SIFT和SURF分析图像时,整个过程的核心是调用
detectAndCompute函数。此函数执行两个不同的步骤——检测和计
算,它们返回2个不同的结果(耦合到一个元组中)。
检测结果是一组关键点,计算结果是这些关键点的一组描述符。
这意味着OpenCV的cv2.SIFT和cv2.SURF类都实现了检测和描述算法。
请记住,原始的SIFT和SURF不是特征检测算法。OpenCV的cv2.SIFT实
现了DoG特征检测和SIFT描述,而OpenCV的cv2.SURF实现了快速
Hessian特征检测和SURF描述。
关键点描述符是图像的一种表示,充当特征匹配的通道,因为你
可以比较两幅图像的关键点描述符并发现它们的共性。
BRIEF是目前最快的描述符之一。BRIEF背后的理论相当复杂,但
是可以这样说,BRIEF采用一系列优化,使其成为特征匹配的一个非常
好的选择。


6.6.3  蛮力匹配
蛮力匹配器是一个描述符匹配器,它比较两组关键点描述符并生
成匹配列表。之所以称为蛮力匹配,是因为在该算法中几乎不涉及优
化。对于第一个集合中的每个关键点描述符,匹配器将之与第二个集
合中的每个关键点描述符进行比较。每次比较产生一个距离值,并基
于最小距离选择最佳匹配。
概括地说,在计算中,“蛮力”一词是指将所有可能组合(例
如,破解已知长度密码的所有可能的字符组合)的穷举按优先级排序
的方法。相反,优先考虑速度的算法可能会跳过一些可能性,并试图
走一条捷径来找到看似最合理的解决方案。
OpenCV提供了一个cv2.BFMatcher类,支持几种蛮力特征匹配的方
法。


6.6.4  匹配两幅图像中的标识
既然我们已经大致了解了什么是FAST和BRIEF,我们就可以理解为
什么ORB背后的团队(由Ethan Rublee、Vincent Rabaud、Kurt
Konolige和Gary R.Bradski组成)选择这两种算法作为ORB的基础了。
在其论文中,作者的目标是实现以下结果:
·为FAST增加一个快速且准确的定位组件。
·面向BRIEF特征的高效计算。
·面向BRIEF特征的方差和相关性分析。
·在旋转不变性下去相关BRIEF特征的一种学习方法,在最近邻应
用中产生更好的性能。
要点很清晰:ORB的目标是优化和加速操作,包括非常重要的以旋
转感知的方式利用BRIEF的步骤,这样匹配就得以改善,即使在训练图
像与查询图像有非常不同的旋转状态的情况下也是如此。
不过,在这个阶段,你可能已经了解了足够的理论,希望深入研
究一些特征匹配,我们来看一些代码。下面的脚本试图将标识中的特
征与包含该标识的照片中的特征进行匹配:

我们一步一步地查看这段代码。在通常的导入语句之后,我们以
灰度格式加载两幅图像(查询图像和场景)。图6-8是查询图像,它是
美国国家航空航天局标识。图6-9是肯尼迪航天中心的照片。

现在,我们继续创建ORB特征检测器和描述符:

与使用SIFT和SURF的方式类似,我们检测并计算这两幅图像的关
键点和描述符。
从这里开始,概念非常简单:遍历描述符并确定是否匹配,然后
计算匹配的质量(距离),并对匹配进行排序,这样就可以在一定程
度上显示前n个匹配,它们实际上匹配了两幅图像上的特征。
cv2.BFMatcher可以实现这一任务:

在此阶段,我们已经有了需要的所有信息,但是作为计算机视觉
爱好者,我们非常重视数据的可视化表示,所以我们在matplotlib图
表中绘制这些匹配:

Python的切片语法非常健壮。如果matches列表中包含的项少
于25个,那么matches[:25]切片命令将正常运行,并提供与原始列表包含
同样多元素的列表。
匹配结果如图6-10所示。

你可能会认为这是一个令人失望的结果。实际上,我们可以看到
大多数匹配都是假匹配。不幸的是,这很典型。为了改善匹配结果,
我们需要应用其他技术来过滤糟糕的匹配。接下来我们将把注意力转
向这项任务。


6.7  使用K最近邻和比率检验过滤匹配
想象一下,一大群知名哲学家邀请你评判他们关于对生命、宇宙
和一切事物都很重要的一个问题的辩论。在每位哲学家轮流发言时,
你都会认真听。最后,在所有哲学家都发表完他们所有的论点时,你
浏览笔记,会发现以下两件事:
·每位哲学家都不赞同其他哲学家的观点。
·没有哲学家比其他哲学家更有说服力。
根据最初的观察,你推断最多只有一位哲学家的观点是正确的,
但事实上,也有可能所有哲学家的观点都是错误的。然后,根据第二
次观察,即使其中一位哲学家的观点是正确的,你也会开始担心可能
会选择一个观点错误的哲学家。不管你怎么看,这些人都会让你的晚
宴迟到。你称其为平局,并说辩论中仍有最重要的问题尚未解决。
我们可以对判断哲学家辩论的假想问题与过滤糟糕关键点匹配的
实际问题进行比较。
首先,假设查询图像中的每个关键点在场景中最多有一个正确的
匹配。也就是说,如果查询图像是NASA标识,那么就假定另一幅图像
(场景)最多包含一个NASA标识。假设一个查询关键点最多有一个正
确或者良好的匹配,那么在考虑所有可能的匹配时,我们主要观察糟
糕的匹配。因此,蛮力匹配器计算每个可能匹配的距离分值,可以提
供大量的对糟糕匹配的距离分值的观察。与无数糟糕的匹配相比,我
们期望良好的匹配会有明显更好(更低)的距离分值,因此糟糕的匹
配分值可以帮助我们为针对良好的匹配选择一个阈值。这样的阈值不
一定能很好地推广到不同的查询关键点或者不同的场景,但是至少在
具体案例上会有所帮助。
现在,我们来考虑修改后的蛮力匹配算法的实现,该算法以上述
方式自适应地选择距离阈值。在上一节的示例代码中,我们使用
cv2.BFMatcher类的match方法来获得包含每个查询关键点的单个最佳
匹配(最小距离)的列表。这样的实现丢弃了有关所有可能的糟糕匹
配的距离分值的信息,而这类信息是自适应方法所需要的。幸运的
是,cv2.BFMatcher还提供了knnMatch方法,该方法接受一个参数k,
可以指定希望为每个查询关键点保留的最佳(最短距离)匹配的最大
数量。(在某些情况下,得到的匹配数可能比指定的数量最大值更
少。)KNN表示K最近邻(K-Nearest Neighbor)。
我们会使用knnMatch方法为每个查询关键点请求两个最佳匹配的
列表。基于每个查询关键点至多有一个正确匹配的假设,我们确信次
优匹配是错误的。次优匹配的距离分值乘以一个小于1的值,就可以获
得阈值。
然后,只有当距离分值小于阈值时,才将最佳匹配视为良好的匹
配。这种方法被称为比率检验(ratio test),最先是由David
Lowe(SIFT算法的作者)提出来的。他在论文“Distinctive Image
Features from Scale-Invariant Keypoints”(网址为
https://www.cs.ubc.cs/~lowe/papers/ijcv04.pdf)中描述了比率检
验。具体来说,在“Application to object recognition”部分,他
声明如下:
一个匹配正确的概率可以根据最近邻到第2近邻的距离比例来确
定。
我们可以用与前面代码示例相同的方式加载图像、检测关键点,
并计算ORB描述符。然后,使用下面两行代码执行蛮力KNN匹配: 

 knnMatch返回列表的列表,每个内部列表至少包含一个匹配项,
且不超过k个匹配项,各匹配项从最佳(最短距离)到最差依次排序。
下列代码行根据最佳匹配的距离分值对外部列表进行排序:

我们来画出前25个最佳匹配,以及knnMatch可能与之配对的所有
次优匹配。不能使用cv2.drawMatches函数,因为该函数只接受一维匹
配列表,相反,必须使用cv2.drawMatchesKnn。下面的代码用来选
择、绘制,并显示匹配:

到目前为止,我们还没有过滤掉所有糟糕的匹配——实际上,还
故意包含了我们认为是糟糕的次优匹配——因此,结果看起来有点
乱,如图6-11所示。
现在,我们来应用比率检验,把阈值设置为次优匹配距离分值的
0.8倍。如果knnMatch无法提供次优匹配,那么就拒绝最佳匹配,因为
无法对其应用检验。下面的代码应用了这些条件,并提供通过测试的
最佳匹配列表:

应用比率检验后,只需处理最佳匹配(而非最佳和次优匹配
对),这样就可以用cv2.drawMatches(而非cv2.drawMatchesKnn)对
其进行绘制。同样,从列表中选择前25个匹配项。下面的代码用于选
择、绘制,并显示匹配项:

 

在图6-12中,我们可以看到通过比率检验的匹配项。
将此输出图像与上一节中的输出图像进行比较,可以看到使用KNN
和比率检验可以过滤掉很多糟糕的匹配项。剩余的匹配项并不完美,
但是几乎所有的匹配项都指向了正确的区域——肯尼迪航天中心一侧
的NASA标识。
我们有了一个良好的开始。接下来,我们将用名为FLANN的更快的
匹配器来代替蛮力匹配器。在此之后,我们将学习如何根据单应性描
述一组匹配,即表示匹配对象的位置、旋转角度、比例以及其他几何
特征的二维变换矩阵。

 6.8  基于FLANN的匹配
FLANN代表快速近似最近邻库(Fast Library for Approximate
Nearest Neighbor),是2条款BSD许可下的一个开源库。FLANN的官方
网站是http://www.cs.ubc.ca/research/flann/。以下内容摘自该网
站:
FLANN是在高维空间执行快速近似最近邻搜索的一个库,包含最
适合最近邻搜索的一个算法集,以及根据数据集自动选择最佳算法和
最优参数的一个系统。
FLANN是用C++编写的,包含C、MATLAB和Python等语言的绑
定。
或者说,FLANN有一个很大的工具箱,知道如何根据任务选择好的
工具,而且会几种语言。这些特性使库更方便、快捷。事实上,FLANN
的作者声称:对于很多数据集来说,FLANN比其他最近邻搜索软件要快
10倍。
作为一个独立的库,FLANN可以在GitHub平台
(https://github.com/mariusmuja/flann/)上找到。但是,我们会
将FLANN作为OpenCV的一部分来使用,因为OpenCV为其提供了一个方便
的封装包。
要开始FLANN匹配的实际示例,需先导入NumPy、OpenCV和
Matplotlib,并从文件加载两幅图像。下面是相关代码:

图6-13是脚本加载的第一幅图像(查询图像)。
这件艺术作品是保罗·高更(Paul Gauguin)于1889年创作完成
的Entre les lys(Among the lilies)。我们将在包含多幅高更作品
以及本教程一位作者所画的一些随意形状的更大图像(见图6-14)中搜
索匹配关键点。
在大图像内,Entre les lys位于第3行第3列。查询图像和大图像
中对应区域不一致,它们用略微不同的颜色和不同的尺度描绘了Entre
les lys。尽管如此,对于我们的匹配器来说,这应该是一种比较简单
的情况。
我们使用cv2.SIFT类来检测必要的关键点,并提取特征:

 

图6-14  用于搜索关键点匹配的大图像

 到目前为止,代码看起来应该很熟悉,因为本章的前几节已专门
讨论过SIFT以及其他描述符。在前面的示例中,我们将描述符送入
cv2.BFMatcher,用于蛮力匹配。这次,我们将使用
cv2.FlannBasedMatcher。下列代码使用自定义参数执行基于FLANN的
匹配:

可以看到,FLANN匹配器接受2个参数:indexParams对象和
searchParams对象。这些参数以Python中字典(和C++中结构体)的形
式传递,确定FLANN内部用于计算匹配的索引和搜索对象的行为。我们
选择的参数提供了精度和处理速度之间的合理平衡。具体来说,我们
使用了包含5棵树的核密度树(kernel density tree,kd-tree)索引
算法,FLANN可以并行处理此算法。FLANN文档建议在1棵树(不提供并
行性)和16棵树(如果系统可以利用的话,那么就可以提供高度的并
行性)之间进行选择。
我们对每棵树执行50次检查或者遍历。检查次数越多,可以提供
的精度也越高,但是计算成本也就更高。
在进行基于FLANN的匹配之后,我们应用乘数为0.7的劳氏比率检
验。为了展示不同的编码风格,与上一节的代码示例相比,我们将以
一种稍有不同的方式使用比率检验的结果。之前,我们组建了一个新
的列表,其中只包含好的匹配项。这次,我们将组建名为
mask_matches的列表,其中每个元素都是长度为k(与传给knnMatch的
k是一样的)的子列表。如果匹配成功,则将子列表中对应的元素设为
1,否则将其设置为0。
例如,如果mask_matches=[[0,0],[1,0]],这意味着有两个匹配
的关键点:对于第一个关键点,最优和次优匹配项都是糟糕的;而对
于第二个关键点,最佳匹配是好的,次优匹配是糟糕的。请注意,我
们假设了所有次优匹配都是糟糕的。使用下列代码来应用比率检验,
并建立掩模:

现在是绘制并显示良好匹配项的时候了。把mask_matches列表传
递给cv2.drawMatchesKnn作为可选参数,如下列代码段中粗体所示:

cv2.drawMatchesKnn只绘制掩模中标记为好的匹配(值为1)。脚
本生成的基于FLANN匹配的可视化结果如图6-15所示。

 结果令人鼓舞:看上去几乎所有的匹配项都处于正确的位置。接
下来,我们试着把这种类型的结果简化为更简洁的几何表示——单应
性,它将描述整个匹配对象的姿态,而不是一堆不连续的匹配点。
6.9  基于FLANN进行单应性匹配
首先,什么是单应性(homography)?来自网络的一个定义是:
两张图之间的一种关系,一张图的任意一点与另一张图的一个点
相对应,反之亦然。因此,在圆上滚动的一条切线将圆的两条固定切
线切成两组同形点。
如果你像本教程的作者一样对前面的定义并不了解,你可能会发现
下面的解释可能更清楚一些:单应性是当一张图是另一张图的一个透
视畸变时,在两张图中寻找彼此的一种情况。
首先,我们来看想要实现什么,这样就可以完全理解什么是单应
性。然后,再检查代码。
假设,我们想搜索图6-16中的文身。

对于我们人来说,很容易在图6-17中找到文身,尽管存在一些旋
转角度上的不同。

作为计算机视觉领域的一个练习,我们想要编写一个脚本生成以
下关键点匹配和单应性可视化结果,如图6-18所示。

在图6-18中,我们在第一张图中选取了主体,在第二张图中正确
地识别了主体,在关键点之间绘制了匹配线,甚至在第二张图中画了
一个白色边框,显示相对于第一张图的主体透视畸变。
你可能已经猜到了,脚本的实现从导入库开始,读取灰度格式的
图像,检测特征并计算SIFT描述符。我们在前面的例子中完成过了所
有这些内容,因此这里不再重复。我们来看接下来做些什么吧!
(1)我们组建一个通过了劳氏比率检验的匹配列表,代码如下:

 (2)从技术上讲,我们最少可以用4个匹配项来计算单应性。但
是,如果这4个匹配项中的任意一个有缺陷,都将会破坏结果的准确
性。实际中最少用到10个匹配项。对于额外的匹配项,单应性查找算
法可以丢弃一些异常值,以便产生与大部分匹配项子集紧密匹配的结
果。因此,我们继续检查是否至少有10个好的匹配项:

 (3)如果满足这个条件,那么就查找匹配的关键点的二维坐标,
并把这些坐标放入浮点坐标对的两个列表中。一个列表包含查询图像
中的关键点坐标,另一个列表包含场景中匹配的关键点坐标:

(4)寻找单应性:

请注意,我们创建了mask_matches列表,将用于最终的匹配绘
制,这样只有满足单应性的点才会绘制匹配线。
(5)在这个阶段,必须执行一个透视转换,取查询图像的矩形角
点,并将其投影到场景中,这样就可以画出边界:

然后,继续绘制关键点并显示可视化效果,正如前面的例子那
样。


6.10  示例应用程序:文身取证
我们以一个实际生活(或者幻想生活)中的例子来结束这一章。
假设你在某市法医部门工作,需要鉴定一个文身。你有罪犯文身的原
始图片(可能是在闭路电视录像中拍摄到的),但是不知道这个人的
身份。可是,你拥有一个文身数据库,它以文身所属人的名字为索
引。
我们把这个任务分成两部分:
·通过将图像描述符保存到文件中来构建数据库。
·加载数据库并扫描查询图像的描述符和数据库中描述符之间的
匹配项。
在接下来的两个小节中,我们将介绍这些任务。


6.10.1  将图像描述符保存到文件
我们要做的第一件事情是将图像描述符保存到外部文件。这样,
我们就不必在每次扫描两个要匹配的图像时都重新创建描述符。
就本示例而言,我们扫描一个图像文件夹,并创建对应的描述符
文件,这样在未来的搜索中就可以随时使用这些内容。要创建描述
符,我们将使用本章已经多次使用过的方法:加载图像、创建特征检
测器、检测特征并计算描述符。要将描述符保存到文件,我们将使用
名为save的NumPy数组的便利方法,以优化的方式将数组数据转储到文
件中。
在Python标准库中,pickle模块提供了更通用的序列化功能,
支持任何Python对象,不仅仅是NumPy数组。可是,NumPy的数组序列
化对于数字数据来说是一个很好的选择。
我们将脚本分解为函数。主函数将被命名为
create_descriptors,它将遍历给定文件夹中的文件。对于每个文
件,create_descriptors将调用名为create_descriptor的辅助函数,
该函数将计算并保存给定图像文件的描述符。
(1)首先,create_descriptors的实现如下: 

注意,create_descriptors创建了特征检测器,因为我们只需要
创建一次,而不是每次加载文件时都要创建。辅助函数
create_descriptor接收特征检测器作为参数。
(2)现在,我们来看辅助函数的实现:

注意,我们将描述符文件保存在与图像相同的文件夹中。此外,
我们假设图像文件具有png扩展名。为了使脚本更加鲁棒,可以对其进
行修改,使脚本支持额外的图像文件扩展名,如jpg。如果文件有一个
意想不到的扩展名,就跳过它,因为它可能是描述符文件(来自之前
的脚本运行)或其他非图像文件。
(3)我们已经完成了函数的实现。为了完成脚本,我们将调用
create_descriptors,用文件夹名称作为参数:

 运行这个脚本时,它将生成NumPy数组文件格式的必要描述符文
件,文件扩展名为npy。这些文件构成了文身描述符数据库,按名字索
引。(每个文件名都是一个人名。)接下来,我们将编写一个独立的
脚本,这样就可以对数据库进行查询了。
6.10.2  扫描匹配
既然已经将描述符保存到文件,我们只需要对每个描述符集执行
匹配,以确定哪个集合最匹配查询图像。
以下是将实施的过程:
(1)加载查询图像(query.png)。
(2)扫描包含描述符文件的文件夹。打印描述符文件的名称。
(3)为查询图像创建SIFT描述符。
(4)对于每个描述符文件,加载SIFT描述符,并搜索基于FLANN
的匹配项。基于比率检验过滤匹配。打印这个人的名字和匹配项的数
量。如果匹配项的数量超过了任意阈值,则打印“此人是嫌疑人”。
(请记住,我们在调查一起犯罪活动。)
(5)打印主要嫌疑人的名字(匹配次数最多的那个人)。
我们来考虑其实现:
(1)首先,用下面的代码块加载查询图像:

(2)汇编并打印描述符文件列表: 

 (3)建立典型的cv2.SIFT和cv2.FlannBasedMatcher对象,并且
生成查询图像的描述符:

(4)寻找嫌疑人,将嫌疑人定义为至少有10个与查询文身良好匹
配的人。搜索过程包括遍历描述符文件、加载描述符、执行基于FLANN
的匹配,以及基于比率检验过滤匹配项。打印每个人(每个描述符文
件)的匹配结果:

注意np.load方法的用法,它将指定的NPY文件加载到NumPy
数组中。
(5)最后,打印主要嫌疑人的名字(如果找到了嫌疑人的话):

运行上述脚本,产生的输出如下: 

如果愿意,就像在前一节中所完成的那样,也可以用图形表示匹
配项和单应性。


6.11  本章小结
本章中,我们介绍了关键点检测、关键点描述符的计算、描述符
的匹配、糟糕匹配的过滤,以及两组匹配关键点之间单应性的寻找。
我们讨论了OpenCV中可用于完成上述任务的一些算法,并将这些算法
应用到各种图像和用例中。
如果把关于关键点的新知识和摄像头以及透视的知识相结合,我
们就能够跟踪三维空间中的物体。这将是第9章的主题。如果你非常渴
望进入三维领域,那么可以先跳到第9章进行学习。
相反,如果你认为接下来的内容对于你完善有关物体检测、识别
以及跟踪的二维解决方案的知识有帮助,那么可以继续学习第7章和第
8章的内容。最好了解一下二维和三维组合技术,这样就可以选择为给
定应用程序提供正确输出类型和恰当计算速度的方法。 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1005427.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号