OpenCV系列一 --- OpenCV简介

版权声明:转载需转载声明 https://blog.csdn.net/qq_32285693/article/details/89287981

OpenCV(开源计算机视觉库:http://opencv.org)是一个开源的BSD许可库,包含数百种计算机视觉算法。该文档描述了OpenCV 2.x 的API,它本质上是一个C++ API,而不是基于C的OpenCV 1.x API(自从OpenCV 2.4发布以来,C API已被弃用,未经过“C”编译器测试)。

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

Core functionality (core)[核心功能(核心)] - 定义基本数据结构的紧凑模块,包括密集的多维数组Mat和所有其他模块使用的基本功能。

Image Processing (imgproc)[图像处理(imgproc)] - 一种图像处理模块,包括线性和非线性图像滤波,几何图像变换(调整大小,仿射和透视扭曲,基于通用表的重新映射),颜色空间转换,直方图等。

Video Analysis (video)[视频分析(视频)] - 视频分析模块,包括运动估计,背景减法和对象跟踪算法。

Camera Calibration and 3D Reconstruction (calib3d)[相机校准和3D重建(calib3d)] - 基本的多视图几何算法,单一和立体相机校准,物体姿态估计,立体对应算法和3D重建元素。

2D Features Framework (features2d)[2D特征框架(features2d) ] - 显著特征检测器,描述符和描述符匹配器。

Object Detection (objdetect)[对象检测(objdetect)] - 检测预定义类的对象和实例(例如,面部,眼睛,马克杯,人,汽车等)。

High-level GUI (highgui)[高级GUI(highgui)] - 易于使用的简单UI功能界面。

Video I/O (videoio)[视频I / O(视频)] - 一个易于使用的视频捕获和视频编解码器接口。

…一些其他辅助模块,例如FLANN和Google测试包装器,Python绑定等。

扫描二维码关注公众号,回复: 5881615 查看本文章

#API 的概念
一、cv 命名空间[cv Namespace]

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

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

要么 :

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

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

	Mat H = findHomography(points1, points2, RANSAC, 5);
	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实例时,实际上并未复制实际数据。相反,引用计数器递增以记住存在相同数据的另一个所有者。要想完全的将Mat的数据拷贝过来,可以用Mat :: clone方法创建矩阵数据的完整副本。

实例:

    // 创建一个大的8Mb矩阵
	Mat A(1000, 1000, CV_64F);
	// 为同一个矩阵创建另一个头信息;
	// 这是一个即时操作,无论矩阵大小如何。
	Mat B = A;
	// 为A的第3行创建另一个头信息; 也没有复制数据
	Mat C = B.row(3);
	// 现在创建一个单独的矩阵副本
	Mat D = B.clone();
	// 将第5行B复制到C,即复制A的第5行到A的第3行
	B.row(5).copyTo(C);
	// 现在让A和D共享数据; 之后的修改版本A仍由B和C引用。
	A = D;
	// 现在使B成为一个空矩阵(它不引用内存缓冲区),
	// 但是C的修改版仍然会被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实例的指针和与指针关联的引用计数器

三、输出数据的自动分配

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);		// 使用Canny算子进行边缘检测
		imshow("edges", edges);
		if (waitKey(30) >= 0) break;
	}
	return 0;
}

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

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

四、饱和度算法

作为计算机视觉库,OpenCV对图像像素进行了大量处理,这些像素通常以紧凑的,每通道8位或16位编码形式,因此具有有限的值范围。此外,对图像的某些操作,如色彩空间转换,亮度 / 对比度调整,锐化,复杂插值(双三次,Lanczos)可以产生超出可用范围的值。如果只存储结果的最低8(16)位,则会导致视觉伪影,并可能影响进一步的图像分析。为了解决这个问题,使用所谓的饱和算术。
例如,要将操作结果r存储到8位图像,您会在0…255范围内找到最接近的值:
I(x, y) = min(max(round®, 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位无符号整数(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 };

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

CV_8UC1 ... CV_64FC4 // 常数(对于1到4个通道)
CV_8UC(n) ... CV_64FC(n) or CV_MAKETYPE(CV_8U, n) ... CV_MAKETYPE(CV_64F, n) //当编译时通道数大于4或未知时的宏。

Note

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); // 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无法构造或处理具有更复杂元素的数组。此外,每个函数或方法只能处理所有可能的数组类型的子集。通常,算法越复杂,支持的格式子集越小。

六、InputArray和OutputArray
许多OpenCV函数需要处理密集的二维或多维数值数组。通常,这样的函数将cppMat作为参数,但在某些情况下使用它更方便std::vector<>(例如,对于点集)或cv::Matx<>(对于3x3单应矩阵等)。为了避免API中的许多重复,引入了特殊的“代理”类。基础“代理”类是cv::InputArray。它用于在函数输入上传递只读数组。派生自InputArray类cv::OutputArray用于指定函数的输出数组。通常,您不应该关心那些中间类型(并且您不应该明确声明这些类型的变量) - 它们都将自动运行。你可以假设,而不是InputArray / 的OutputArray您可以随时使用Mat,std::vector<>,cv::Matx<>,cv::Vec<>或cv::Scalar。当函数具有可选的输入或输出数组,并且您没有或不需要它时,请传递cv::noArray()。

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

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

通常使用CV_Error(errcode, description)宏或类似printf的CV_Error_(errcode, (printf-spec, printf-args))变体抛出异常,或者使用CV_Assert(条件)宏来检查条件并在不满足时抛出异常。对于性能关键代码,CV_DbgAssert(条件)仅保留在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的简介到这里就结束啦,如果大家喜欢的话,可以给我点个赞哦!!!

猜你喜欢

转载自blog.csdn.net/qq_32285693/article/details/89287981
今日推荐