安阳哪里做网站,成都公司网站建设,做网站分为竞价和优化,微信如何制作一个网页【OpenCV C20 学习笔记】基本图像容器——Mat 概述Mat内部结构引用计数机制颜色数据格式 显式创建Mat对象使用cv::Mat::Mat构造函数矩阵的数据项 使用数组进行初始化的构造函数cv::Mat::create函数MATLAB风格的初始化小型矩阵通过复制创建Mat对象 Mat对象的输出其他普通数据项的… 【OpenCV C20 学习笔记】基本图像容器——Mat 概述Mat内部结构引用计数机制颜色数据格式 显式创建Mat对象使用cv::Mat::Mat构造函数矩阵的数据项 使用数组进行初始化的构造函数cv::Mat::create函数MATLAB风格的初始化小型矩阵通过复制创建Mat对象 Mat对象的输出其他普通数据项的输出 概述
电子设备中储存的图像本质是图像在每个像素点上的数值这些数据形成一个矩阵除此之外还包括一些描述这个数值矩阵的信息。OpenCV作为一个计算机视觉库同样也是要处理这样的信息。所以要学习OpenCV首要的事情是了解在OpenCV中是如何储存图像的数值矩阵以及描述信息的。 本文较长详细介绍了Mat对象的原理、创建方式和输出方式读者可根据目录跳转至相关章节
Mat
2001年OpenCV诞生的时候是在C语言的接口上创建的并将图像储存在一个称作IplImage的C语言数据结构中。这种方式最大的一个缺点就是需要用户自己管理内存。如果是小型的项目尚可如果数据量变大管理内存就会使人很头疼。 OpenCV2.0 引入了C接口实现了内存的自动管理。Mat成为了OpenCV储存图片信息的数据结构。 Mat不需要手动分配或释放内存大部分的OpenCV方法都会自动为输出的Mat对象分配内存。如果你已经为一个Mat对象分配了它需要的内存那么你在传输它的时候这个内存会被重复利用。也就是说在执行任务的时候不会使用多余的内存。
内部结构
Mat实质上是一个包含了两个部分的类
矩阵头matrix header它包含了矩阵的大小、存储方式、存储地址等信息指向矩阵的指针Mat对象只是储存了矩阵的指针并没有储存矩阵本身而矩阵中包含了像素值像素值矩阵的维度由存储方式决定 Mat对象的大小是固定的但是矩阵本身的大小是跟随图像变化的。 在函数间传递图像是OpenCV中非常常见的操作而且某些图像处理算法很复杂。为了提高程序的运行速度OpenCV使用了“引用计数机制”。每个Mat对象都有自己独立的矩阵头但是同一个矩阵可能会被多个Mat对象共享即多个Mat对象的指针可能会指向内存中的同一个矩阵。而复制操作只会复制Mat对象的矩阵头以及指向矩阵的指针并不会直接复制矩阵的数值!
下面的代码详细展示了Mat对象在实际应用中的内存分配问题
Mat A, C; //创建Mat对象的时候只是创建了矩阵头的部分
A imread(argv[1], IMREAD_COLOR); //读取图片分配内存存储图片的数值矩阵并将A的指针指向这个矩阵的内存地址Mat B(A); //调用复制构造函数创建B但仅仅是将A中的指向图片矩阵的指针复制到B中并没有复制图片的数值矩阵C A; //赋值操作也只是将A中的指针复制到C中上面的代码最终使A、B、C3个Mat对象中的指针都指向同一个图片的数值矩阵虽然进行了复制和赋值操作但内存中始终只有一个数值矩阵如下图 因为3个Mat对象的指针都是指向同一个数据矩阵所以在任何一个Mat对象中对数据矩阵进行修改都会影响到其他Mat对象。实际上不同的Mat对象只是为处理同一个数据矩阵提供了不同的使用方法。但是这些Mat对象的矩阵头部分是不同的你甚至可以创建一个只指向数据矩阵的其中一部分的Mat对象。例如要想在图像中创建一个感兴趣区域region of interestROI你可以新建一个Mat对象
Mat D(A, Rect(10, 10, 100, 100); //使用矩形区域
Mat E A(Range::all(), Range(1, 3)); //使用行和列引用计数机制
如果像上面的例子一样同一个数据矩阵属于不同的Mat对象那到底谁来负责释放它的内存呢答案是最后一个使用它的Mat对象。这就是通过上面所说的“引用计数机制”来实现的。当有指向数据矩阵A的Mat对象被复制的时候矩阵A的引用计数就会增加当有指向矩阵A的Mat对象被销毁的时候矩阵A的引用计数就会减少。当计数为0的时候矩阵A就会被释放。 OpenCV还提供了深度复制数据矩阵的方法当你不想只是复制指针而是想复制矩阵的值的时候可以使用cv::Mat::clone()和cv::Mat::copyTo()方法。
Mat F A.clone(); //将A指向的数据矩阵复制给F
Mat G;
A.copyTo(G); //将A指向的数据矩阵复制到G这样修改F和G的时候就不会影响A指向的数据矩阵了。
总结一下
OpenCV中函数导出的图像数据是自动分配内存的除非特别指定不自动分配使用OpenCV的C接口的时候不用考虑内存管理的问题赋值运算符和复制构造函数只是复制Mat对象的头部信息和指针可以用cv::Mat::clone()和cv::Mat::copyTo()方法实现底层的图片数据矩阵的复制
颜色数据格式
对于如何储存像素的值通常从两个方面考虑颜色空间和数据类型。 颜色空间是指利用基本的颜色组合成特定的颜色的方式。有多种方式可以选择
RGB这是最常用的因为它与人眼编码颜色的方式相似由红、绿、蓝3中基本颜色的值加上透明度alpha来确定最终颜色注意OpenCV中的标准颜色显示系统为BGR红色和蓝色的值调换了位置HSV和HLS将颜色分解为色调、饱和度和亮度这种方式能更方便地处理图片的亮度YCrCb这是JPEG格式的图片常用的颜色编码方式CIT Lab*这种编码方式能够方便测量两种颜色之间的差距灰度只有黑色和白色两种基本颜色
显式创建Mat对象
使用cv::Mat::Mat构造函数
Mat M(2,2, CV_8U3, Scalar(0,0,255));
cout M endl M endl endl;这里使用了Mat类的其中一个构造函数。该构造函数一共包括4个参数
行数定义矩阵行数列数定义矩阵列数数据类型定义每个数据项的类型下文详述Scalar常量用来定义每个数据项的值的向量数组
矩阵的数据项
矩阵数据项的数据类型的定义遵循以下语法规则 CV_[每个数据项的比特数][有符号或无符号][类型前缀]C[通道数量]
比特数确定每个数据项即像素点的数值的长度如8比特比特数越高每个像素点的值域就越大比如32比特的浮点类型比8比特的char类型能够储存更多的颜色值有符号或无符号确定每个数据项的值是否是有符号的可省略默认为无类型前缀如果是char类型则为C如果是float类型则为F……通道数量确定每个数据项中包含的颜色通道数量比如RGB颜色空间可以有4个通道分别是红色值、绿色值、蓝色值和透明度值通道数量可以加上括号如CV_8UC(3) 可省略默认为1 上面代码中的CV_8U3就代表每个数据项的是具有3个通道的8比特无符号的值输出结果如下 可以看到矩阵中每个项有3个数值代表3个颜色通道共有2*2个项每个项中的3个颜色通道的值都与Scalar中定义的相同。
使用数组进行初始化的构造函数
除了2维的矩阵也可以创建3维矩阵的Mat对象
int sz[3]{ 2,2,2 };
Mat L(3, sz, CV_8UC1, Scalar::all(0));这个构造函数也使用4个参数
维度确定矩阵的维度大小一个数组用来确定每个维度的大小数据项的数据类型同上一个构造函数Scalar常量同上一个构造函数 所以这里创建了一个3维的矩阵每个维度都只有2个数据项即222每个数据项使用的都是只有1个颜色通道的8比特无符号数值每个数据项的值都为0。
cv::Mat::create函数
这个函数看起来像是在创建一个Mat对象但其实它只能修改已有的Mat对象。 比如对上面创建的M对象进行修改
M.create(4, 4, CV_8UC2);
cout M endl M endl endl;cv::Mat::create函数使用了3个参数
行数修改后的行数列数修改后的列数数据项类型修改后的数据项类型 输出结果为 可以看到原本22的3颜色通道的矩阵变成了44的2颜色通道矩阵。cv::Mat::create函数为M对象重新分配了内存使其能储存修改之后的更大的矩阵。
MATLAB风格的初始化
cv::Mat::zeros, cv::Mat::ones, cv::Mat::eye等与MATLAB语言类似的函数也可以用来初始化OpenCV中的Mat对象 zeros函数用来创建全为0值的矩阵ones函数用来创建全为1值的矩阵eye函数用来创建对角线为1其他值为0的矩阵 Mat E {Mat::eye(4, 4, CV_64F)};cout E endl E endl endl;Mat O {Mat::ones(2, 2, CV_32F)};cout O endl O endl endl;Mat Z {Mat::zeros(3,3, CV_8UC1)};cout Z endl Z endl endl;这些函数都使用相同的参数列表
行数列数数据项类型 输出结果如下
小型矩阵
如果要构造小型矩阵可以直接以逗号为间隔用运算符将每个值一行一行依次输入 在C11之后也可以使用{}风格的初始化列表
//运算符Mat C (Mat_double(3,3) 0, -1, 0, -1, 5, -1, 0, -1, 0);cout C endl C endl endl;//初始化列表C (Mat_double({0, -1, 0, -1, 5, -1, 0, -1, 0})).reshape(3); //reshape函数将矩阵中的数据项变成3通道类型cout C endl C endl endl;输出结果如下
通过复制创建Mat对象
要复制Mat对象需要第二节讲的使用cv::Mat::clone 或 cv::Mat::copyTo 函数
Mat对象的输出
上面的例子中的输出使用的都是默认格式但还有几种其他的输出格式 首先使用随机数创建一个3通道的3*2矩阵 Mat R {Mat(3, 2, CV_8UC3)};randu(R, Scalar::all(0), Scalar::all(255));cv::randu()为随机数生成函数使用3个参数
Mat对象用来储存随机值的Mat对象最低值Scalar常量类型确定随机数的最小值最高值Scalar常量类型确定随机数的最大值 接下来使用format函数定义输出格式该函数使用两个参数Mat对象需要输出的Mat对象格式定义在Formatter中定义的枚举类型 详见以下代码
cout R (default) endl R endl endl;
cout R (Python) endl format(R, Formatter::FMT_PYTHON) endl endl;
cout R (csv) endl format(R, Formatter::FMT_CSV) endl endl;
cout R (numpy) endl format(R, Formatter::FMT_NUMPY) endl endl;
cout R (C) endl format(R, Formatter::FMT_C) endl endl;输出结果如下
其他普通数据项的输出
OpenCV中的大部分数据结构都支持运算符 以下代码展示了如何运用运算符输出点、向量类型的对象
Point2f P(5, 1);
cout Point (2D) P endl endl;Point3f P3f(2, 6, 7);
cout Point (3D) P3f endl endl;vectorfloat v;
v.push_back((float)CV_PI); v.push_back(2); v.push_back(3.01f);
cout Vector of floats via Mat Mat(v) endl endl;vectorPoint2f vPoints(20);
for (size_t i 0; i vPoints.size(); i)vPoints[i] Point2f((float)(i * 5), (float)(i % 7));
cout A vector of 2D Points vPoints endl endl;输出结果如下