OpenCV 4.10.0
开源计算机视觉库
|
OpenCV(开源计算机视觉库:https://opencv.ac.cn)是一个开源库,包含数百种计算机视觉算法。本手册介绍了所谓的 OpenCV 2.x API,它本质上是一个 C++ API,与基于 C 的 OpenCV 1.x API 相反(C API 已被弃用,并且自 OpenCV 2.4 版本以来未经“C”编译器测试)。
OpenCV 具有模块化结构,这意味着该软件包包含多个共享库或静态库。以下是可用的模块:
手册的后续章节将介绍每个模块的功能。但首先,请确保熟悉库中广泛使用的通用 API 概念。
所有 OpenCV 类和函数都放置在 cv
命名空间中。因此,要从您的代码中访问此功能,请使用 cv::
指定符或 using namespace cv;
指令
或
OpenCV 的一些当前或未来的外部名称可能与 STL 或其他库冲突。在这种情况下,请使用显式命名空间指定符来解决命名冲突
OpenCV 自动处理所有内存。
首先,std::vector、cv::Mat 和函数和方法使用的其他数据结构具有析构函数,这些析构函数会在需要时释放底层内存缓冲区。这意味着析构函数并不总是像 Mat 那样释放缓冲区。它们会考虑可能的数据共享。析构函数会递减与矩阵数据缓冲区关联的引用计数。仅当引用计数达到零时才会释放缓冲区,即当没有其他结构引用相同缓冲区时。类似地,当复制 Mat 实例时,不会实际复制任何数据。相反,引用计数会递增以记录存在另一个相同数据的拥有者。还有一个 cv::Mat::clone 方法,它会创建矩阵数据的完整副本。请参见下面的示例
您会看到 Mat 和其他基本结构的使用很简单。但是,高级类或甚至在没有考虑自动内存管理的情况下创建的用户数据类型呢?对于它们,OpenCV 提供了 cv::Ptr 模板类,它类似于 C++11 中的 std::shared_ptr。因此,与其使用普通指针
您可以使用
或
Ptr<T>
封装指向 T 实例的指针以及与该指针关联的引用计数。有关详细信息,请参见 cv::Ptr 说明。
OpenCV 自动释放内存,并在大多数情况下自动为输出函数参数分配内存。因此,如果一个函数有一个或多个输入数组(cv::Mat 实例)和一些输出数组,则输出数组将自动分配或重新分配。输出数组的大小和类型由输入数组的大小和类型决定。如果需要,函数会采用帮助确定输出数组属性的额外参数。
示例
由于视频帧分辨率和位深度对视频采集模块已知,因此数组 frame 由 >>
运算符自动分配。数组 edges 由 cvtColor 函数自动分配。它具有与输入数组相同的大小和位深度。通道数为 1,因为传递了颜色转换代码 cv::COLOR_BGR2GRAY,这意味着颜色转换为灰度转换。请注意,frame 和 edges 仅在循环体第一次执行期间分配一次,因为所有后续视频帧都具有相同的分辨率。如果您以某种方式更改了视频分辨率,则数组会自动重新分配。
这项技术的关键组件是 cv::Mat::create 方法。它接受所需的数组大小和类型。如果数组已经具有指定的大小和类型,则该方法不会执行任何操作。否则,它会释放先前分配的数据(如果有)(此部分涉及递减引用计数器并将其与零进行比较),然后分配一个具有所需大小的新缓冲区。大多数函数都会调用 cv::Mat::create 方法为每个输出数组,因此实现了自动输出数据分配。
此方案中的一些显著例外是 cv::mixChannels、cv::RNG::fill 以及其他一些函数和方法。它们无法分配输出数组,因此您必须提前进行此操作。
作为一个计算机视觉库,OpenCV 处理大量图像像素,这些像素通常以紧凑的 8 位或 16 位每通道形式编码,因此具有有限的值范围。此外,图像上的某些操作(如颜色空间转换、亮度/对比度调整、锐化、复杂插值(双三次、兰索斯))可能会产生超出可用范围的值。如果您只是存储结果的最低 8 位(16 位),这会导致视觉伪影,并可能影响进一步的图像分析。为了解决这个问题,使用了所谓的饱和算术。例如,要将操作结果 r 存储到 8 位图像中,您需要找到 0..255 范围内最接近的值
\[I(x,y)= \min ( \max (\textrm{round}(r), 0), 255)\]
类似的规则适用于 8 位有符号、16 位有符号和无符号类型。此语义在整个库中使用。在 C++ 代码中,它是使用 cv::saturate_cast<>
函数完成的,这些函数类似于标准 C++ 转换操作。请参见下面提供的公式的实现
其中 cv::uchar 是 OpenCV 8 位无符号整型。在优化的 SIMD 代码中,使用 paddusb、packuswb 等 SSE2 指令。它们有助于实现与 C++ 代码中完全相同的行为。
模板是 C++ 的一项强大功能,它可以实现非常强大、高效且安全的的数据结构和算法。但是,大量使用模板可能会显着增加编译时间和代码大小。此外,当专门使用模板时,很难分离接口和实现。这对基本算法来说可能没问题,但对于计算机视觉库来说,一个算法可能跨越数千行代码,这并不适合。由于这个原因,以及为了简化其他语言(如 Python、Java、Matlab,它们根本没有模板或模板功能有限)的绑定开发,当前的 OpenCV 实现基于多态性和运行时调度超过模板。在运行时调度速度太慢(如像素访问运算符)、不可能(通用 cv::Ptr<>
实现)或非常不方便(cv::saturate_cast<>()
)的地方,当前的实现引入了小型模板类、方法和函数。在当前 OpenCV 版本的其他任何地方,对模板的使用都受到限制。
因此,库可以操作的原始数据类型集是有限的。也就是说,数组元素应该具有以下类型之一
对于这些基本类型,应用以下枚举
可以使用以下选项指定多通道(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)
位。示例
具有更复杂元素的数组不能使用 OpenCV 创建或处理。此外,每个函数或方法只能处理所有可能数组类型的一个子集。通常,算法越复杂,支持的格式子集就越小。请参见下面此类限制的典型示例
每个函数支持的类型子集是根据实际需求定义的,可以根据用户的请求在将来扩展。
许多 OpenCV 函数处理密集的二维或多维数值数组。通常,这些函数将 cv::Mat
作为参数,但在某些情况下,使用 std::vector<>
(例如,对于点集)或 cv::Matx<>
(用于 3x3 单应性矩阵等)更方便。为了避免 API 中出现许多重复项,引入了特殊的“代理”类。基本“代理”类是 cv::InputArray。它用于在函数输入上传递只读数组。从 InputArray 类派生的 cv::OutputArray 用于指定函数的输出数组。通常,您不应该关心这些中间类型(您也不应该显式声明这些类型的变量)——一切都会自动工作。您可以假设,除了 InputArray/OutputArray 之外,您始终可以使用 cv::Mat
、std::vector<>
、cv::Matx<>
、cv::Vec<>
或 cv::Scalar
。当函数具有可选的输入或输出数组,并且您没有或不想要数组时,请传递 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),它仅在调试配置中保留。由于自动内存管理,所有中间缓冲区在发生突然错误时会自动释放。如果需要,您只需要添加一个 try 语句来捕获异常。
当前的 OpenCV 实现是完全可重入的。也就是说,可以从不同的线程调用相同的函数或不同类实例的相同方法。同样,相同的 Mat 可以在不同的线程中使用,因为引用计数操作使用特定于体系结构的原子指令。