OpenCV 简介

OpenCV(Open Source Computer Vision Library,开源计算机视觉库:http://opencv.org)是一个开放源代码库,其中包含数百种计算机视觉算法。本文档介绍所谓的 OpenCV 2.x API,与基于 C 的 OpenCV 1.x API 相比,该 API 本质上是一套 C++  API(自 OpenCV 2.4 发行以来,不推荐再使用 C API,并且不使用 “C” 编译器进行测试)。

OpenCV 具有模块化的结构,这意味着该程序库包含多个共享库或静态库。它提供了以下模块:

  • Core functionalitycore)- 核心模块,定义基本数据结构的紧凑模块,包括密集的多维数组类 Mat 和所有其他模块使用的基本函数。
  • Image Processingimgproc)- 图像处理模块,包括线性和非线性图像滤波,图像的几何变换(调整大小,仿射和透视变换,基于通用表的重映射),色彩空间转换,直方图等。
  • Video Analysisvideo)- 视频分析模块,包括运动估计,背景相减和对象跟踪算法。
  • Camera Calibration and 3D Reconstructioncalib3d)- 相机校准和 3D 重建模块,包括基本的多视图几何算法,单镜头和立体相机校准,对象姿态估计,立体匹配算法以及 3D 元素重建。
  • 2D Features Frameworkfeatures2d)- 2D 特征模块,包括显著特征检测器,描述符和描述符匹配器。
  • Object Detectionobjdetect)- 对象检测模块,检测对象和预定义类的实例(例如:人脸,眼睛,杯子,人,汽车等)。
  • High-level GUIhighgui)- GUI 模块,提供一些易于使用的接口,可用于实现简单的 UI 功能。
  • Video I/Ovideoio)- 视频 I/O 模块,提供一些易于使用的视频捕获和视频编解码器接口。
  • ... 其他一些辅助模块,例如 FLANN 和 Google 测试包装器,Python 绑定等。

本文档的其他章节介绍了每个模块的功能。但首先请确保熟悉该库中大量使用的通用 API 概念。

API 相关概念

cv 命名空间

所有的 OpenCV 类和函数都放置在 cv 名称空间中。因此,要从你的代码访问这些函数,请使用 cv:: 说明符或使用 using namespace cv; 来指示:

#include "opencv2/core.hpp"
...
cv::Mat H = cv::findHomography(points1, points2, cv::RANSAC, 5);
...

或者:

#include "opencv2/core.hpp"
using namespace cv;
...
Mat H = findHomography(points1, points2, RANSAC, 5 );
...

当前或将来的某些 OpenCV 外部名称可能与 STL 或其他库冲突。在这种情况下,请使用显式命名空间说明符来解决名称冲突:

Mat a(100, 100, CV_32F);
randu(a, Scalar::all(1), Scalar::all(std::rand()));
cv::log(a, a);
a /= std::log(2.);

自动内存管理

OpenCV 自动处理所有内存。

首先,被函数或方法使用的 std::vector,cv::Mat 以及其他数据结构具有析构函数,这些析构函数可以在需要时释放潜在的内存缓冲区。 这意味着像 Mat 这样的类的析构函数并不总是释放缓冲区,它还考虑了可能的数据共享。析构函数的调用会使与矩阵数据缓冲区相关联的引用计数器递减。当且仅当引用计数器达到零时,即没有其他结构引用该缓冲区时,才释放缓冲区。类似地,当复制 Mat 实例时,不会真正复制任何实际数据,相反,引用计数器会递增,以记住相同的数据还有另外一个所有者。当然,OpenCV 还提供了 Mat::clone 方法来创建矩阵数据的完整副本。请参阅以下示例:

// 创建一个 8Mb 的大型矩阵
Mat A(1000, 1000, CV_64F);
// 为同一矩阵创建另一个标头;
// 无论矩阵大小如何,这都是一个即时操作。
Mat B = A;
// 为 A 的第 3 行数据创建另一个标头;也没有数据被复制
Mat C = B.row(3);
// 现在创建矩阵的单独副本
Mat D = B.clone();
// 将 B 的第 5 行复制到 C, 也就是说,复制 A 的第 5 行到 A 的第 3 行。
B.row(5).copyTo(C);
// 现在让 A 和 D 共享数据;之后,B 和 C 仍引用 A 的修改版本。
A = D;
// 现在将 B 设为一个空矩阵(不引用任何内存缓冲区),
// 但是 C 仍将引用 A 的修改版本,尽管 C 只是原始 A 的一行
B.release();
// 最后,制作 C 的完整副本。
// 矩阵将被释放,因为任何人都没有引用它
C = C.clone();

你会看到 Mat 和其他基本类型使用起来非常简单。 但是在没有考虑自动内存管理的情况下创建的高级类甚至用户数据类型又如何呢?对于他们,OpenCV 提供了 cv::Ptr 模板类,该类类似于 C++11 中的 std::shared_ptr。 因此,不要使用普通的指针:

T* ptr = new T(...);

你可以使用:

Ptr<T> ptr(new T(...));

或者:

Ptr<T> ptr = makePtr<T>(...);

Ptr<T> 封装了指向 T 实例的指针和与该指针关联的引用计数器。有关详细信息,请参见 cv::Ptr

输出数据的内存自动分配

OpenCV 在大多数情况下会自动释放分配的内存,并自动为函数的输出参数分配内存。 因此,如果一个函数具有一个或多个输入数组(cv::Mat 实例)和一些输出数组,则将自动分配或重新分配输出数组所需的内存。 输出数组的大小和类型由输入数组的大小和类型确定。 如果需要,函数可以使用额外的参数来帮助确定输出数组的属性。

示例:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

using namespace cv;

int main()
{
    VideoCapture cap(0);
    if (!cap.isOpened()) 
        return -1;

    Mat frame, edges;
    namedWindow("edges", WINDOW_AUTOSIZE);
    for (;;) {
        cap >> frame;
        cvtColor(frame, edges, COLOR_BGR2GRAY);
        GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);
        Canny(edges, edges, 0, 30, 3);
        imshow("edges", edges);
        if (waitKey(30) >= 0) 
            break;
    }
    return 0;
}

运算符 >> 会自动为数组 frame 分配内存,因为视频捕获模块是知道视频帧的分辨率和位深度的。 数组 edges 的内存由 cvtColor 函数自动分配。 它具有与输入数组相同的大小和位深度,通道数为 1,因为传递了颜色转换方式 cv::COLOR_BGR2GRAY,这意味着从彩色转换到灰度。 注意,在循环主体的第一次执行期间,frame 和 edges 仅分配一次,因为所有接下来的视频帧都具有相同的分辨率。 如果您以某种方式更改了视频分辨率,则会自动重分配。

实现该技术的关键组件是 Mat::create 方法。它依据数组所需的大小和类型来实现内存的分配。 如果数组已经具有指定的大小和类型,则该方法不执行任何操作。 否则,它将释放先前分配的数据(如果有的话)(此部分涉及递减引用计数器并将其与零进行比较),然后分配所需大小的新缓冲区。 大多数函数为每个输出数组调用 Mat::create 方法,因此实现了输出数据的内存自动分配。如果仅存储结果的最低 8(16)位的话,则会导致视觉伪影,并可能影响进一步的图像分析。

此方案中存在明显的例外:cv::mixChannelscv::RNG::fill 其它一些函数和方法。它们无法为输出数组进行自动内存分配,因此你必须提前执行此操作。

饱和算法

作为计算机视觉库,OpenCV 需要处理大量的图像像素数据,这些图像像素通常以紧凑的,每通道 8 位或 16 位的形式编码,因此其取值范围是有限的。此外,对图像的某些操作(例如颜色空间转换,亮度/对比度调整,锐化,复杂插值运算(双三次,Lanczos))可能会产生超出可用范围的值。为了解决这个问题,使用了所谓的饱和算法(saturation arithmetics)。例如,要将运算结果 r 存储到 8 位图像中,你可以从 0..255 范围内找到最接近的值:

                                                                              I(x,y)= \min ( \max (\textrm{round}(r), 0), 255)

类似的规则也适用于 8 位有符号,16 位有符号和无符号类型。在 C++ 代码中,它是使用类似于标准 C++ 转换操作的 cv::saturate_cast<> 函数完成的。如下代码提供了以上公式的实现:

I.at<uchar>(y, x) = saturate_cast<uchar>(r);

其中 cv::uchar 是 OpenCV 8 位无符号整数类型。在优化的 SIMD 代码中,它使用了 SSE2 指令,例如 paddusb,packuswb 等。它们有助于实现与 C++ 代码完全相同的行为。

注意:当结果为 32 位整数时,不应使用饱和算法。

 固定的像素类型和对模板的限制使用

模板是 C++ 的一项重要特性,可实现非常强大、高效且安全的数据结构和算法。但是,广泛使用模板可能会大大增加编译时间和代码大小。此外,当专门使用模板时,很难将接口与实现分开。这对于基本算法可能很好,但对于单个算法可能跨越成千上万行代码的计算机视觉库来说却不是很好。因此,为了简化针对完全不具有模板或模板功能有限的其他语言(如 Python,Java,Matlab)的绑定开发,当前的OpenCV 实现了基于多态和基于模板的运行时调度。在运行时调度太慢的地方(例如像素访问运算符),不可能(通用 cv::Ptr<> 的实现)或非常不方便的地方(cv::saturate_cast<>()),当前实现引入了小型模板类 、方法和函数。除此之外,当前 OpenCV 版本中的其他任何地方,模板的使用都受到限制。

因此,库可以使用的固定的有限的原始数据类型集。也就是说,数组元素应具有以下类型之一:

  • 8-bit unsigned integer (uchar)
  • 8-bit signed integer (schar)
  • 16-bit unsigned integer (ushort)
  • 16-bit signed integer (short)
  • 32-bit signed integer (int)
  • 32-bit floating-point number (float)
  • 64-bit floating-point number (double)
  • 由几个元素组成的元组,其中所有元素都具有相同的类型(上述之一)。不同于元素为标量值的单通道数组,由此类元组组成的数组被称为多通道数组。通道的最大可能数目由 CV_CN_MAX 常量定义,该常量当前设置为 512。

对于这些基本类型,可以使用以下枚举:

enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 };

多通道(n 通道)类型可以使用以下选项指定:

注意:CV_32FC1 == CV_32F, CV_32FC2 == CV_32FC(2) == CV_MAKETYPE(CV_32F, 2)CV_MAKETYPE(depth, n) == ((depth&7) + ((n-1)<<3). 这意味着常量类型是由深度(最低的3位)和通道数减去 1(从下一个 log2(CV_CN_MAX) 位开始)形成的。

例如:

Mat mtx(3, 3, CV_32F); // 创建一个 3x3 浮点型矩阵
Mat cmtx(10, 1, CV_64FC2); // 创建一个 10x1 2 通道浮点型矩阵(10 元素的复数向量)
                          
Mat img(Size(1920, 1080), CV_8UC3); // 创建一幅大小为 1920x1080、通道数为 3 的图像
Mat grayscale(image.size(), CV_MAKETYPE(image.depth(), 1)); // 创建一个通道数为 1、
                                                            // 大小和通道数据类型与 
                                                            // img 相同的图像

具有更复杂元素的数组不能使用 OpenCV 构造或处理。此外,每个函数或方法只能处理所有可能的数组类型的子集。通常,算法越复杂,支持的格式子集越小。请参见以下此类限制的典型示例:

  • 人脸检测算法仅适用于8位灰度或彩色图像。
  • 线性代数函数和大多数机器学习算法仅适用于浮点数组。
  • 基本函数,如 cv::add,支持所有类型。
  • 色彩空间转换功能支持 8 位无符号整型,16 位无符号整型以及 32 位浮点类型。

每个函数支持的类型的子集都是根据实际需要定义的,将来可以根据用户需求进行扩展。

输入数组和输出数组

许多 OpenCV 函数处理稠密的二维或多维数组。通常,此类函数将 cv::Mat 作为参数,但在某些情况下,使用 std::vector<>(例如用于点集)或 cv::Matx<>(用于 3x3 单应矩阵等)会更方便。为了避免 API 中的许多重复项,引入了特殊的“代理”类。基本的“代理”类是 cv::InputArray。它用于在函数输入上传递只读数组。从 InputArray 类派生的 cv::OutputArray 用于指定函数的输出数组。通常,您无需关心这些中间类型(也不应显式声明这些类型的变量),它们都将自动运行。您可以假设可以始终使用 Matstd::vector<>cv::Matx<>cv::Vec<>cv::Scalar 代替 InputArray / OutputArray。当一个函数具有可选的输入或输出数组,而您又没有或不需要一个数组时,请传递 cv::noArray()

错误处理

OpenCV 使用异常来表示严重错误。 当输入数据具有正确的格式并属于指定的值范围时,但是由于某种原因该算法无法成功(例如,优化算法未收敛),它将返回一个特殊的错误代码(通常只是一个布尔变量) 。

异常可以是 cv::Exception 类或其派生类的实例。反过来, cv::Exception 是 std::exception 的派生。因此,可以使用其他 C++ 标准库组件在代码中优雅地对其进行处理。

通常使用 CV_Error(errcode, description) 宏或其类 printf 的 CV_Error_(errcode, (printf-spec, printf-args)) 变体抛出异常,或使用 CV_Assert(condition) 宏来检查条件并在不满足时抛出异常。 对于性能至关重要的代码,有 CV_DbgAssert(condition) 仅保留在 Debug 配置中。 由于具有自动内存管理功能,因此在突然发生错误时,所有中间缓冲区都将自动释放。 您只需要添加一条 try 语句即可捕获异常:

try {
    ... // 调用 OpenCV 
} catch (const cv::Exception& e) {
    const char* err_msg = e.what();
    std::cout << "exception caught: " << err_msg << std::endl;
}

 多线程和可重入性

当前的 OpenCV 实现完全可以重复的使用。也就是说,可以从不同的线程调用相同的函数或不同类实例的相同方法。此外,相同的 Mat 可以在不同的线程中使用,因为引用计数操作使用特定于体系结构的原子指令。

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值