什么是 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 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(该项目使用 CMakeconan 进行构建)。

例如,我们有以下代码,它使用经典的 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 算子,然后将应用算子的结果保存到另一个图像文件。因此,如果我们有以下图像文件作为输入

A color picture of a steam engine

那么结果可能如下所示

The Sobel operator applied to that image

此类代码通常用于 边缘检测,这是一项常用的图像处理任务。

好的,如何将您已有的代码迁移到 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 存储库,以便编译和运行本文中的示例。