二十、为任务寻找最佳 OpenCV 算法
任何计算机视觉问题都可以用不同的方式解决。 根据数据、资源或目标的不同,每种方法都有其优缺点和成功的相对衡量标准。在使用 OpenCV 时,计算机视觉工程师手头有许多算法选项来解决给定的任务。 在知情的情况下做出正确的选择是极其重要的,因为它可以对整个解决方案的成功产生巨大影响,并防止您被束缚在僵化的实现中。 本章将讨论在考虑 OpenCV 中的选项时应遵循的一些方法。 我们将讨论 OpenCV 覆盖的计算机视觉领域,如果存在多个竞争算法,如何在竞争算法之间进行选择,如何衡量算法的成功,最后讨论如何用流水线以稳健的方式衡量成功。
本章将介绍以下主题:
- 它是否包含在 OpenCV 中? 使用 OpenCV 中提供的算法的计算机视觉主题。
- 选择哪种算法? OpenCV 中包含多个可用解决方案的主题。
- 如何知道哪种算法是最好的? 建立衡量算法成功的度量标准。
- 使用管道在相同的数据上测试不同的算法。
技术要求
本章中使用的技术和安装如下:
- 具有 Python 绑定的 OpenCV v3 或 v4
- Jupiter 笔记本服务器
上面列出的组件的构建说明以及实现本章中所示概念的代码将在附带的代码存储库中提供。
本章的代码可以通过 gihub:https://github.com/PacktPublishing/Building-Computer-Vision-Projects-with-OpenCV4-and-CPlusPlus/tree/master/Chapter20 访问。
它是否包含在 OpenCV 中?
当第一次处理计算机视觉问题时,任何工程师都应该首先问:我是应该从头开始实施解决方案,还是从纸上或已知方法中实施解决方案,还是使用现有的解决方案并使其适合我的需求?
这个问题与 OpenCV 中提供的实现密切相关。 幸运的是,OpenCV 对规范的和特定的计算机视觉任务都有非常广泛的覆盖范围。 另一方面,并不是所有的 OpenCV 实现都可以轻松地应用于给定的问题。 例如,虽然 OpenCV 提供了一些先进的对象识别和分类功能,但它远远逊于人们在会议和文学中看到的最先进的计算机视觉。 在过去的几年里,当然是在 OpenCV V4.0 中,有一项努力是将深度卷积神经网络与 OpenCV API 轻松集成(通过核心dnn
模块),这样工程师就可以享受所有最新和最伟大的工作。
我们努力列出了 OpenCVV4.0 中当前提供的算法,以及对它们对宏伟的计算机视觉主题的覆盖面的主观估计。 我们还注意到 OpenCV 是否提供 GPU 实现覆盖范围,以及该主题是在核心模块还是在 Conrib 模块中涵盖。 Contrib 模块各不相同;有些模块非常成熟,并提供文档和教程(例如,tracking
),而另一些模块是黑盒实现,文档非常差(例如,xobjectdetect
)。 拥有核心模块实现是一个好兆头,它将会有足够的文档、示例和健壮性。
以下是计算机视觉的主题列表及其在 OpenCV 中的最高级别:
| 主题 | 覆盖范围 | OpenCV 产品 | _ | GPU? | | 图像加工,图像处理 | 非常好 | 线性和非线性过滤、变换、色彩空间、直方图、形状分析、边缘检测 | 肯定的回答 / 赞成 / 是 | 满意的 / 合格的 / 好的 / 愉快的 | | 特征检测 | 非常好 | 角点检测、关键点提取、描述符计算 | 是+Conrib | 穷的 / 糟糕的 / 差的 / 可怜的 | | 分割 | 平庸的 / 平凡的 | 分水岭,轮廓和连通分量分析,二值化和阈值,GrabCut,前景-背景分割,超像素 | 是+Conrib | 穷的 / 糟糕的 / 差的 / 可怜的 | | 图像对齐、拼接、稳定 | 满意的 / 合格的 / 好的 / 愉快的 | 全景拼接流水线,视频稳定流水线,模板匹配,变换估计,翘曲,无缝拼接 | 是+Conrib | 穷的 / 糟糕的 / 差的 / 可怜的 | | 从运动到结构 | 穷的 / 糟糕的 / 差的 / 可怜的 | 相机位姿估计、基本和基本矩阵估计、与外部 Sfm 库集成 | 是+Conrib | 毫不 / 绝不 | | 运动估计、光流、跟踪 | 满意的 / 合格的 / 好的 / 愉快的 | 光流算法、卡尔曼滤波、目标跟踪框架、多目标跟踪 | 主要是人为设计的 | 穷的 / 糟糕的 / 差的 / 可怜的 | | 立体和三维重建 | 满意的 / 合格的 / 好的 / 愉快的 | 立体匹配框架、三角测量、结构光扫描 | 是+Conrib | 满意的 / 合格的 / 好的 / 愉快的 | | 摄像机定标 | 非常好 | 从多个模式进行校准,立体声钻机校准 | 是+Conrib | 毫不 / 绝不 | | 目标检测 | 平庸的 / 平凡的 | 级联分类器、二维码检测器、人脸地标检测器、3D 对象识别、文本检测 | 是+Conrib | 穷的 / 糟糕的 / 差的 / 可怜的 | | 目标识别、分类 | 穷的 / 糟糕的 / 差的 / 可怜的 | Eigen 和 Fisher 人脸识别,词袋 | 主要是人为设计的 | 毫不 / 绝不 | | 计算摄影 | 平庸的 / 平凡的 | 去噪、HDR、超分辨率 | 是+Conrib | 毫不 / 绝不 |
虽然 OpenCV 在传统的计算机视觉算法(如图像处理、摄像机校准、特征提取和其他主题)方面做了大量工作,但它对 SfM 和对象分类等重要主题的覆盖率也很低。 在其他方面,如分段,它提供了一个不错的产品,但仍然没有达到最先进的水平,尽管它几乎完全转移到了卷积网络,基本上可以使用dnn
模块来实现。
在一些主题中,例如特征检测、提取和匹配,以及摄像机校准,OpenCV 被认为是当今最全面、最免费和最可用的库,可能在成千上万的应用中使用。 然而,在计算机视觉项目的过程中,工程师可能会在原型阶段之后考虑与 OpenCV 解耦,因为库很重,并且大大增加了构建和部署的开销(这对移动应用来说是一个严重的问题)。 在这些情况下,OpenCV 是一个很好的原型制作拐杖,因为它提供了广泛的产品,对测试很有用,并且可以在同一任务的不同算法之间进行选择,例如,计算 2D 特征。 除了原型之外,还有许多其他的考虑因素变得更加重要,比如执行环境、代码的稳定性和可维护性、权限和许可等等。 在那个阶段,使用 OpenCV 应该满足产品的要求,包括前面提到的注意事项。
OpenCV 中的算法选项
OpenCV 有许多涵盖同一主题的算法。 在实现新的处理流水线时,有时流水线中的一个步骤有多种选择。 例如,在第 14 章,使用 SfM 模块从运动中探索结构,我们随意决定使用 AKAZE 功能查找图像之间的地标以估计摄像机运动,以及稀疏 3D 结构,然而,在 OpenCV 的features2D
模块中有更多类型的 2D 功能可用。 更明智的操作模式应该是根据我们的需要,根据其性能选择要使用的特征算法类型。 至少,我们需要意识到不同的选择。
同样,我们希望创建一种方便的方法来查看同一任务是否有多个选项。 我们创建了一个表,其中列出了在 OpenCV 中具有多个算法实现的特定计算机视觉任务。 我们还努力标记算法是否有共同的抽象 API,从而在代码中轻松且完全可互换。 虽然 OpenCV 为其大多数算法(如果不是全部算法)提供了cv::Algorithm
基类抽象,但这种抽象处于非常高的级别,对多态性和互换性的支持很少。 从我们的审查中,我们排除了机器学习算法(ml
模块和cv::StatsModel
通用 API),因为它们不是适当的计算机视觉算法,以及实际上确实有重叠实现的低级图像处理算法(例如,Hough 检测器系列)。 我们还排除了阴影几个核心主题的 GPU CUDA 实现,例如对象检测、背景分割、2D 功能等等,因为它们大多是 CPU 实现的副本。
以下是 OpenCV 中具有多个实施的主题:
| 主题 | 实施 | 帖子主题:Re:Колибри |
| 光流 | video
模块:SparsePyrLKOpticalFlow
,FarnebackOpticalFlow
,DISOpticalFlow
,VariationalRefinement``optflow
Conrib 模块:DualTVL1OpticalFlow
,OpticalFlowPCAFlow
| 肯定的回答 / 赞成 / 是 |
| 目标跟踪 | track
控制模块:TrackerBoosting
,TrackerCSRT
,TrackerGOTURN
,TrackerKCF
,TrackerMedianFlow
,TrackerMIL
,TrackerMOSSE
,TrackerTLD
外部:DetectionBasedTracker
| 是1 |
| 目标检测 | objdetect
模块:CascadeClassifier
,HOGDescriptor
,QRCodeDetector
,linemod
Conrib 模块:Detector``aruco
Conrib 模块:aruco::detectMarkers
| 没有Колибри2Колибри |
| 2D 要素 | OpenCV 最成熟的通用 API。features2D
模块:AgastFeatureDetector
、AKAZE
、BRISK
、FastFeatureDetector
、GFTTDetector
、KAZE
,MSER
,ORB
,SimpleBlobDetector``xfeatures2D
Conrib 模块:BoostDesc
,BriefDescriptorExtractor
,DAISY
,FREAK
,HarrisLaplaceFeatureDetector
,LATCH
,LUCID
,MSDDetector
,SIFT
,StarDetector
,SURF
,VGG
| 肯定的回答 / 赞成 / 是 |
| 特征匹配 | BFMatcher
,FlannBasedMatcher
| 肯定的回答 / 赞成 / 是 |
| 背景减去 | video
模块:BackgroundSubtractorKNN
,BackgroundSubtractorMOG2``bgsegm
控制模块:BackgroundSubtractorCNT
,BackgroundSubtractorGMG
,BackgroundSubtractorGSOC
,BackgroundSubtractorLSBP
,BackgroundSubtractorMOG
| 肯定的回答 / 赞成 / 是 |
| 摄像机定标 | calib3d
模块:calibrateCamera
,calibrateCameraRO
,stereoCalibrate``aruco
Conrib 模块:calibrateCameraArcuo
,calibrateCameraCharuco``ccalib
Conrib 模块:omnidir::calibrate
,omnidir::stereoCalibrate
| 不 / 否决票 / 同 Noh |
| 立体重建 | calib3d
模块:StereoBM
,StereoSGBM``stereo
Conrib 模块:StereoBinaryBM
,StereoBinarySGBM``ccalib
Conrib 模块:omnidir::stereoReconstruct
| 部分3 |
| 估算 | solveP3P
,solvePnP
,solvePnPRansac
| 不 / 否决票 / 同 Noh |
1仅适用于track
Conrib 模块中的类。
2某些类共享同名函数,但没有继承的抽象类。
3每个模块本身都有一个库,但不能在模块之间共享。
在使用几个算法选项处理问题时,重要的是不要过早地执行一条执行路径。 我们可以使用上表来查看存在的选项,然后探索它们。 接下来,我们将讨论如何从选项池中进行选择。
哪种算法最好?
计算机视觉是一个知识的世界,是一项长达数十年的研究。 与许多其他学科不同,计算机视觉没有很强的层次性或垂直性,这意味着针对给定问题的新解决方案并不总是更好,也可能不是基于之前的工作。 作为一个应用领域,计算机视觉算法的产生关注了以下几个方面,这可能解释了非垂直发展:
- 计算资源:CPU、GPU、嵌入式系统、内存占用、网络连接。
- 数据:图像大小、图像数量、图像流(摄像机)数量、数据类型、顺序性、照明条件、场景类型等。
- 性能要求:实时输出或其他定时限制(例如,人的感知)、准确性和精确度。
- 元算法:算法简单性(交叉引用 Occam‘s Razor 定理)、实现系统和外部工具、形式证明的可用性。
由于每个算法都是为了迎合其中某个考虑因素而创建的,如果不正确测试其中的一些或全部,就永远无法确定它是否会优于所有其他算法。 诚然,测试给定问题的所有算法是不切实际的,即使实现确实是可用的,而且 OpenCV 肯定有很多实现可用,正如我们在上一节中所看到的。 另一方面,如果计算机视觉工程师不考虑他们的算法选择导致他们的实现不是最优的可能性,那么他们就是玩忽职守。 这在本质上源于没有免费午餐定理,该定理概括地说,在整个可能的数据集空间中,没有一个算法是最好的算法。
因此,在承诺选择其中最好的算法选项之前,测试一组不同的算法选项是一种非常受欢迎的做法。 但是我们如何找到最好的呢? 单词BEST意味着每个人都会比其他人好(或差),这反过来又意味着有一个客观的量表或衡量标准,在这个标准或衡量标准中,他们都被重新评分并按顺序排序。 显然,对于所有问题中的所有算法,没有单一的度量(度量),每个问题都会有自己的度量。 在许多情况下,成功的度量将形成对错误的度量,即与已知的基本事实值的偏差,该值来自人类或我们可以信任的其他算法。 在优化中,这被称为损失函数或成本函数,我们希望最小化(有时最大化)该函数,以便找到得分最低的最佳选项。 另一类重要的度量不太关心输出性能(例如,错误),而更关心运行时计时、内存占用、容量和吞吐量等。
*以下是我们在部分计算机视觉问题中可能会看到的指标的部分列表:
| 任务 | 示例指标 | | 重建、配准、特征匹配 | 平均绝对误差(MAE), 均方误差(MSE), 均方根误差(RMSE), 距离平方和(SSD) | | 目标分类、识别 | 准确率、精确度、召回率、F1 得分、 假阳性率(FPR) | | 分割,目标检测 | 并集交集(借条) | | 特征检测 | 可重复性, 精度召回 |
为什么要为给定的任务找到最佳算法,要么是在测试场景中设置我们可以使用的所有选项,并根据选择的指标衡量它们的性能,要么是在标准实验或数据集上获得其他人的测量结果。 应该选择排名最高的选项,其中排名是从多个指标的组合中得出的(在只有一个指标的情况下,这是一项简单的任务)。 接下来,我们将尝试这样一个任务,并在最佳算法上做出知情选择。
算法比较性能测试实例
例如,我们将设置一个场景,要求我们对齐重叠的图像,就像在全景或航空照片拼接中所做的那样。 我们需要测量性能的一个重要特性是具有基本事实,这是我们试图用近似方法恢复的真实条件的精确测量。 地面真实数据可以从提供给研究人员用于测试和比较他们的算法的数据集中获得;事实上,许多这样的数据集都存在,并且计算机视觉研究人员一直在使用它们。 寻找计算机视觉数据集的一个很好的资源是又一个计算机视觉索引到数据集(YACVID),或者https://riemenschneider.hayko.at/vision/dataset/,它在过去八年中一直在积极维护,包含成百上千个到数据集的链接。 以下也是一个很好的数据来源:https://github.com/jbhuang0604/awesome-computer-vision#datasets。
然而,我们将选择一种不同的方式来获取基本事实,这在计算机视觉文献中得到了很好的实践。 我们将在我们的参数控制中创造一个人为的情况,并创建一个基准,我们可以改变这个基准来测试我们算法的不同方面。 在我们的示例中,我们将获取单个图像,并将其分割为两个重叠的图像,然后对其中一个图像应用一些变换。 使用我们的算法对图像进行融合将试图重建原始的融合图像,但它可能不会做得很完美。 我们在系统中选择片段时所做的选择(例如,2D 特征的类型、特征匹配算法和变换恢复算法)将影响最终结果,我们将对其进行测量和比较。 通过使用人造地面真实数据,我们可以很好地控制试验的条件和水平。
考虑下图及其双向重叠拆分:
Image: https://pixabay.com/en/forest-forests-tucholski-poland-1973952/
我们保持左边的图像不变,同时对右边的图像执行人工变换,看看我们的算法能够多好地撤销它们。 为简单起见,我们将只旋转几个括号中的右侧图像,如下所示:
我们为无旋转的情况添加了一个中间括号,在这种情况下,右侧的图像只被稍微平移了一些。“这构成了我们的地面真实数据,其中我们确切地知道发生了什么变换以及原始输入是什么。
我们的目标是衡量不同 2D 特征描述符类型在对齐图像方面的成功程度。 衡量我们是否成功的一个标准可以是最终重新拼接图像像素上的均方误差(MSE)。 如果转换恢复做得不是很好,像素将不会完全对齐,因此我们预计会看到很高的 MSE。 当 MSE 接近零时,我们知道拼接做得很好。 出于实际原因,我们可能还想知道哪个特性是最有效的,这样我们还可以测量执行时间。 为此,我们的算法可以非常简单:
- 将原始图像分割为左图像和右图像。
- 对于每种要素类型(SURF、SIFT、ORB、AKAZE、BRISK),请执行以下操作:
- 在左侧图像中查找关键点和特征。
- 对于每个旋转角度[-90,-67,...,67,90],请执行以下操作:
- 按旋转角度旋转右侧图像。
- 在旋转的右侧图像中查找关键点和特征。
- 在旋转的右图像和左图像之间匹配关键点。
- 估计刚性 2D 变换。
- 根据估计进行变换。
- 使用原始未分割图像测量最终结果的MSE。
- 测量提取、计算和匹配要素并执行对齐所需的总时间。
作为一种快速优化,我们可以缓存旋转的图像,而不计算每个特征类型的图像。 算法的其余部分保持不变。 此外,为了在时间上保持公平,我们应该注意为每种特征类型提取相似数量的关键点(例如,2500 个关键点),这可以通过设置关键点提取函数的阈值来实现。
注对齐执行管道与特征类型无关,并且在给定匹配关键点的情况下工作方式完全相同。 这是测试许多选项的一个非常重要的功能。 使用 OpenCV 的cv::Feature2D
和cv::DescriptorMatcher
公共基础 API 可以实现这一点,因为所有功能和匹配器都实现它们。 但是,如果我们看一下中的表格,它是否包含在 OpenCV 中?一节中,我们可以看到这可能不适用于 OpenCV 中的所有视觉问题,因此我们可能需要添加我们自己的仪器代码来进行此比较。
在附带的代码中,我们可以找到该例程的 Python 实现,它提供以下结果。 为了测试旋转不变性,我们改变角度并测量重建的均方误差:
对于相同的实验,我们记录了一种特征类型的所有实验的平均 MSE,以及平均执行时间,如下所示:
结果分析,我们可以清楚地看到一些功能在 MSE 方面表现更好,无论是在不同的旋转角度还是整体上,我们还可以看到时间上的很大差异。 在旋转角度范围内,AKAZE 和 SURF 的对准成功率似乎是最高的,AKAZE 在更高的旋转角度(~60°)时更具优势。 然而,在角度变化非常小的情况下(旋转角接近 0°),SIFT 在 MSE 接近于零的情况下实现了几乎完美的重建,它的重建效果也不亚于旋转在 30°以下的其他图像。 ORB 在整个领域做得非常糟糕,虽然 Bliisk 没有那么糟糕,但很少能够击败任何先行者。
考虑到时间,ORB 和 BRISK(本质上是相同的算法)是明显的赢家,但它们在重建精度方面都远远落后于其他算法。 AKAZE 和 SURF 是计时性能不相上下的领先者。
现在,由我们作为应用开发人员根据项目的要求对功能进行排序。 根据我们执行的这次测试的数据,应该很容易做出决定。 如果我们在寻找速度,我们会选择轻快,因为它是最快的,而且性能比 ORB 更好。 如果我们追求精确度,我们会选择 AKAZE,因为它是最好的表演者,而且比冲浪更快。 使用 SURF 本身就是一个问题,因为算法不是免费的,而且它是受专利保护的,所以我们很幸运地发现 AKAZE 是一个免费和足够的替代方案。
这是一个非常初级的测试,只看了两个简单的测量(MSE 和时间)和一个变化的参数(旋转)。 在实际情况中,我们可能希望根据系统的要求在转换中加入更多的复杂性。 例如,我们可以使用完全透视变换,而不仅仅是刚性旋转。 此外,我们可能希望对结果进行更深入的统计分析。 在此测试中,对于每个旋转条件,我们只运行一次对齐过程,这不利于捕获良好的计时度量,因为某些算法可能受益于连续执行(例如,将静态数据加载到内存)。 如果我们有多次执行,我们可以对执行中的差异进行推理,并计算标准差或误差,以便为我们的决策过程提供更多信息。 最后,给定足够的数据,我们可以执行统计推断过程和假设检验,例如t 检验或方差分析(ANOVA),以确定条件之间的细微差别(例如,AKAZE 和 SURF)是否具有统计显著性,或者太过嘈杂而无法区分。
简略的 / 概括的 / 简易判罪的 / 简易的
为这项工作选择最好的计算机视觉算法是一个虚幻的过程,这是许多工程师不执行它的原因。 虽然针对不同选择发布的调查工作提供了基准性能,但在许多情况下,它没有对工程师可能遇到的特定系统需求进行建模,因此必须实施新的测试。 测试算法选项的主要问题是检测代码,这对工程师来说是一项额外的工作,而且并不总是那么简单。 OpenCV 为几个视觉问题领域的算法提供了基础 API,但覆盖年限并不完整。 另一方面,OpenCV 覆盖了非常广泛的计算机视觉问题,是执行此类测试的主要框架之一。
在选择算法时做出明智的决定是视觉工程的一个非常重要的方面,有许多要素需要优化,例如,速度、准确性、简单性、内存占用,甚至可用性。 每个视觉系统工程都有特定的要求,这些要求会影响每个元素的权重,从而影响最终的决策。 通过相对简单的 OpenCV 代码,我们了解了如何收集数据、绘制图表,并就玩具问题做出明智的决定。
在下一章中,我们将讨论 OpenCV 开源项目的历史,以及使用 OpenCV 时的一些常见陷阱以及针对这些陷阱提出的解决方案。*