![]() |
OpenCV 4.12.0
开源计算机视觉
|
下一教程: 在 G-API 上移植各向异性图像分割
在本教程中,您将学习
此示例需要
face-detection-adas-0001;age-gender-recognition-retail-0013;emotions-recognition-retail-0003.许多计算机视觉算法运行在视频流上,而非单个图像上。流处理通常包含多个步骤——如解码、预处理、检测、跟踪、分类(针对检测到的对象)和可视化——构成一个视频处理管线。此外,这种管线的许多步骤可以并行运行——现代平台在同一芯片上拥有不同的硬件模块,如解码器和 GPU,并且可以插入额外的加速器作为扩展,例如用于深度学习卸载的 Intel® Movidius™ 神经计算棒。
鉴于如此多的选项和各种视频分析算法,有效地管理这些管线很快就成了一个问题。当然,可以手动完成,但这种方法不具备可扩展性:如果算法需要更改(例如,添加新的管线步骤),或者如果它被移植到具有不同功能的新平台上,则整个管线需要重新优化。
从 4.2 版本开始,OpenCV 提供了解决此问题的方法。OpenCV G-API 现在可以管理深度学习推理(任何现代分析管线的基石)以及传统的计算机视觉和视频捕获/解码,所有这些都在一个单一的管线中。G-API 负责管线化本身——因此,如果算法或平台发生变化,执行模型会自动适应。
我们的示例应用程序基于 OpenVINO™ Toolkit 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™ Inference Engine 的后端可用,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 中表示如下:
每个管线都从声明空数据对象开始——它们充当管线的输入。然后我们调用一个通用的 cv::gapi::infer<>,它被专门用于 Faces 检测网络。cv::gapi::infer<> 从其模板参数继承签名——在这种情况下,它期望一个输入 cv::GMat 并产生一个输出 cv::GMat。
在此示例中,我们使用一个预训练的基于 SSD 的网络,其输出需要解析为检测结果数组(感兴趣对象区域,ROIs)。这通过自定义操作 custom::PostProc 完成,该操作返回一个矩形数组(类型为 cv::GArray<cv::Rect>)返回到管线。此操作还会根据置信度阈值过滤结果——这些细节隐藏在内核本身中。然而,在图构建时,我们只操作接口,不需要实际的内核来表达管线——因此,此后处理的实现将在稍后列出。
检测结果输出被解析为对象数组后,我们可以对其中任何一个运行分类。G-API 尚不支持图中循环(如 for_each())的语法,但 cv::gapi::infer<> 提供了一个特殊的面向列表的重载。
用户可以使用 cv::GArray 作为第一个参数调用 cv::gapi::infer<>,G-API 就会认为需要在给定帧(第二个参数)的给定列表中对每个矩形运行关联的网络。此类操作的结果也是一个列表——一个 cv::GMat 的 cv::GArray。
由于 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™ Inference Engine 后端可用于推理。G-API 中的每个推理后端都必须提供一个特殊的、可参数化的结构来表达后端特定的神经网络参数——在这种情况下,它是 cv::gapi::ie::Params。
这里我们定义了三个参数对象:det_net、age_net 和 emo_net。每个对象都是我们使用的每个特定网络的 cv::gapi::ie::Params 结构参数化。在编译阶段,G-API 利用此信息自动将网络参数与图中其对应的 cv::gapi::infer<> 调用进行匹配。
无论拓扑如何,每个参数结构都由三个字符串参数构造——这些参数特定于 OpenVINO™ Inference Engine:
定义网络并实现自定义内核后,管线将为流式传输进行编译:
cv::GComputation::compileStreaming() 触发了一种特殊的面向视频的图编译形式,G-API 试图优化吞吐量。此编译的结果是特殊类型 cv::GStreamingCompiled 的对象——与传统的、可调用的 cv::GCompiled 不同,这些对象在语义上更接近媒体播放器。
管线优化基于同时处理多个输入视频帧,并行运行管线的不同步骤。这就是为什么当框架完全控制视频流时,它的效果最好。
流式 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。
示例应用程序指定输入源如下:
请注意,一个 GComputation 仍然可以有多个输入,例如 cv::GMat、cv::GScalar 或 cv::GArray 对象。用户也可以在输入向量中传递它们各自的主机端类型(cv::Mat、cv::Scalar、std::vector<>),但在流模式下,这些对象将创建“无限”的常量流。允许混合真实的视频源流和常量数据流。
运行管线很简单——只需调用 cv::GStreamingCompiled::start(),然后使用阻塞式 cv::GStreamingCompiled::pull() 或非阻塞式 cv::GStreamingCompiled::try_pull() 获取数据;重复此操作直到流结束:
上述代码可能看起来复杂,但实际上它处理两种模式——带图形用户界面 (GUI) 和不带图形用户界面 (GUI)。
--pure 选项)时,此代码简单地使用阻塞式 pull() 从管线中拉取数据,直到结束。这是性能最高的执行模式。try_pull() 拉取数据,直到没有更多可用数据(但这并不表示流结束——只表示新数据尚未准备好),然后才显示最新获取的结果并刷新屏幕。通过此技巧减少在 GUI 中花费的时间可以稍微提高整体性能。该示例也可以在串行模式下运行,用于参考和基准测试目的。在这种情况下,使用常规的 cv::GComputation::compile(),并生成一个常规的单帧 cv::GCompiled 对象;管线优化不应用于 G-API 内部;从 cv::VideoCapture 对象获取图像帧并将其传递给 G-API 是用户的责任。
在一台测试机器上(Intel® Core™ i5-6600),OpenCV 构建时支持 [Intel® TBB],检测器网络分配给 CPU,分类器分配给 iGPU,管线化的示例性能比串行模式高出 1.36 倍(因此整体吞吐量增加了 36%)。
G-API 引入了一种技术方法来构建和优化混合管线。切换到新的执行模型不需要更改使用 G-API 表达的算法代码——只有图的触发方式不同。
G-API 提供了一种简单的方法,即使在流模式下运行并处理张量数据,也可以将自定义代码插入到管线中。推理结果由多维 cv::Mat 对象表示,因此访问它们就像使用常规 DNN 模块一样简单。
基于 OpenCV 的 SSD 后处理内核在此示例中定义和实现如下: