OpenCV Graph API (G-API)
Introduction:
OpenCV的Graph API(或称G-API)是一个让常规图像处理变得更快(fast)和轻量(portable)的新模块。这两种方式的是通过引入一个新的基于图像的执行模型实现的(graph-based model of execution)。
G-API是一个特殊的OpenCV模块。和其它大多数的主要模块(OpenCV本体模块)不同的是,此模块扮演的角色是一个框架,而不是某种特别的CV算法。G-API提供了定义CV操作的方式,用以构图(以一种表达的方式),最终通过特定的后端(backend)来生效和执行操作。
注意:G-API是一个新模块,目前正处在活跃的开发过程中,它的API现阶段是不稳定的,在未来或许会有一些微小但破坏兼容性的变化。
Contents
G-API的文档被编排为如下篇章:
开发G-API背后的动机和其的目标
G-API构架的总体概览和其主要的内部构件
学习如何引入G-API中新的操作并使其在不同的后端下生效
- Implementation details
- API参考:函数和类
- G-API Core functionlity
G-API的核心操作,算术、布尔和其他矩阵运算
图像处理的函数:色彩空间转换,滤波器等等
API Example
下面举一个非常简单的G-API应用的例子:
g-api.cpp
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/gapi.hpp>
#include <opencv2/gapi/core.hpp>
#include <opencv2/gapi/imgproc.hpp>
int main(int argc, char *argv[])
{
cv::VideoCapture cap;
if (argc > 1) cap.open(argv[1]);
else cap.open(0);
CV_Assert(cap.isOpened());
cv::GMat in;
cv::GMat vga = cv::gapi::resize(in, cv::Size(), 0.5, 0.5);
cv::GMat gray = cv::gapi::BGR2Gray(vga);
cv::GMat blurred = cv::gapi::blur(gray, cv::Size(5,5));
cv::GMat edges = cv::gapi::Canny(blurred, 32, 128, 3);
cv::GMat b,g,r;
std::tie(b,g,r) = cv::gapi::split3(vga);
cv::GMat out = cv::gapi::merge3(b, g | edges, r);
cv::GComputation ac(in, out);
cv::Mat input_frame;
cv::Mat output_frame;
CV_Assert(cap.read(input_frame));
do
{
ac.apply(input_frame, output_frame);
cv::imshow("output", output_frame);
} while (cap.read(input_frame) && cv::waitKey(30) < 0);
return 0;
}
注意 必须编译opencv-contrib
,才能正常编译以上代码。且必须为OpenCV 4.x
代码中调用了摄像头,没有摄像头可以用视频代替,cap.open("filename.xxx")
或者在运行程序时在后面加入视频的名称(如./g-api ./filename.xxx
)。
编译命令:
g++ g-api.cpp -o g-api `pkg-config --flags --libs opencv4`
由于G-API是OpenCV的一个独立模块,所以其头文件必须单独引入。
也就是说 直接引入#include <opencv2/opencv.hpp>
头文件不行,opencv.hpp
里面没有包含G-API的头文件。main()
函数里面首先创建和初始化了标准OpenCV视频类,可以从视频流或者摄像头读取每帧图像。
G-API的管线(pipeline:pipeline表示在外接程序与其宿主之间交换数据的管线段的线性通信模型。从宿主端开始,管线具有以下一系列管线段:宿主、外接程序的宿主视图、宿主端适配器、协定、外接程序端适配器、外接程序视图和外接程序。)随之被创建,事实上它是通过调用cv::GMat
来完成一系列的G-API数据操作。关于这块代码比较重要的一个方面是:它只是声明了要去做什么操作,而并不是直接执行什么操作。到此时也还没有进行任何的图像处理。G-API只追踪管线的操作以及其实如何连接的。G-API的数据对象(在此处是cv::GMat
)是用以连接各种操作的(PS,这点其实就等同于流水线作业)。cv::GMat in
则是一个为空的GMat
信号,用于告知计算的开始。
在写完G-API这块代码之后,图像(frame帧)便被捕捉到一个调用图(call graph)中,并同时实例化cv::GComputation
这个对象。此对象把输入/输出(input/output)数据当做参数(在本例中依次是in
和out
这两个cv::GMat对象),并基于in和out的中的数据流来重建调用图。
在某种程度上,cv::GComputation
是一个缩减对象(thin object,我也不知道怎么翻译好),它负责捕捉何种图像处理操作来组成计算。但他也可以用于执行计算——在接下来的循环处理中,每次捕捉到的帧(cv::Mat frame
)就被传入cv::GComputation::apply()
中。
cv::GComputation::apply()
是一个多态方法(polimorphic method),它可以接受数量可变(variadic number)的参数。由于定义好了一个输入和一个输出,cv::GComputation::apply()
的一个特殊重载函数就被用于传入输入参数,得到输出参数。
总而言之,cv::GComputation::apply()
就根据给定输入参数来编译捕捉图(captured graph)并立即执行编译捕捉图。
针对这个例子中一些比较重要的概念可以列出如下大纲:
- 图像(Graph)的声明和执行时分开的两个步骤
- 图像是根据一系列的G-API表达式来隐式构建的
- G-API支持一些类似函数的调用——比如
cv::gapi::resize()
以及一些重载操作符,比如operator|()
一个用于按位或的操作符。 - G-API的语法力求简洁,每一项调用操作即在一个图(Graph)中产生一个新的结果,从而形成了有向无环图(Directed Acyclic Graph,DAG)。
- 图的声明并没有绑定任何的数据——真实数据(
cv::Mat
)是在图(Graph)已经定义之后才生成的。
PS:这里的图(Graph)实际上是不能看到的,OpenCV调用cv::imshow()
函数现阶段是不支持cv::GMat
格式的,只有转化为cv::Mat
格式才能正常显示。
如果对G-API的特性和理念有兴趣,还可以上OpenCV看更多的例子和教程。教程和例子
以下是个人实验的部分
(没有拿去和普通OpenCV操作作对比。)
在g-api.cpp
中我加入了计时部分。编译时加入-DDEBUG
显示时间
头文件额外引入#include <iomanip>
,不然setprecision()
可能不能用。
do
{
double start = (double)cv::getTickCount();
ac.apply(input_frame, output_frame);
cv::imshow("output", output_frame);
double time_consume = ((double)cv::getTickCount() - start) / cv::getTickFrequency();
#ifdef DEBUG
std::cout << "time_comsume = "<< time_consume * 100 //以s做单位没意义,改为ms
<< std::setprecision(2) << "ms"<<std::endl;
#endif
} while (cap.read(input_frame) && cv::waitKey(30) < 0);
以下是测试的结果:
CPU占用也如图所示,大概在30%左右,消耗的时间在调试控制台
中显示,平均时间大概在0.3ms左右。