![]() |
OpenCV 4.13.0
开源计算机视觉库 (Open Source Computer Vision)
|
下一个教程: 将各向异性图像分割移植到 G-API
在本教程中你将学习
本示例需要
face-detection-adas-0001;age-gender-recognition-retail-0013;emotions-recognition-retail-0003.许多计算机视觉算法在视频流而非单个图像上运行。流处理通常包含多个步骤——例如解码、预处理、检测、跟踪、分类(对检测到的对象)和可视化——构成一个视频处理流水线。此外,此类流水线的许多步骤可以并行运行——现代平台在同一芯片上具有不同的硬件块,如解码器和 GPU,并且可以插入额外的加速器作为扩展,例如用于深度学习卸载的 Intel® Movidius™ Neural Compute Stick。
鉴于所有这些选择的多样性和视频分析算法的多样性,有效管理此类流水线很快就成了一个问题。当然,这可以手动完成,但这种方法无法扩展:如果算法需要更改(例如,添加了新的流水线步骤),或者如果它被移植到具有不同功能的新平台,则需要对整个流水线进行重新优化。
从 4.2 版本开始,OpenCV 提供了一个解决方案。OpenCV G-API 现在可以在一个单独的流水线中管理深度学习推理(任何现代分析流水线的基石)、传统的计算机视觉以及视频捕获/解码。G-API 本身负责流水线——因此,如果算法或平台发生变化,执行模型会自动适应。
我们的示例应用程序基于 OpenVINO™ 工具包 Open Model Zoo 中的“交互式人脸检测”演示。简化的流水线包含以下步骤:
为视频流场景构建 G-API 图与 G-API 的常规用法差别不大——它仍然是关于定义图的数据(使用cv::GMat、cv::GScalar和cv::GArray)和对其执行的操作。推理也成为图中的一个操作,但定义方式略有不同。
与传统 CV 函数(请参阅核心和图像处理)不同,G-API 为每个函数声明不同的操作,而在 G-API 中,推理是一个通用的单一操作 cv::gapi::infer<>。和往常一样,这只是一个接口,它可以在底层以多种方式实现。在 OpenCV 4.2 中,只有基于 OpenVINO™ 推理引擎的后端可用,OpenCV 自有的 DNN 模块后端即将推出。
cv::gapi::infer<> 由我们将要执行的拓扑的详细信息参数化。与操作一样,G-API 中的拓扑是强类型的,并且使用特殊宏 G_API_NET() 定义
与使用 G_API_OP() 定义操作类似,网络描述需要三个参数:
std::function<> 风格的 API 签名。G-API 将网络视为常规的“函数”,这些函数接收和返回数据。这里,网络 Faces(一个检测器)接收一个 cv::GMat 并返回一个 cv::GMat,而网络 AgeGender 已知提供两个输出(分别为年龄和性别 blob)——因此,它具有 std::tuple<> 作为返回类型。现在,上述流水线在 G-API 中表示如下:
每个流水线都以声明空数据对象开始——这些对象充当流水线的输入。然后,我们调用专门针对 Faces 检测网络的通用 cv::gapi::infer<>。 cv::gapi::infer<> 从其模板参数继承签名——在这种情况下,它期望一个输入 cv::GMat 并产生一个输出 cv::GMat。
在此示例中,我们使用预训练的基于 SSD 的网络,其输出需要解析为一组检测(对象感兴趣区域,ROI)。这由自定义操作 custom::PostProc 完成,该操作返回一个矩形数组(类型为 cv::GArray<cv::Rect>)给流水线。此操作还会根据置信度阈值过滤结果——这些细节隐藏在内核本身中。尽管如此,在图构建时,我们只操作接口,而无需实际内核来表达流水线——因此,此后处理的实现将在后面列出。
在检测结果输出被解析为对象数组后,我们可以对其中任何一个进行分类。G-API 尚不支持图内循环(如 for_each())的语法,但 cv::gapi::infer<> 提供了一种特殊的面向列表的重载。
用户可以调用 cv::gapi::infer<>,并将 cv::GArray 作为第一个参数,因此 G-API 会假定它需要在给定帧(第二个参数)的每个矩形上运行关联的网络。此类操作的结果也是一个列表——一个 cv::GArray 的 cv::GMat。
由于 AgeGender 网络本身会产生两个输出,因此其面向列表的 cv::gapi::infer 的输出类型是数组的元组。我们使用 std::tie() 将其分解为两个独立的对象。
Emotions 网络产生一个输出,因此其面向列表的推理返回类型为 cv::GArray<cv::GMat>。
G-API 严格区分构建和配置——其理念是保持算法代码本身与平台无关。在上面的列表显示中,我们只声明了我们的操作并表达了整体数据流,但甚至没有提到我们使用了 OpenVINO™。我们只描述了我们做什么,而不是我们怎么做。将这两个方面明确分开是 G-API 的设计目标。
平台特定的细节出现在流水线编译时——即从声明性形式转变为可执行形式。如何运行事物的指定方式通过编译参数来完成,新的推理/流功能也不例外。
G-API 构建在实现接口的后端之上(有关详细信息,请参阅架构和内核)——因此 cv::gapi::infer<> 是一个可以由不同后端实现的函数。在 OpenCV 4.2 中,只有用于推理的 OpenVINO™ 推理引擎后端可用。G-API 中的每个推理后端都必须提供一个特殊的参数化结构来表达后端特定的神经网络参数——在这种情况下,它是 cv::gapi::ie::Params
此处,我们定义了三个参数对象:det_net、age_net 和 emo_net。每个对象都是一个 cv::gapi::ie::Params 结构,用于为我们使用的每个特定网络进行参数化。在编译阶段,G-API 使用此信息自动匹配参数与图中的 cv::gapi::infer<> 调用。
无论拓扑如何,每个参数结构都用三个字符串参数构造——特定于 OpenVINO™ 推理引擎:
一旦网络定义完毕且自定义内核实现完成,流水线就会为流式处理进行编译:
在 cv::GComputation::compileStreaming() 中无需传递描述输入视频流格式的元数据参数——G-API 会自动确定输入向量的格式,并实时调整流水线以适应这些格式。用户仍然可以像使用常规 cv::GComputation::compile() 一样在那里传递元数据,以将流水线固定到特定的输入格式。
流 API 背后的理念是,用户指定流水线的输入源,然后 G-API 会自动管理其执行,直到源结束或用户中断执行。G-API 从源拉取新的图像数据并将其传递给流水线进行处理。
流源由接口 cv::gapi::wip::IStreamSource 表示。实现此接口的对象可以通过 cv::gin() 辅助函数作为常规输入传递给 GStreamingCompiled。在 OpenCV 4.2 中,每个流水线只允许一个流源——这个要求将在未来放宽。
OpenCV 提供了出色的 cv::VideoCapture 类,并且默认情况下,G-API 提供了一个基于它的流源类——cv::gapi::wip::GCaptureSource。用户可以实现自己的流源,例如使用 VAAPI 或其他媒体或网络 API。
示例应用程序如下指定输入源:
auto in_src = cv::gapi::wip::make_src<cv::gapi::wip::GCaptureSource>(input);
运行流水线很容易——只需调用 cv::GStreamingCompiled::start() 并使用阻塞的 cv::GStreamingCompiled::pull() 或非阻塞的 cv::GStreamingCompiled::try_pull() 获取数据;重复直到流结束。
// 指定数据源后,启动执行
上面的代码可能看起来很复杂,但实际上它处理了两种模式——有和没有图形用户界面(GUI)。
pull() 从流水线中拉取数据,直到流水线结束。这是最高效的执行模式。try_pull() 拉取数据,直到没有更多数据可用(但它不标记流结束——只是表示新数据尚未准备好),然后才显示最新获取的结果并刷新屏幕。通过这种技巧减少 GUI 占用的时间可以略微提高整体性能。示例还可以以串行模式运行,用于参考和基准测试。在这种情况下,使用常规的 cv::GComputation::compile(),并生成常规的单帧 cv::GCompiled 对象;G-API 内部不应用流水线优化;用户负责从 cv::VideoCapture 对象获取图像帧并将其传递给 G-API。
在测试机器(Intel® Core™ i5-6600)上,使用支持 [Intel® TBB] 构建的 OpenCV,将检测器网络分配给 CPU,将分类器分配给 iGPU,流水线化示例的性能优于串行模式 1.36 倍(因此整体吞吐量提高了 36%)。
G-API 引入了一种构建和优化混合流水线的技术方法。切换到新的执行模型不需要更改使用 G-API 表达的算法代码——只需要更改触发图的方式。
G-API 提供了一种简便的方法,可以将自定义代码插入流水线,即使它处于流式处理模式并处理张量数据。推理结果由多维 cv::Mat 对象表示,因此访问它们就像访问常规 DNN 模块一样简单。
基于 OpenCV 的 SSD 后处理内核在此示例中定义和实现如下: