国外课栈 - 国外电子信息技术视角栈

聚焦Python和OpenCV通过特征匹配和透视变换查找对象

 二维码 8

通过特征匹配和透视变换查找对象

准备

本文已经过OpenCV 4.1.1的测试。

请注意,您可能必须从以下位置https://github.com/Itseez/opencv_contrib获得所谓的额外模块。

我们安装了带有OPENCV_ENABLE_NONFREE和OPENCV_EXTRA_MODULES_PATH变量的OpenCV,以便安装加速健壮功能(SURF)和近似邻居的快速库(FLANN)。 您还可以使用存储库中可用的Docker文件,其中包含所有必需的安装。

此外,请注意,您可能必须获得许可才能在商业应用程序中使用SURF。

罗列App执行任务清单

该应用程序将分析每个捕获的帧以执行以下任务:

  • 特征提取:我们将使用加速鲁棒特征(SURF)描述感兴趣的对象,该算法用于查找图像中比例不变和旋转不变的独特关键点。 这些关键点将帮助我们确保在多个帧上跟踪正确的对象,因为对象的外观可能会不时发生变化。 找到不依赖于对象的观看距离或视角(因此,缩放比例和旋转不变性)的关键点很重要。
  • 特征匹配:我们将尝试使用近似最近邻居快速库(FLANN)在关键点之间建立对应关系,以查看帧是否包含与我们感兴趣的对象的关键点相似的关键点。 如果找到合适的匹配项,则将在每个帧上标记对象。
  • 特征跟踪:我们将使用各种形式的早期离群值检测和离群值剔除来逐帧跟踪感兴趣的目标对象,以加快算法的速度。
  • 透视变换:然后,我们将通过扭曲透视图来反转对象经过的所有平移和旋转,以使对象在屏幕中央垂直显示。 这样会产生一种很酷的效果,其中对象看起来像冻结在一个位置上,而整个周围的场景都围绕着它旋转。

下面的屏幕快照显示了前三个步骤的示例,即特征提取,匹配和跟踪。


屏幕截图的左侧是我们感兴趣的对象的模板图像,右侧是该模板图像的手持打印输出。 两个框架中的匹配特征用蓝线连接,并且找到的对象在右侧以绿色勾勒出轮廓。

最后一步是变换定位的对象,以便将其投影到额面上,如下图所示:


该图像看起来与原始模板图像大致相似,看起来很近,而整个场景似乎都在扭曲。

构思App

最终的应用程序将包括一个用于检测,匹配和跟踪图像特征的Python类,以及一个用于访问网络摄像头并显示每个已处理帧的脚本。

该项目将包含以下模块和脚本:

  • feature_matching:此模块包含用于特征提取,特征匹配和特征跟踪的算法。 我们将此算法与应用程序的其余部分分开,以便可以将其用作独立模块。
  • feature_matching.FeatureMatching:此类实现了整个功能匹配过程。 它接受蓝色,绿色,红色(BGR)相机框架,并尝试在其中找到感兴趣的对象。
  • chapter3:这是主要脚本。
  • chapter3.main::这是启动应用程序,访问摄像机,将要处理的每一帧发送到FeatureMatching类的实例以及显示结果的主要功能例程。

设置App

在深入了解功能匹配算法之前,我们需要确保可以访问网络摄像头并显示视频流。

运行main()函数例程

要运行我们的应用程序,我们将需要执行main()函数例程。 以下步骤向我们展示了main()例程的执行:

  1. 该函数首先使用VideoCapture方法访问网络摄像头,方法是传递0作为参数,这是对默认网络摄像头的引用。 如果无法访问网络摄像头,则该应用将终止:
    import cv2 as cv
    from feature_matching import FeatureMatching
    def main():
     capture = cv.VideoCapture(0)
     assert capture.isOpened(), "Cannot connect to camera"
  1. 然后,设置期望的帧大小和视频流的每秒帧数。 以下代码段显示了用于设置视频的帧大小和每秒帧数的代码:
     capture.set(cv.CAP_PROP_FPS, 10)
     capture.set(cv.CAP_PROP_FRAME_WIDTH, 640)
     capture.set(cv.CAP_PROP_FRAME_HEIGHT, 480)
  1. 接下来,使用描述感兴趣对象的模板(或训练)文件的路径初始化FeatureMatching类的实例。 以下代码显示FeatureMatching类:
    matching = FeatureMatching(train_image='train.png')
  1. 之后,要处理摄像机中的帧,我们从capture.read函数创建一个迭代器,当该函数无法返回帧时,迭代器将终止((False,None))。 在以下代码块中可以看到:
    for success, frame in iter(capture.read, (False, None)):
     cv.imshow("frame", frame)
     match_succsess, img_warped, img_flann =
    matching.match(frame)

    在上一个代码块中,FeatureMatching.match方法处理BGR图像(capture.read以BGR格式返回帧)。 如果在当前帧中检测到对象,则match方法将报告match_success = True并返回变形的图像以及显示匹配的图像img_flann。

    显示结果

    实际上,只有在match方法返回结果时,我们才能显示结果,对吧? 在以下块中可以看到:

    if match_succsess:
     cv.imshow("res", img_warped)
     cv.imshow("flann", img_flann)
     if cv.waitKey(1) & 0xff == 27:
     break

处理流程

FeatureMatching类(尤其是公共match方法)提取,匹配和跟踪要素。 但是,在开始分析传入的视频流之前,我们需要做一些功课。 可能尚不清楚其中的某些含义(特别是对于SURF和FLANN)。

现在,我们只需要担心初始化:

class FeatureMatching:
 def __init__(self, train_image: str = "train.png") -> None:

以下步骤介绍了初始化过程:

  1. 以下代码行设置了SURF检测器,我们将使用该检测器从图像中检测和提取特征,其Hessian阈值介于300到500之间,即400:
    self.f_extractor = cv.xfeatures2d_SURF.create(hessianThreshold=400)
  1. 我们加载感兴趣的对象(self.img_obj)的模板,或者在找不到该对象时显示错误:
    self.img_obj = cv.imread(train_image, cv.CV_8UC1)
    assert self.img_obj is not None, f"Could not find train image
    {train_image}"
  1. 另外,为了方便起见,我们存储图像的形状(self.sh_train):
    self.sh_train = self.img_obj.shape[:2]

    我们将模板图像称为训练图像,因为将训练我们的算法来查找该图像,而每个传入帧均称为查询图像,因为我们将使用这些图像来查询训练图像。 以下照片是训练图像:

    先前的训练图像大小为512 x 512像素,将用于训练算法。

  1. 接下来,我们将SURF应用于感兴趣的对象。 这可以通过方便的函数调用完成,该函数返回键列表和描述符。
    self.key_train, self.desc_train = \
     self.f_extractor.detectAndCompute(self.img_obj, None)

    我们将对每个传入的帧执行相同的操作,然后比较图像之间的特征列表。

  1. 现在,我们设置一个FLANN对象,该对象将用于匹配火车的特征并查询图像(有关更多详细信息,请参阅“了解特征匹配”部分)。 这要求通过字典指定一些其他参数,例如使用哪种算法以及并行运行多少棵树:
    index_params = {"algorithm": 0, "trees": 5}
    search_params = {"checks": 50}
    self.flann = cv.FlannBasedMatcher(index_params, search_params)
  1. 最后,初始化一些其他簿记变量。 当我们想要使功能跟踪更快,更准确时,这些功能将派上用场。 例如,我们将跟踪最新的计算单应性矩阵以及已花费的帧数,而不会找到感兴趣的对象。
    self.last_hinv = np.zeros((3, 3))
    self.max_error_hinv = 50.
    self.num_frames_no_success = 0
    self.max_frames_no_success = 5

然后,大部分工作由FeatureMatching.match方法完成。 此方法遵循此处阐述的过程:

  1. 它从每个传入的视频帧中提取有趣的图像特征。
  1. 它与模板图像和视频帧之间的功能匹配。 这是在FeatureMatching.match_features中完成的。 如果找不到这样的匹配项,它将跳到下一帧。
  1. 它找到视频帧中模板图像的拐角点。 这是在detect_corner_points函数中完成的。 如果任何一个角位于(显着)框架外,则跳至下一帧。
  1. 它计算四个角点跨越的四边形的面积。 如果该区域太小或太大,则跳至下一帧。
  1. 它概述了当前帧中模板图像的拐角点。
  1. 它找到将已定位的对象从当前帧移至平行平面所必需的透视变换。 如果结果与我们先前在较早的帧中获得的结果明显不同,则跳至下一帧。
  1. 它扭曲当前帧的透视图,以使感兴趣的对象显得居中且直立。

特征提取

特征检测

使用SURF检测图像中的特征

使用SURF获取特征描述符

特征匹配

使用FLANN在图像之间匹配功能

测试比率以消除异常值

可视化特征匹配

映射单应性估计

扭曲图像

特征追踪

了解早期异常值检测和拒绝

算法实践

视角点


文章分类: 编程碎语PythonOpenCV