- 操作系统:ubuntu22.04
- OpenCV版本:OpenCV4.9
- IDE:Visual Studio Code
- 编程语言:C++11
描述
G-API 是一个异构框架,提供了统一的 API 来使用多个支持的后端编程图像处理流水线。
关键的设计理念是在指定使用哪些内核和设备时保持流水线代码本身的平台中立性,这通过在图编译(配置)时使用额外的参数来实现。这一要求导致了如下架构:
API层 – 这是顶层,实现了G-API的公共接口、其构建块及其语义。当用户使用G-API构建一个流水线时,他直接与此层交互,用户操作的实体(如cv::GMat或cv::GComputation)由这一层提供。
图编译器层 – 这是中间层,它将用户的计算展开成图,然后对其应用一系列转换(例如优化)。这一层建立在ADE框架之上。
后端层 – 这是最底层,列出了多个后端。与上述两层相比,后端与低级平台细节高度耦合,每个后端代表一个特定的平台。后端对来自图编译器的处理过的图进行操作,并针对特定平台或设备最优地执行这个图。
API层
API层是用户在定义和使用流水线(在G-API术语中称为计算)时直接交互的部分。API层定义了一组G-API动态对象,这些对象可以用作图中的输入、输出和中间数据对象:
- cv::GMat
- cv::GScalar
- cv::GArray(模板类)
API层还指定了可以应用于这些数据对象的一系列操作——即所谓的内核。有关G-API默认提供的操作的详细信息,请参见G-API的核心和imgproc命名空间。
G-API不限于这些操作——用户可以使用特殊的宏G_TYPED_KERNEL()轻松定义自己的内核。
API层还负责在创建流水线时对操作参数进行编组和存储。除了上述提到的G-API动态对象外,操作还可以接受任意参数(更多详情见此处),因此API层会在执行时捕获这些值并内部存储。
最后,cv::GComputation和cv::GCompiled是API层中剩余的重要组件。前者将一系列G-API表达式封装到一个对象(图)中,而后者是图编译的产物(详见此章节)。
图编译器层
每个G-API计算在执行之前都会被编译。编译过程通过两种方式触发:
- 隐式编译,当使用cv::GComputation::apply()时触发。在这种情况下,图编译紧接着立即执行。
- 显式编译,当使用cv::GComputation::compile()时触发。这种情况下,将返回一个cv::GCompiled对象,该对象可以像C++函数对象一样被调用。
第一种方式适用于输入数据格式事先未知的情况——例如,当数据来自任意输入文件时。第二种方式推荐用于部署(生产)场景,在这些场景中,输入数据特征通常是预定义的。
图编译过程建立在ADE框架之上。最初,根据API层捕获的表达式生成一个二分图。这个图包含两种类型的节点:数据和操作。图总是以一个或多个数据节点开始和结束,其间是操作节点。每个操作节点都有输入和输出,它们都是数据节点。
在初始图生成之后,它实际上会经过一系列称为“passes”的图变换处理。ADE框架充当编译pass管理引擎,而passes是专门为G-API编写的。
存在不同的passes来检查图的有效性、细化操作和数据的细节、基于亲和性或用户指定的区域化[TBD]将节点组织成集群(“Islands”),等等。后端也能够在编译过程中注入特定于后端的passes,更多关于这方面的信息请参见专门的章节。
图编译的结果是一个编译后的对象,由类cv::GCompiled表示。无论是否有显式或隐式的编译请求(见上文),都会创建一个新的cv::GCompiled对象。实际的图执行是在cv::GCompiled内发生的,并由参与图编译的后端决定。
另请参阅:
- cv::GComputation::apply()
- cv::GComputation::compile()
- cv::GCompiled
后端层
上述图表列出了两个后端:OpenCV和Fluid。OpenCV被称为“参考后端”,它使用传统的OpenCV函数实现G-API操作。这个后端对于在熟悉的开发系统上进行原型设计非常有用。Fluid是一个插件,用于在CPU上高效执行缓存——它实现了不同的执行策略,并使用其特有的内核。Fluid后端允许在CPU上运行时减少内存占用并提高内存局部性。
可能还有更多的后端可用,例如Halide、OpenCL等——G-API提供了一个统一的内部API来开发后端,因此任何爱好者或公司都可以自由地在新平台或加速器上扩展G-API。在OpenCV基础设施方面,每个新的后端都是一个新的独立OpenCV模块,当作为OpenCV的一部分构建时,它扩展了G-API。
图执行
图的执行方式由编译时选择的后端定义。实际上,每个后端在图编译过程的最终阶段生成自己的执行脚本,即创建可执行(编译)对象时。例如,在OpenCV后端中,这个脚本只是需要调用的OpenCV函数的拓扑排序序列;对于Fluid后端,情况类似——是每次迭代处理输入行的代理(Agents)的拓扑排序列表。
图执行通过两种方式触发:
- 通过cv::GComputation::apply(),为给定的输入数据即时编译图;
- 通过cv::GCompiled::operator()(),当图已被预编译时使用。
这两种方法都是多态的,接受变长参数列表,并在运行时执行有效性检查。如果传递的数据对象的数量、形状和格式与预期不符,则会抛出运行时异常。G-API还提供了类型包装器,将这些检查移到编译时——参见cv::GComputationT<>。
G-API图执行声明为无状态的——这意味着已编译的函数对象(cv::GCompiled)就像一个纯粹的C++函数一样工作,并对相同的输入参数集提供相同的结果。
这两种执行方法都接受N+M个参数,其中N是输入数量,M是在其上定义cv::GComputation的输出数量。请注意,虽然在定义中使用了G-API类型(如cv::GMat等),但执行方法接受持有实际数据的传统OpenCV数据类型(如cv::Mat)——参见参数编组中的表格。