AI在 G-API 上移植各向异性图像分割(二)

本文介绍了G-API的图形表示、内存管理和优化,通过Valgrind和Massif分析了基于G-API和OpenCV的各向异性图像分割算法的内存占用。着重展示了Fluid后端在内存效率和执行模型上的改进。
摘要由CSDN通过智能技术生成

了解图形结构

G-API 代表“Graph API”,但您在上面的例子中提到了任何图形吗?这是最初的设计目标之一——G-API 在设计时考虑了表达式,使采用和移植过程更加简单。人们在编写普通代码时通常不会考虑节点边缘,因此 G-API 虽然是 Graph API,但不会强迫其用户这样做。

但是,在定义 cv::GComputation 对象时,仍会隐式构建图形。检查生成的图形的外观,以检查它是否正确生成以及它是否真正代表我们的算法可能很有用。学习图形的结构以查看它是否有任何冗余也很有用。

G-API 允许将生成的图形转储到文件中,然后可以使用流行的开放式图形可视化软件 Graphviz 进行可视化。.dot

为了将我们的图形转储到文件中,请在运行应用程序之前设置为文件名,例如:.dotGRAPH_DUMP_PATH

<span style="background-color:#fbfcfd"><span style="color:#000000">$ GRAPH_DUMP_PATH=segm.dot ./bin/example_tutorial_porting_anisotropic_image_segmentation_gapi
</span></span>

现在,可以使用如下命令可视化此文件:dot

<span style="background-color:#fbfcfd"><span style="color:#000000">$ dot segm.dot -Tpng -o segm.png
</span></span>

或以交互方式查看(有关如何安装这些软件包,请参阅您的发行版/操作系统文档)。xdot

segm.gif

各向异性图像分割图

上图演示了 G-API 内部算法表示的许多有趣方面:

  1. G-API 底层图是二分图:它由操作节点和数据节点组成,数据节点只能连接到操作节点,操作节点只能连接到数据节点,单一类型的节点永远不会直接连接。
  2. 图形是有向的 - 图形中的每条边都有一个方向。
  3. 图形“开始”和“结束”以 Data 类型的节点。
  4. 一个数据节点只能有一个写入器和多个读取器。
  5. 一个 Operation 节点可以有多个输入,但每个输入都必须具有唯一的端口号(在输入之间)。
  6. 一个 Operation 节点可以有多个输出,并且每个输出必须具有唯一的端口号(在输出之间)。

测量内存占用

让我们测量和比较该算法在两个版本中的内存占用:基于 G-API 和基于 OpenCV。目前,G-API 版本也是基于 OpenCV 的,因为它回退到内部的 OpenCV 函数。

在 GNU/Linux 上,可以使用 Valgrind 分析应用程序内存占用情况。在 Debian/Ubuntu 系统上,它可以像这样安装(假设您具有管理员权限):

<span style="background-color:#fbfcfd"><span style="color:#000000">$ sudo apt-get install valgrind massif-visualizer
</span></span>

安装后,我们可以轻松收集两个算法版本的内存配置文件:

<span style="background-color:#fbfcfd"><span style="color:#000000">$ valgrind --tool=massif --massif-out-file=ocv.out ./bin/example_tutorial_anisotropic_image_segmentation
==6101== Massif, a heap profiler
==6101== Copyright (C) 2003-2015, and GNU GPL'd, by Nicholas Nethercote
==6101== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==6101== Command: ./bin/example_tutorial_anisotropic_image_segmentation
==6101==
==6101==
$ valgrind --tool=massif --massif-out-file=gapi.out ./bin/example_tutorial_porting_anisotropic_image_segmentation_gapi
==6117== Massif, a heap profiler
==6117== Copyright (C) 2003-2015, and GNU GPL'd, by Nicholas Nethercote
==6117== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==6117== Command: ./bin/example_tutorial_porting_anisotropic_image_segmentation_gapi
==6117==
==6117==
</span></span>

完成后,我们可以使用Massif Visualizer(安装在上述步骤中)检查收集的配置文件。

以下是该算法的原始 OpenCV 版本的可视化内存配置文件:

massif_export_ocv.png

内存配置文件:原始各向异性图像分割示例

我们看到内存是在应用程序执行时分配的,在 calcGST() 函数中达到峰值;然后,当 calcGST() 完成其执行并释放所有临时缓冲区时,占用空间会下降。Massif 报告的峰值内存消耗为 7.6 MiB。

现在让我们看一下 G-API 版本的配置文件:

massif_export_gapi.png

内存配置文件:各向异性图像分割样本的 G-API 端口

创建 G-API 计算并开始执行后,G-API 会立即分配所有必需的内存,然后内存配置文件保持平坦,直到程序终止。Massif 报告的峰值内存消耗为 11.4 MiB。

在这一点上,读者可能会问一个正确的问题——G-API 有那么糟糕吗?使用它的原因是什么?

希望不是。我们之所以在这里看到内存消耗增加,是因为使用默认的朴素的基于 OpenCV 的后端来执行此图。该后端主要用于在卸载/进一步优化之前进行快速原型设计和调试算法。

该后端尚未使用任何复杂的内存管理策略,因为它目前不是重点。在下一章中,我们将了解 Fluid 后端,并了解相同的 G-API 代码如何在完全不同的模型中运行(并且占用空间缩小到数千字节)。

后端和内核

本章介绍了如何以特殊方式执行 G-API 计算,例如卸载到另一台设备,或使用特殊智能进行调度。G-API 旨在使其图形具有可移植性——这意味着一旦以 G-API 术语定义了图形,如果我们想在 CPU 或 GPU 或同时在两个设备上运行它,则不需要对其进行任何更改。G-API 高级概述和 G-API 内核 API 更深入地揭示了使之成为可能的技术细节。在本章中,我们将利用 G-API Fluid 后端来使我们的图形缓存在 CPU 上高效。

G-API 将后端定义为知道如何运行内核的较低级别的实体。后端可能具有(事实上确实具有)不同的内核 API,这些 API 用于为该后端编程和集成内核。在此上下文中,内核操作的实现,在顶级 API 级别上定义(参见 G_TYPED_KERNEL() 宏)。

后端是一个知道设备和平台细节的东西,并在执行其内核时牢记这些细节。例如,可能有 Halide 后端,它允许用 Halide 语言编写(实现)G-API 操作,然后为 G-API 图的部分生成功能性 Halide 代码,这些代码在那里映射得很好。

使用 Fluid 后端运行图形

OpenCV 4.0 捆绑了两个 G-API 后端——我们刚刚使用的默认“OpenCV”和一个特殊的“Fluid”后端。

Fluid 后端重新组织执行以节省内存并实现近乎完美的缓存局部性,实现所谓的“流式”执行模型。

为了开始使用 Fluid 内核,我们首先需要包含适当的头文件(默认情况下不包括这些文件):

#include “opencv2/gapi/fluid/core.hpp // Fluid Core 内核库
#include “opencv2/gapi/fluid/imgproc.hpp // Fluid ImgProc 内核库

一旦包含了这些头文件,我们就可以形成一个新的内核包,并将其指定给 G-API:

准备内核包并运行图形
cv::GKernelPackage fluid_kernels = cv::gapi::combine // 定义自定义内核包:
(cv::gapi::core::fluid::kernels(), // ...带有流体核心内核
cv::gapi::imgproc::fluid::kernels());// ...和 Fluid ImgProc 内核

在 G-API 中,内核(或操作实现)是对象。内核被组织成集合或内核包,由类 cv::GKernelPackage 表示。内核包的主要目的是捕获我们想要在图中使用的内核,并将其作为图编译选项传递:

segm.apply(cv::gin(imgIn), // 输入数据向量
cv::gout(imgOut, imgOutCoherency, imgOutOrientation), // 输出数据向量
cv::compile_args(fluid_kernels));要使用的内核包

传统的OpenCV在逻辑上被划分为多个模块,每个模块都提供一组功能。在 G-API 中,还有一些“模块”,它们表示为特定后端提供的内核包。在此示例中,我们将 Fluid 内核包传递给 G-API,以便在图中使用适当的 Fluid 函数。

内核包是可组合的——在上面的例子中,我们将“Core”和“ImgProc”Fluid 内核包组合成一个。请参阅 cv::gapi::combine 上的文档参考。

如果选项中未指定内核包,则 G-API 使用默认包,该包由默认的 OpenCV 实现组成,因此 G-API 图默认通过 OpenCV 函数执行。OpenCV 后端提供比任何其他后端更广泛的功能覆盖范围。如果指定了内核包,如本例所示,则该软件包将与默认软件包结合使用。这意味着在发生冲突时,用户指定的实现将替换默认实现。

故障排除和自定义

经过上述修改后,(在 OpenCV 4.0 中)应用程序应该崩溃并显示如下消息:

$ ./bin/example_tutorial_porting_anisotropic_image_segmentation_gapi_fluid
在抛出“std::logic_error”实例后调用终止
what(): .../modules/gapi/src/backends/fluid/gfluidimgproc.cpp:436:函数运行中的断言 kernelSize.width == 3 && kernelSize.height == 3 失败
已中止(核心转储)

Fluid 后端在 OpenCV 4.0 中有许多限制(有关最新状态,请参阅此 wiki 页面)。具体而言,此示例中使用的 Box 筛选器仅支持静态 3x3 内核大小。

在此示例中,我们可以通过避免使用 Box 过滤器内核的 Fluid 版本来避免 G-API,从而轻松克服这个问题。这可以通过从我们刚刚创建的内核包中删除适当的内核来完成:

fluid_kernels。删除<cv::gapi::imgproc::GBoxFilter>();// 移除不合适的流体箱过滤器,
G-API 将在那里回退到 OpenCV。

现在,此内核包没有任何 Box 筛选器内核接口的实现(指定为模板参数)。如上所述,G-API 现在将回退到 OpenCV 来运行此内核。具有此更改的结果代码现在如下所示:

准备内核包并运行图形
cv::GKernelPackage fluid_kernels = cv::gapi::combine // 定义自定义内核包:
(cv::gapi::core::fluid::kernels(), // ...带有流体核心内核
cv::gapi::imgproc::fluid::kernels());// ...和 Fluid ImgProc 内核
fluid_kernels。删除<cv::gapi::imgproc::GBoxFilter>();// 移除不合适的流体箱过滤器,
G-API 将在那里回退到 OpenCV。
segm.apply(cv::gin(imgIn), // 输入数据向量
cv::gout(imgOut, imgOutCoherency, imgOutOrientation), // 输出数据向量
cv::compile_args(fluid_kernels));要使用的内核包

让我们检查一下切换到 Fluid 后端后此示例的内存配置文件。现在它看起来像这样:

内存配置文件:各向异性图像分割样本的 G-API/流体端口

现在该工具报告了 4.7MiB——我们只是在代码中更改了几行,而没有修改图形本身!它比之前的 G-API 结果提高了 ~2.4 倍,比原始 OpenCV 版本提高了 ~1.6 倍。

我们还要看看图形的内部表示现在是什么样子的。将图形转储到中将导致如下可视化效果:.dot

segm_fluid.gif

使用 OpenCV 和 Fluid 内核的各向异性图像分割图

此图在结构上与其以前的版本(在操作和数据对象方面)没有区别,尽管更改的布局(在转储的左侧)很容易引起注意。

可视化反映了 G-API 如何处理混合图(也称为异构图)。此图中的大多数操作都是通过 Fluid 后端实现的,但 Box 过滤器是由 OpenCV 后端执行的。可以很容易地看到图形是分区的(带有矩形)。G-API 根据其亲和力对连接的操作进行分组,形成子图(或 G-API 术语中的孤岛),我们的顶级图成为多个较小子图的组合。每个后端都决定了其子图(孤岛)的执行方式,因此 Fluid 后端会尽可能地优化内存,并且 OpenCV Box 过滤器访问的 6 个中间缓冲区被完全分配,无法优化。

结论

本教程演示了 G-API 是什么及其关键设计概念是什么,如何将算法移植到 G-API,以及如何在此之后利用图模型的优势。

在 OpenCV 4.0 中,G-API 仍处于起步阶段——它更像是所有未来工作的基础,尽管现在已经准备好使用。

   在线教程

有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

请添加图片描述

人工智能书籍

第一阶段:零基础入门(3-6个月)

新手应首先通过少而精的学习,看到全景图,建立大局观。 通过完成小实验,建立信心,才能避免“从入门到放弃”的尴尬。因此,第一阶段只推荐4本最必要的书(而且这些书到了第二、三阶段也能继续用),入门以后,在后续学习中再“哪里不会补哪里”即可。

第二阶段:基础进阶(3-6个月)

熟读《机器学习算法的数学解析与Python实现》并动手实践后,你已经对机器学习有了基本的了解,不再是小白了。这时可以开始触类旁通,学习热门技术,加强实践水平。在深入学习的同时,也可以探索自己感兴趣的方向,为求职面试打好基础。

第三阶段:工作应用

这一阶段你已经不再需要引导,只需要一些推荐书目。如果你从入门时就确认了未来的工作方向,可以在第二阶段就提前阅读相关入门书籍(对应“商业落地五大方向”中的前两本),然后再“哪里不会补哪里”。

 有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值