阅读(4352) (0)

OpenCV教程

2017-08-22 11:02:53 更新

OpenCV教程这里提到的所有源代码都是作为OpenCV常规版本的一部分提供的,因此请在开始复制和粘贴代码之前检查。以下教程列表将自动从位于我们的GIT存储库中的reST文件生成。

和往常一样,我们很乐意听到您的意见,并收到您对任何教程的贡献。

该文档的其他章节描述了每个模块的功能。但首先,请确保熟悉库中使用的常见API概念。

API概念

cv命名空间

所有OpenCV类和函数都放在cv命名空间中。因此,要从代码访问此功能,请使用cv :: specifier或使用命名空间cv; 指示:

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

or:

#include "opencv2/core.hpp"
using namespace cv;
...
Mat H = findHomography(points1, points2, CV_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,Mat和其他数据结构具有析构函数,在需要时释放底层内存缓冲区。这意味着析构函数并不总是像Mat一样释放缓冲区。他们考虑到可能的数据共享。析构函数递减与矩阵数据缓冲器相关联的引用计数器。当且仅当引用计数器达到零时,即当没有其他结构指向相同的缓冲区时,缓冲区被解除分配。类似地,当Mat实例被复制时,实际数据没有被真正复制。相反,引用计数器增加以记住相同数据的另一个所有者。还有Mat :: clone方法可以创建矩阵数据的完整副本。见下面的例子:

// create a big 8Mb matrix
Mat A(1000, 1000, CV_64F);
// create another header for the same matrix;
// this is an instant operation, regardless of the matrix size.
Mat B = A;
// create another header for the 3-rd row of A; no data is copied either
Mat C = B.row(3);
// now create a separate copy of the matrix
Mat D = B.clone();
// copy the 5-th row of B to C, that is, copy the 5-th row of A
// to the 3-rd row of A.
B.row(5).copyTo(C);
// now let A and D share the data; after that the modified version
// of A is still referenced by B and C.
A = D;
// now make B an empty matrix (which references no memory buffers),
// but the modified version of A will still be referenced by C,
// despite that C is just a single row of the original A
B.release();
// finally, make a full copy of C. As a result, the big modified
// matrix will be deallocated, since it is not referenced by anyone
C = C.clone();

你看到Mat和其他基本结构的使用很简单。但是,如果不考虑自动内存管理,高级类甚至用户数据类型的创建呢?对于他们来说,OpenCV提供了类似于C ++ 11中的std :: shared_ptr的Ptr模板类。所以,而不是使用简单的指针:

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

您可以使用:

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

or:

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

Ptr <T>封装了一个指向T实例的指针和与该指针关联的引用计数器。有关详细信息,请参阅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",1);
    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,因为颜色转换代码COLOR_BGR2GRAY被传递,这意味着要进行灰度转换的颜色。请注意,帧和边缘在循环体的第一次执行期间仅被分配一次,因为所有下一个视频帧具有相同的分辨率。如果您以某种方式更改视频分辨率,则会自动重新分配阵列。

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

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

饱和度算术

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

QQ图片20170831111912

类似的规则适用于8位有符号的16位有符号和无符号类型。这个语义在库中随处可见。在C ++代码中,使用类似于标准C ++转换操作的saturate_cast <>函数完成。见下面提供的公式的实现:

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

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

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

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

模板是C ++的一个很好的功能,可以实现非常强大,高效且安全的数据结构和算法。然而,广泛使用模板可能会大大增加编译时间和代码大小。此外,当仅使用模板时,难以分离接口和实现。这对于基本算法来说可能是很好的,但对于单个算法可能跨越数千行代码的计算机视觉库而言并不好。因此,为了简化其他语言的绑定,如Python,Java,没有模板或模板功能有限的Matlab,目前的OpenCV实现是基于模板上的多态和运行时调度。在那些运行时调度太慢(像像素访问操作符)的地方,不可能(通用的Ptr <>实现),或只是非常不方便(saturate_cast <>()),目前的实现引入了小模板类,方法和函数。目前OpenCV版本中的其他任何地方,模板的使用受到限制。

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

  • 8位无符号整数(uchar)
  • 8位有符号整数(schar)
  • 16位无符号整数(ushort)
  • 16位有符号整数(短)
  • 32位有符号整数(int)
  • 32位浮点数(浮点数)
  • 64位浮点数(双)
  • 所有元素具有相同类型(上述之一)的几个元素的元组。其元素是这样的元组的数组称为多通道阵列,与单通道阵列相反,其单元是标量值。通道的最大可能数量由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通道)类型:

例:

Mat mtx(3, 3, CV_32F); // make a 3x3 floating-point matrix
Mat cmtx(10, 1, CV_64FC2); // make a 10x1 2-channel floating-point
                           // matrix (10-element complex vector)
Mat img(Size(1920, 1080), CV_8UC3); // make a 3-channel (color) image
                                    // of 1920 columns and 1080 rows.
Mat grayscale(image.size(), CV_MAKETYPE(image.depth(), 1)); // make a 1-channel image of
                                                            // the same size and same
                                                            // channel type as img

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

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

每个功能的支持类型的子集已经根据实际需要定义,并且可以在将来根据用户请求进行扩展。

InputArray和OutputArray

许多OpenCV功能处理密集的二维或多维数字数组。通常,这些函数以cppMat为参数,但在某些情况下,使用std :: vector <>(例如,对于一个点集合)或Matx <>(对于3x3单应性矩阵等)来说更方便。为了避免API中的许多重复,引入了特殊的“代理”类。基本的“代理”类是InputArray。它用于在函数输入上传递只读数组。派生自InputArray类的OutputArray用于指定函数的输出数组。通常,您不应该关心这些中间类型(而不应该明确地声明这些类型的变量) - 它们都将自动工作。你可以假设,而不是InputArray / OutputArray,你可以随时使用Mat,std :: vector <>,Matx <>,Vec <>或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(条件)仅保留在调试配置中。由于自动内存管理,所有中间缓冲区会在突发错误的情况下自动释放。如果需要,您只需要添加一个try语句即可捕获异常:

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

多线程和可重入性

目前的OpenCV实现完全可重新进入。也就是说,可以从不同的线程调用类实例的相同函数,相同的常量方法或不同类实例的相同的非常量方法。另外,同样的cv :: mat可以用于不同的线程,因为引用计数操作使用特定于架构的原子指令。