OpenCV 4.7.0 简介

简介

OpenCV(开源计算机视觉库:http://opencv.org)是一个开源的库,包括几百种计算机视觉算法。该文件描述了所谓的OpenCV 2.x API,它本质上是一个C++ API,而不是基于C的OpenCV 1.x API(自OpenCV 2.4发布以来,C API已被弃用,并且没有使用 "C "编译器进行测试)

OpenCV有一个模块化的结构,这意味着该软件包包括几个共享或静态库。以下模块是可用的:

核心功能(core)–一个紧凑的模块,定义了基本的数据结构,包括密集的多维阵列Mat和所有其他模块使用的基本功能。
图像处理(imgproc)–一个图像处理模块,包括线性和非线性图像过滤、几何图像变换(调整大小、仿生和透视翘曲、基于通用表的重映射)、色彩空间转换、直方图等等。
视频分析(video)–视频分析模块,包括运动估计、背景减法和物体跟踪算法。
相机校准和三维重建(calib3d)–基本的多视角几何算法、单机和立体相机校准、物体姿态估计、立体对应算法以及三维重建的元素。
二维特征框架(features2d)–突出特征检测器、描述器和描述器匹配器。
对象检测(objdetect)–检测对象和预定义类别的实例(例如,脸、眼睛、杯子、人、汽车等)。
高级GUI(highgui)–一个易于使用的简单UI功能的界面。
视频I/O(videoio)–一个易于使用的视频捕获和视频编解码接口。
…其他一些辅助模块,如FLANN和Google测试包装器、Python绑定等。

本文件的后续章节将描述每个模块的功能。但首先,请确保彻底熟悉库中使用的常见API概念。

API概念

cv 名称空间/命名空间(即namespace)

所有的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实例被复制时,没有实际的数据被复制。相反,引用计数器被递增以记住相同数据的另一个所有者。还有一个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
// 之后,A的修改版本仍然被B和C所引用。
A = D;
// 现在让B成为一个空矩阵(它没有引用任何内存缓冲区)。
// 但是A的修改版本仍然被C引用。
// 尽管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 实例的指针和一个与该指针相关的引用计数器。详见cv::Ptr描述。

输出数据的自动分配

OpenCV会自动去分配内存,以及在大多数情况下自动为输出函数参数分配内存。因此,如果一个函数有一个或多个输入数组(cv::Mat实例)和一些输出数组,输出数组会被自动分配或重新分配。输出数组的大小和类型是由输入数组的大小和类型决定的。如果需要,这些函数会接受额外的参数,以帮助弄清输出数组的属性。

示例:

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
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;
}

数组帧是由>>操作符自动分配的,因为视频帧的分辨率和位深对视频捕获模块来说是已知的。数组边缘是由cvtColor函数自动分配的。它的大小和位深与输入数组相同。通道数为1,因为颜色转换代码cv::COLOR_BGR2GRAY被传递,这意味着颜色到灰度的转换。请注意,在循环体的第一次执行中,帧和边只被分配一次,因为所有接下来的视频帧都有相同的分辨率。如果你以某种方式改变了视频分辨率,数组会自动重新分配。

这项技术的关键部分是cv::Mat::create方法。它接收所需的数组大小和类型。如果数组已经有了指定的大小和类型,这个方法就不做任何事情。否则,它将释放先前分配的数据(如果有的话)(这部分包括递减引用计数器并将其与零进行比较),然后分配一个所需大小的新缓冲区。大多数函数为每个输出数组调用cv::Mat::create方法,因此自动输出数据分配得到了实现。

这个方案的一些明显的例外是cv::mixChannels,cv::RNG::fill,和其他一些函数和方法。它们不能分配输出数组,所以你必须提前做这个。

饱和度算法

作为一个计算机视觉库,OpenCV经常与图像像素打交道,这些像素通常以每通道8位或16位的紧凑形式进行编码,因此其数值范围有限。此外,对图像的某些操作,如色彩空间转换、亮度/对比度调整、锐化、复杂的插值(双立方体、Lanczos)会产生超出可用范围的数值。如果你只是存储结果的最低8(16)位,这将导致视觉伪影,并可能影响进一步的图像分析。为了解决这个问题,采用了所谓的饱和运算法则。例如,要将操作的结果r存储到8位图像中,你要在0…255范围内找到最近的值:

I ( x , y ) = min ⁡ ( max ⁡ ( r o u n d ( r ) , 0 ) , 255 ) I\left( x,y \right) =\min \left( \max \left( round\left( r \right) ,0 \right) ,255 \right) I(x,y)=min(max(round(r),0),255)

类似的规则也适用于8位有符号、16位有符号和无符号类型。这种语义在库中到处都有使用。在C++代码中,它是通过cv::saturate_cast<>函数完成的,这些函数类似于标准的C++投射操作。请看下面提供的公式的实现:

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

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

注意
当结果为32位整数时,不应用饱和度。
固定的像素类型。模板的有限使用

模板是C++的一个伟大功能,它能够实现非常强大、高效而又安全的数据结构和算法。然而,大量使用模板可能会极大地增加编译时间和代码大小。此外,当完全使用模板时,很难将接口和实现分开。这对基本的算法来说可能是好的,但对计算机视觉库来说却不是好事,因为一个算法可能会跨越数千行的代码。正因为如此,同时也为了简化其他语言的绑定开发,如Python、Java、Matlab,这些语言根本没有模板或模板功能有限,目前OpenCV的实现是基于多态性和模板的运行时调度。在那些运行时调度会太慢(如像素访问运算符)、不可能(通用的cv::Ptr<>实现)或只是非常不方便(cv::saturate_cast<>())的地方,目前的实现引入了小型模板类、方法和函数。在当前的OpenCV版本中,其他任何地方对模板的使用都是有限的。

因此,有一个有限的固定的原始数据类型集,该库可以对其进行操作。也就是说,数组元素应该有以下类型之一:

8位无符号整数(ugar)
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 };

多通道(n-channel)类型可以通过以下选项来指定。
CV_8UC1 … CV_64FC4常数(用于1到4个通道的数量)。
CV_8UC(n) … CV_64FC(n) 或 CV_MAKETYPE(CV_8U, n) … CV_MAKETYPE(CV_64F, n) 宏,当通道数超过4或在编译时未知时。

备注
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的双通道浮点矩阵
                           // 矩阵(10元素复数向量)
Mat img(Size(1920, 1080), CV_8UC3); // 制作一个3通道(彩色)图像。
                                    //有1920列和1080行。
Mat grayscale(img.size(), CV_MAKETYPE(img.depth(), 1)); // 做一个单通道的图像,大小相同。
                                                        // 与img.size()相同的单通道图像,并且
                                                        // 的单通道图像,并且与Img的通道类型相同。

具有更多复杂元素的数组不能用OpenCV构建或处理。此外,每个函数或方法只能处理所有可能的数组类型中的一个子集。通常情况下,算法越复杂,支持的格式子集就越小。请看下面这种限制的典型例子:

人脸检测算法只适用于8位灰度或彩色图像。
线性代数函数和大多数机器学习算法只对浮点数组工作。
基本函数,如cv::add,支持所有类型。
色彩空间转换函数支持8位无符号、16位无符号和32位浮点类型。
每个函数支持的类型子集是根据实际需要定义的,将来可以根据用户要求进行扩展。

InputArray和OutputArray

许多OpenCV函数处理密集的二维或多维数字数组。通常,这类函数采用cv::Mat作为参数,但在某些情况下,使用std::vector<>(例如用于点集)或cv::Matx<>(用于3x3同源矩阵之类的)更为方便。为了避免API中的许多重复,我们引入了特殊的 "代理 "类。基本的 "代理 "类是cv::InputArray。它用于在一个函数输入上传递只读数组。从InputArray派生出来的类cv::OutputArray是用来为一个函数指定一个输出数组。通常情况下,你不应该关心这些中间类型(你也不应该明确地声明这些类型的变量)–这一切都会自动工作。你可以假设你总是使用cv::Mat, std::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
{
    ... // call OpenCV
}
catch (const cv::Exception& e)
{
    const char* err_msg = e.what();
    std::cout << "exception caught: " << err_msg << std::endl;
}
多线程和可重入性

目前的OpenCV实现是完全可重入的。也就是说,不同类实例的相同函数或相同方法可以从不同的线程中调用。同时,由于引用计数操作使用了特定架构的原子指令,所以同一个Mat可以在不同的线程中使用。

你可能感兴趣的:(OpenCV,4.7.0,说明文档,opencv,计算机视觉,人工智能)