简介:为什么选择G-API
许多计算机视觉算法在视频流上运行,而不是在单个图像上运行。流处理通常由多个步骤组成,如解码、预处理、检测、跟踪、分类(对检测到的对象)和可视化,形成视频处理管道。此外,此类流水线的许多步骤可以并行运行——现代平台在同一芯片上具有不同的硬件模块,如解码器和 GPU,并且可以插入额外的加速器作为扩展,例如用于深度学习卸载的英特尔® Movidius™ 神经计算模块。
鉴于所有这些选项和视频分析算法的多样性,有效地管理此类管道很快就会成为一个问题。当然,它可以手动完成,但这种方法无法扩展:如果需要对算法进行更改(例如,添加新的流水线步骤),或者将其移植到具有不同功能的新平台上,则需要重新优化整个流水线。
从版本 4.2 开始,OpenCV 为此问题提供了解决方案。OpenCV G-API 现在可以通过传统的计算机视觉以及视频捕获/解码在单个管道中管理深度学习推理(任何现代分析管道的基石)。G-API 负责流水线本身,因此如果算法或平台发生变化,执行模型会自动适应它。
管道概述
我们的示例应用程序基于 OpenVINO™ 工具套件 Open Model Zoo 中的“交互式人脸检测”演示。简化的管道包括以下步骤:
- 图像采集与解码;
- 预处理检测;
- 使用两个网络对每个检测到的物体进行分类和预处理;
- 可视化。
构建管道
为视频流案例构建 G-API 图与常规使用 G-API 没有太大区别——它仍然是关于定义图数据(使用 cv::GMat、cv::GScalar 和 cv::GArray)及其操作。推理也成为图中的操作,但定义方式略有不同。
声明深度学习拓扑
与传统的 CV 函数(参见 core 和 imgproc)相比,G-API 为每个函数声明不同的操作,而 G-API 中的推理是单个通用操作 cv::gapi::infer<>。像往常一样,它只是一个接口,可以在后台以多种方式实现。在 OpenCV 4.2 中,只有基于 OpenVINO™ 推理引擎的后端可用,OpenCV 自己的基于 DNN 模块的后端即将推出。
cv::gapi::infer<> 由我们将要执行的拓扑的详细信息进行参数化。与操作一样,G-API 中的拓扑是强类型的,并使用特殊的宏 G_API_NET() 进行定义:
与使用 G_API_OP() 定义操作的方式类似,网络描述需要三个参数:
- 类型名称。每个定义的拓扑都被声明为一个不同的 C++ 类型,在程序中进一步使用 - 见下文;
- 类似 - 的 API 签名。G-API 特征网络作为常规“函数”,用于获取和返回数据。这里 network(检测器)接受一个 cv::GMat 并返回一个 cv::GMat,而已知 network 提供两个输出(分别是年龄和性别 blob)——因此它有一个作为返回类型。
std::function<>
Faces
AgeGender
std::tuple<>
- 拓扑名称 – 可以是任何非空字符串,G-API 使用这些名称来区分内部网络。名称在单个图形的作用域中应该是唯一的。
构建 GComputation
现在上面的流水线用 G-API 表示,如下所示:
每个管道都从声明空数据对象开始,这些对象充当管道的输入。然后我们调用一个通用的 cv::gapi::infer<>专门用于检测网络。cv::gapi::infer<> 从其模板参数继承其签名 – 在本例中,它需要一个输入 cv::GMat 并生成一个输出 cv::GMat。Faces
在此示例中,我们使用基于 SSD 的预训练网络,其输出需要解析为检测数组(感兴趣的对象区域,ROI)。它是由一个自定义操作完成的,该操作将一个矩形数组(类型)返回给管道。此操作还会通过置信度阈值过滤掉结果,而这些细节隐藏在内核本身中。尽管如此,在图构建的那一刻,我们只使用接口进行操作,不需要实际的内核来表达管道——所以这个后处理的实现将在后面列出。custom::PostProc
cv::GArray<cv::Rect>
在检测结果输出被解析为对象数组后,我们可以对其中任何一个对象运行分类。G-API 目前还不支持图内循环的语法,但 cv::gapi::infer<> 带有一个特殊的面向列表的重载。for_each()
用户可以调用 cv::gapi::infer<> 将 cv::GArray 作为第一个参数,因此 G-API 假设它需要在给定帧的给定列表(第二个参数)的每个矩形上运行关联的网络。这种操作的结果也是一个列表——cv::GMat 的 cv::GArray。
由于网络本身产生两个输出,因此基于列表的 cv::gapi::infer 版本的输出类型是数组的元组。我们习惯于将此输入分解为两个不同的对象。AgeGender
std::tie()
Emotions
network 生成单个输出,因此其基于列表的推理的返回类型为 。cv::GArray<cv::GMat>
配置管道
G-API 将构造与配置严格分开,其理念是保持算法代码本身与平台无关。在上面的清单中,我们只声明了我们的操作并表达了整体数据流,但甚至没有提到我们使用 OpenVINO™。我们只描述了我们的工作,但没有描述我们如何做。将这两个方面明确分开是 G-API 的设计目标。
编译管道时会出现特定于平台的细节,即从声明形式转变为可执行形式。如何运行东西的方式是通过编译参数指定的,新的推理/流式处理功能也不例外。
G-API 建立在实现接口的后端之上(详见架构和内核)——因此 cv::gapi::infer<> 是一个可以由不同后端实现的功能。在 OpenCV 4.2 中,只有用于推理的 OpenVINO™ 推理引擎后端可用。G-API 中的每个推理后端都必须提供一个特殊的可参数化结构来表达特定于后端的神经网络参数——在本例中,它是 cv::gapi::ie::P arams:
自动det_net = cv::gapi::ie::P arams<custom::Faces> {cmd.get<std::string>(“fdm”), // 读取 cmd args: 拓扑 IR 的路径cmd.get<std::string>(“fdw”), // 读取 cmd args: 权重路径cmd.get<std::string>(“fdd”), // 读取 cmd args: device specifier};自动age_net = cv::gapi::ie::P arams<custom::AgeGender> {cmd.get<std::string>(“agem”), // 读取 cmd args: 拓扑 IR 的路径cmd.get<std::string>(“agew”), // 读取 cmd args: 权重路径cmd.get<std::string>(“aged”), // 读取 cmd args: device specifier}.cfgOutputLayers({ “age_conv3”, “prob” });自动emo_net = cv::gapi::ie::P arams<custom::Emotions> {cmd.get<std::string>(“emom”), // 读取 cmd args: 拓扑 IR 的路径cmd.get<std::string>(“emow”), // 读取 cmd args: 权重路径cmd.get<std::string>(“emod”), // 读取 cmd args: device specifier};
这里我们定义了三个参数对象:、 和 。每个对象都是我们使用的每个特定网络的 cv::gapi::ie::P arams 结构参数化。在编译阶段,G-API 会使用此信息自动将网络参数与其 cv::gapi::infer<> 调用在图形中匹配。det_net
age_net
emo_net
无论拓扑结构如何,每个参数结构都由三个字符串参数构建,特定于 OpenVINO™ 推理引擎:
- 拓扑的中间表示形式(.xml 文件)的路径;
- 拓扑模型权重的路径(.bin 文件);
- 运行位置的设备 – “CPU”、“GPU” 等 – 取决于您的 OpenVINO™ 工具套件安装。这些参数取自命令行分析器。
定义网络并实现自定义内核后,将编译管道以进行流式处理:
形成一个内核包(使用我们的后处理)和一个网络包(包含我们的三个网络)。自动内核 = cv::gapi::kernels<custom::OCVPostProc>();汽车网络 = cv::gapi::networks(det_net, age_net, emo_net);编译我们的管道,并将我们的内核和网络作为参数。这是 G-API 学习的地方我们实际使用的网络和内核(图描述本身对此一无所知)。auto cc = pp.compileStreaming(cv::compile_args(内核,网络));
cv::GComputation::compileStreaming() 触发了一种特殊的面向视频的图形编译形式,其中 G-API 试图优化吞吐量。此编译的结果是一个特殊类型的 cv::GStreamingCompiled 对象 – 与传统的可调用 cv::GCompiled 相比,这些对象在语义上更接近媒体播放器。
注意
无需在 cv::GComputation::compileStreaming() – G-API 中传递描述输入视频流格式的元数据参数,自动计算输入向量的格式,并动态调整管道以适应这些格式。用户仍然可以像使用常规 cv::GComputation::compile() 一样将元数据传递到那里,以便将管道修复为特定的输入格式。
运行管道
流水线优化基于同时处理多个输入视频帧,并行运行流水线的不同步骤。这就是为什么当框架完全控制视频流时,它效果最好。
流式处理 API 背后的想法是,用户为管道指定一个输入源,然后 G-API 自动管理其执行,直到源结束或用户中断执行。G-API 从源中提取新的图像数据,并将其传递到管道进行处理。
流源由接口 cv::gapi::wip::IStreamSource 表示。实现此接口的对象可以通过帮助程序函数作为常规输入传递给。在 OpenCV 4.2 中,每个流水线只允许一个流源 - 此要求将在未来放宽。GStreamingCompiled
cv::gin()
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::p ull() 或非阻塞 cv::GStreamingCompiled::try_pull() 获取数据;重复上述步骤,直到直播结束:
指定数据源后,开始执行cc.start();声明我们将从管道接收的数据对象。cv::Mat框架;捕获的帧本身std::vector<cv::Rect>面;检测到的人脸数组标准::矢量<cv::Mat> out_ages;推断年龄数组(每张脸一个斑点)std::vector<cv::Mat> out_genders;推断的性别数组(每张脸一个斑点)标准::矢量<cv::Mat> out_emotions;分类情绪数组(每张脸一个斑点)根据显示选项实施不同的执行策略以获得最佳性能。而 (cc.running()) {自动out_vector = cv::gout(frame, faces, out_ages, out_genders, out_emotions);如果 (no_show) {这纯粹是一种视频处理。无需平衡具有 UI 渲染功能。使用阻塞 pull() 获取数据。如果流结束,则中断循环。if (!cc.pull(std::move(out_vector)))破;} 否则 if (!cc.try_pull(std::move(out_vector))) {使用非阻塞 try_pull() 获取数据。如果没有数据,请让 UI 刷新(并处理按键)if (cv::waitKey(1) >= 0) 中断;否则继续;}在这一点上,我们肯定有数据(在阻塞或非阻塞方式)。帧++;标签::D rawResults(frame, faces, out_ages, out_genders, out_emotions);标签::D rawFPS(frame, frames, avg.fps(frames));if (!no_show) cv::imshow(“Out”, frame);}
上面的代码可能看起来很复杂,但实际上它处理两种模式——带和不带图形用户界面 (GUI):
- 当示例以“无头”模式运行(设置了选项)时,此代码只是使用阻塞从管道中提取数据,直到它结束。这是性能最高的执行模式。
--pure
pull()
- 当结果也显示在屏幕上时,窗口系统需要花费一些时间来刷新窗口内容并处理 GUI 事件。在这种情况下,演示会以非阻塞方式拉取数据,直到没有更多可用数据(但它不会标记流的结束——只是意味着新数据尚未准备好),然后才显示最新获得的结果并刷新屏幕。使用此技巧减少在 GUI 中花费的时间可以稍微提高整体性能。
try_pull()
与串行模式的比较
该示例还可以在串行模式下运行,以用于参考和基准测试。在本例中,使用常规的 cv::GComputation::compile() 并生成常规的单帧 cv::GCompiled 对象;G-API 中未应用流水线优化;用户有责任从 cv::VideoCapture 对象获取图像帧并将其传递给 G-API。
在测试机器(英特尔®酷睿™ i5-6600)上,OpenCV 支持 [Intel® TBB] 支持,检测器网络分配给 CPU,分类器分配给 iGPU,流水线样本的性能比串行样本高出 1.36 倍(因此整体吞吐量增加了 +36%)。
结论
G-API 引入了一种构建和优化混合管道的技术方法。切换到新的执行模型不需要更改使用 G-API 表示的算法代码,只是触发图形的方式不同。
列表:后处理内核
G-API 提供了一种将自定义代码插入管道的简单方法,即使它以流模式运行并处理张量数据。推理结果由多维 cv::Mat 对象表示,因此访问这些对象就像使用常规 DNN 模块一样简单。
本示例中定义和实现基于 OpenCV 的 SSD 后处理内核,如下所示:
在线教程
- 麻省理工学院人工智能视频教程 – 麻省理工人工智能课程
- 人工智能入门 – 人工智能基础学习。Peter Norvig举办的课程
- EdX 人工智能 – 此课程讲授人工智能计算机系统设计的基本概念和技术。
- 人工智能中的计划 – 计划是人工智能系统的基础部分之一。在这个课程中,你将会学习到让机器人执行一系列动作所需要的基本算法。
- 机器人人工智能 – 这个课程将会教授你实现人工智能的基本方法,包括:概率推算,计划和搜索,本地化,跟踪和控制,全部都是围绕有关机器人设计。
- 机器学习 – 有指导和无指导情况下的基本机器学习算法
- 机器学习中的神经网络 – 智能神经网络上的算法和实践经验
- 斯坦福统计学习
有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓
人工智能书籍
- OpenCV(中文版).(布拉德斯基等)
- OpenCV+3计算机视觉++Python语言实现+第二版
- OpenCV3编程入门 毛星云编著
- 数字图像处理_第三版
- 人工智能:一种现代的方法
- 深度学习面试宝典
- 深度学习之PyTorch物体检测实战
- 吴恩达DeepLearning.ai中文版笔记
- 计算机视觉中的多视图几何
- PyTorch-官方推荐教程-英文版
- 《神经网络与深度学习》(邱锡鹏-20191121)
- …
第一阶段:零基础入门(3-6个月)
新手应首先通过少而精的学习,看到全景图,建立大局观。 通过完成小实验,建立信心,才能避免“从入门到放弃”的尴尬。因此,第一阶段只推荐4本最必要的书(而且这些书到了第二、三阶段也能继续用),入门以后,在后续学习中再“哪里不会补哪里”即可。
第二阶段:基础进阶(3-6个月)
熟读《机器学习算法的数学解析与Python实现》并动手实践后,你已经对机器学习有了基本的了解,不再是小白了。这时可以开始触类旁通,学习热门技术,加强实践水平。在深入学习的同时,也可以探索自己感兴趣的方向,为求职面试打好基础。
第三阶段:工作应用
这一阶段你已经不再需要引导,只需要一些推荐书目。如果你从入门时就确认了未来的工作方向,可以在第二阶段就提前阅读相关入门书籍(对应“商业落地五大方向”中的前两本),然后再“哪里不会补哪里”。
有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓