OpenCV 4.11.0
开源计算机视觉库
加载中…
搜索中…
未找到匹配项
简介

OpenCV(开源计算机视觉库:https://opencv.ac.cn)是一个开源库,包含数百种计算机视觉算法。本文档描述的是所谓的 OpenCV 2.x API,它本质上是一个 C++ API,与基于 C 的 OpenCV 1.x API 相反(自 OpenCV 2.4 版本以来,C API 已被弃用,并且未经过“C”编译器的测试)

OpenCV 具有模块化结构,这意味着该软件包包含多个共享库或静态库。可用的模块如下:

  • 核心功能 (core) - 一个紧凑型模块,定义了基本数据结构,包括密集多维数组 Mat 和所有其他模块使用的基本函数。
  • 图像处理 (imgproc) - 一个图像处理模块,包括线性与非线性图像滤波、几何图像变换(缩放、仿射和透视变换、基于通用表的重映射)、颜色空间转换、直方图等等。
  • 图像文件读取和写入 (imgcodecs) - 包含用于读取和写入各种格式图像文件的函数。
  • 视频 I/O (videoio) - 一个易于使用的视频捕捉和视频编解码器接口。
  • 高级 GUI (highgui) - 一个易于使用的简单 UI 功能接口。
  • 视频分析 (video) - 一个视频分析模块,包括运动估计、背景减除和目标跟踪算法。
  • 相机标定和 3D 重建 (calib3d) - 基本的多视图几何算法、单目和立体相机标定、目标姿态估计、立体匹配算法和 3D 重建元素。
  • 2D 特征框架 (features2d) - 显著特征检测器、描述符和描述符匹配器。
  • 目标检测 (objdetect) - 预定义类(例如,人脸、眼睛、杯子、人、汽车等等)的目标和实例检测。
  • 深度神经网络模块 (dnn) - 深度神经网络模块。
  • 机器学习 (ml) - 机器学习模块包含一组用于数据统计分类、回归和聚类的类和函数。
  • 计算摄影 (photo) - 高级照片处理技术,如降噪、修复。
  • 图像拼接 (stitching) - 用于图像拼接和全景图创建的函数。
  • …还有一些其他的辅助模块,例如 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);
...
Mat findHomography(InputArray srcPoints, InputArray dstPoints, int method=0, double ransacReprojThreshold=3, OutputArray mask=noArray(), const int maxIters=2000, const double confidence=0.995)
查找两个平面之间的透视变换。
@ RANSAC
RANSAC 算法。
定义 calib3d.hpp:448

或者

#include "opencv2/core.hpp"
using namespace cv;
...
Mat H = findHomography(points1, points2, RANSAC, 5 );
...
n 维密集数组类
定义 mat.hpp:829
定义 core.hpp:107

当前或未来的某些 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.);
void log(InputArray src, OutputArray dst)
计算每个数组元素的自然对数。
#define CV_32F
定义 interface.h:78

自动内存管理

OpenCV 自动处理所有内存。

首先,函数和方法使用的std::vector、cv::Mat和其他数据结构具有析构函数,这些析构函数会在需要时释放底层内存缓冲区。这意味着析构函数并不总是像 Mat 的情况那样释放缓冲区。它们会考虑可能的数据共享。析构函数会递减与矩阵数据缓冲区关联的引用计数器。当且仅当引用计数器达到零时(即,当没有其他结构引用同一个缓冲区时),才会释放缓冲区。类似地,当复制 Mat 实例时,不会实际复制任何数据。相反,引用计数器会递增,以记住还有另一个相同数据的拥有者。还有一个cv::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 共享数据;之后,A 的修改版本
// 仍然由 B 和 C 引用。
A = D;
// 现在将 B 设置为空矩阵(不引用任何内存缓冲区),
// 但 A 的修改版本仍然会被 C 引用,
// 尽管 C 只是原始 A 的单行
B.release();
// 最后,创建一个 C 的完整副本。结果,大的修改后的
// 矩阵将被释放,因为它没有被任何人引用
C = C.clone();
CV_NODISCARD_STD Mat clone() const
创建数组和底层数据的完整副本。
void copyTo(OutputArray m) const
将矩阵复制到另一个矩阵。
Mat row(int y) const
为指定的矩阵行创建一个矩阵头。
void release()
递减引用计数器,并在需要时释放矩阵。
#define CV_64F
定义 interface.h:79

您可以看到 Mat 和其他基本结构的使用很简单。但是,对于高级类甚至在没有考虑自动内存管理的情况下创建的用户数据类型呢?对于它们,OpenCV 提供了 cv::Ptr 模板类,它类似于 C++11 中的 std::shared_ptr。因此,无需使用普通指针

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

您可以使用

Ptr<T> ptr(new T(...));
std::shared_ptr< _Tp > Ptr
定义 cvstd_wrapper.hpp:23

或者

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

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

输出数据的自动分配

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

示例

using namespace cv;
int main(int, char**)
{
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;
}
用于指定图像或矩形大小的模板类。
定义 types.hpp:335
用于从视频文件、图像序列或摄像头捕获视频的类。
定义 videoio.hpp:766
int main(int argc, char *argv[])
定义 highgui_qt.cpp:3

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

这项技术的关键组件是 cv::Mat::create 方法。它采用所需的数组大小和类型。如果数组已经具有指定的大小和类型,则该方法什么也不做。否则,它将释放先前分配的数据(如果存在)(此部分涉及递减引用计数器并将其与零进行比较),然后分配所需大小的新缓冲区。大多数函数都会为每个输出数组调用 cv::Mat::create 方法,因此实现了自动输出数据分配。

此方案的一些显著例外是 cv::mixChannelscv::RNG::fill 和一些其他函数和方法。它们无法分配输出数组,因此您必须提前执行此操作。

饱和运算

作为一个计算机视觉库,OpenCV 广泛处理图像像素,这些像素通常以紧凑的、每个通道 8 位或 16 位的形式编码,因此具有有限的值范围。此外,某些图像运算(如颜色空间转换、亮度/对比度调整、锐化、复杂插值(双三次、Lanczos))可能会产生超出可用范围的值。如果您只存储结果的最低 8(16)位,这会导致视觉伪影,并可能影响进一步的图像分析。为了解决这个问题,使用了所谓的 *饱和* 运算。例如,要将运算结果 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);
unsigned char uchar
定义 interface.h:51

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

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

固定像素类型。有限的模板使用

模板是C++的一项强大特性,它能够实现高效、安全且功能强大的数据结构和算法。然而,过度使用模板可能会显著增加编译时间和代码大小。此外,当只使用模板时,难以分离接口和实现。对于基本的算法来说,这可能还可以接受,但对于计算机视觉库来说则不然,因为单个算法可能包含数千行代码。由于这个原因,以及为了简化其他语言(例如Python、Java、Matlab)的绑定开发(这些语言根本没有模板或模板能力有限),当前的OpenCV实现基于多态性和运行时调度,而不是模板。在运行时调度速度过慢(例如像素访问运算符)、不可行(泛型cv::Ptr<>实现)或非常不方便(cv::saturate_cast<>())的地方,当前实现引入了小型模板类、方法和函数。在当前OpenCV版本的其他任何地方,模板的使用都是有限的。

因此,库可以操作的原始数据类型数量是有限的。也就是说,数组元素应该具有以下类型之一:

  • 8位无符号整数 (uchar)
  • 8位有符号整数 (schar)
  • 16位无符号整数 (ushort)
  • 16位有符号整数 (short)
  • 32位有符号整数 (int)
  • 32位浮点数 (float)
  • 64位浮点数 (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 };
#define CV_8S
定义 interface.h:74
#define CV_8U
定义 interface.h:73
#define CV_32S
定义 interface.h:77
#define CV_16S
定义 interface.h:76
#define CV_16U
定义 interface.h:75

可以使用以下选项指定多通道(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 元素复数向量)
// 矩阵(10 元素复数向量)
Mat img(Size(1920, 1080), CV_8UC3); // 创建一个 3 通道(彩色)图像
// 宽 1920 像素,高 1080 像素。
Mat grayscale(img.size(), CV_MAKETYPE(img.depth(), 1)); // 创建一个单通道图像,大小和通道类型与 img 相同
// 与 img 相同的大小和通道类型
// 的图像
#define CV_8UC3
定义 interface.h:90
#define CV_64FC2
定义 interface.h:125
#define CV_MAKETYPE(depth, cn)
定义 interface.h:85

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

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

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

InputArray 和 OutputArray

许多OpenCV函数处理密集的二维或多维数值数组。通常,这些函数将cv::Mat作为参数,但在某些情况下,使用std::vector<>(例如,对于点集)或cv::Matx<>(例如,对于3x3单应性矩阵)更方便。为了避免API中的许多重复,引入了特殊的“代理”类。基本“代理”类是cv::InputArray。它用于在函数输入中传递只读数组。从InputArray类派生的cv::OutputArray用于指定函数的输出数组。通常,你不必关心这些中间类型(并且你不应该显式声明这些类型的变量)——所有这些都会自动工作。你可以假设,代替InputArray/OutputArray,你总是可以使用cv::Matstd::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语句来捕获异常。

try
{
... // 调用OpenCV
}
catch (const cv::Exception& e)
{
const char* err_msg = e.what();
std::cout << "捕获到异常: " << err_msg << std::endl;
}
传递给错误的类。
定义 core.hpp:120
virtual const char * what() const noexcept override

多线程和可重入性

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