OpenCV 4.0.0 新的图 API (G-API)
什么是 OpenCV?
OpenCV 代表 **开放源代码计算机视觉库**。
顾名思义,该库拥有许多用于计算机视觉的各种功能,例如
OpenCV 是跨平台的,支持主要的桌面平台(Windows、Linux、MacOS),以及移动平台(iOS、Android)。
该项目是开源软件,根据 BSD 许可证授权。
此外,它还具有 Java、Python、Haskel、MATLAB 等的绑定。
OpenCV 非常注重性能,其代码针对各种微架构进行了优化(x86 上的 SSE 和 AVX,ARM 上的 NEON,PowerPC 上的 VSX 等)。
该库适用于异构计算,支持 CUDA 和 OpenCL。
总而言之,OpenCV 库非常庞大,并且拥有大量用于计算机视觉的功能。此外,如果基础库不够用,它还提供了一组称为 OpenCV Contrib 的附加模块。
OpenCV 4.0.0 版本有哪些新功能?
OpenCV 于 2018 年 11 月发布了最终版本 4.0.0。
它有很多新功能(请参阅完整的 更改日志),例如
此外,OpenCV 4.x 正在消除一些技术债务,例如
- OpenCV 现在是 C++11 库,并需要 符合 C++11 标准的编译器
- 删除了许多 OpenCV 1.x C API
- std::string 和 std::shared_ptr 现在用于代替手工编写的 cv::String 和 cv::Ptr
像往常一样,还有一些错误修复和性能改进。
OpenCV G-API
让我们更深入地了解名为 G-API(代表图 API)的新 OpenCV 功能。
以前,使用经典的 OpenCV 2.x API,编程模型非常传统——您调用 OpenCV 函数,它们执行一些计算并返回结果。对于大多数程序员来说,此模型应该看起来很熟悉和自然,因为它类似于常规的 文件系统 API。OpenCV 4.x 引入了一种非常不同的编程模型,您首先定义要执行的操作流水线,然后将此流水线应用于一些实际数据。换句话说,每当您调用 OpenCV G-API 函数时,执行都会被延迟(惰性求值),并且会返回延迟操作的结果而不是实际的计算结果。这个概念可能听起来非常类似于 Ranges TS(或 Range V3,或 Boost Range)。
这是一个特别重要的功能,因为如果您不需要获取中间结果,则可以轻松地将整个流水线卸载到 GPU,因此系统与视频内存之间不会有中间复制——只有初始输入和最终输出需要加载到/从 GPU 中。
实践 G-API
此博文中的完整代码示例可在 GitHub 上找到:opencv4-demo(该项目使用 CMake 和 conan 进行构建)。
例如,我们有以下代码,它使用经典的 OpenCV 2.x API
#include <cstdlib>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
int main(int argc, char * argv[])
{
cv::Mat imgIn = cv::imread("in.png"), imgBlur, imgGray, imgOut, sobelX, sobelY, gradX, gradY;
cv::GaussianBlur(imgIn, imgBlur, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);
cv::cvtColor(imgBlur, imgGray, cv::COLOR_BGR2GRAY);
cv::Sobel(imgGray, sobelX, CV_16S, 1, 0, 3);
cv::Sobel(imgGray, sobelY, CV_16S, 0, 1, 3);
cv::convertScaleAbs(sobelX, gradX);
cv::convertScaleAbs(sobelY, gradY);
cv::addWeighted(sobelX, 0.5, sobelY, 0.5, 0, imgOut);
cv::imwrite("out.png", imgOut);
return EXIT_SUCCESS;
}
该示例获取输入图像文件,对其进行模糊处理,转换为灰度,最后应用 Sobel 算子,然后将应用算子的结果保存到另一个图像文件。因此,如果我们有以下图像文件作为输入
那么结果可能如下所示
此类代码通常用于 边缘检测,这是一项常用的图像处理任务。
好的,如何将您已有的代码迁移到 G-API?首先,您需要包含其他 OpenCV 头文件
#include <opencv2/gapi.hpp>
#include <opencv2/gapi/core.hpp>
#include <opencv2/gapi/imgproc.hpp>
其次,cv:: 命名空间中的函数将被新 cv::gapi:: 命名空间中的相应函数替换,例如 cv::Sobel 变成 cv::gapi::Sobel,依此类推。这些函数没有输出参数,而是返回 cv::GMat 类型的值(众所周知的 cv::Mat 类型的类似物)。输入参数也是如此——它们也接受 cv::GMat。某些函数可能在 cv::gapi:: 命名空间中不存在,例如 cv::convertScaleAbs,但自己实现它非常简单
static cv::GMat convertScaleAbs(const cv::GMat & src, double alpha = 1.0, double beta = 0.0)
{
auto result = cv::gapi::absDiffC(cv::gapi::addC(cv::gapi::mulC(src, alpha), beta), 0.0);
return cv::gapi::convertTo(result, CV_8UC1);
}
使用上述实现,重写图像处理代码非常简单
auto imgBlur = cv::gapi::gaussianBlur(gIn, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);
auto imgGray = cv::gapi::convertTo(imgBlur, CV_32F);
auto sobelX = cv::gapi::Sobel(imgGray, CV_16S, 1, 0, 3);
auto sobelY = cv::gapi::Sobel(imgGray, CV_16S, 0, 1, 3);
auto gradX = convertScaleAbs(sobelX);
auto gradY = convertScaleAbs(sobelY);
auto gOut = cv::gapi::addWeighted(sobelX, 0.5, sobelY, 0.5, 0);
建立流水线后,是时候构建 cv::GComputation 对象了
cv::GComputation computation(cv::GIn(gIn), cv::GOut(gOut));
最后,计算可以应用于实际数据
computation.apply(cv::gin(imgIn), cv::gout(imgOut));
此时,实际的数据处理开始进行。
使用 G-API 的完整示例
#include <cstdlib>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/gapi.hpp>
#include <opencv2/gapi/core.hpp>
#include <opencv2/gapi/imgproc.hpp>
static cv::GMat convertScaleAbs(const cv::GMat & src, double alpha = 1.0, double beta = 0.0)
{
auto result = cv::gapi::absDiffC(cv::gapi::addC(cv::gapi::mulC(src, alpha), beta), 0.0);
return cv::gapi::convertTo(result, CV_8UC1);
}
int main(int argc, char * argv[])
{
cv::GMat gIn;
auto imgBlur = cv::gapi::gaussianBlur(gIn, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);
auto imgGray = cv::gapi::convertTo(imgBlur, CV_32F);
auto sobelX = cv::gapi::Sobel(imgGray, CV_16S, 1, 0, 3);
auto sobelY = cv::gapi::Sobel(imgGray, CV_16S, 0, 1, 3);
auto gradX = convertScaleAbs(sobelX);
auto gradY = convertScaleAbs(sobelY);
auto gOut = cv::gapi::addWeighted(sobelX, 0.5, sobelY, 0.5, 0);
cv::GComputation computation(cv::GIn(gIn), cv::GOut(gOut));
cv::Mat imgIn = cv::imread("in.png"), imgOut;
computation.apply(cv::gin(imgIn), cv::gout(imgOut));
cv::imwrite("out.png", imgOut);
return EXIT_SUCCESS;
}
结论
OpenCV 4.0 版本添加了非常基础的更改,这些更改彻底改变了编写程序的方式,使异构计算的支持更加简单。请随时尝试使用新的 OpenCV 功能,例如 G-API,查看 opencv4-demo 存储库,以便编译和运行本文中的示例。