0.给自己打个小广告
本人211硕士毕业,目前从事深度学习,机器学习计算机视觉算法行业,目前正在将我的各类学习笔记发布在我的公众号中,希望感兴趣一起学习的同学们可以关注下~~~
本人微信公众号:yuanCruise
1.cv::Mat类简介
cv::Mat用于大型矩阵类型,这可以说是Opencv库整个c++实现的核心类。OpenCV库中绝大多数函数都是cv::Mat类的成员函数,或者以cv::Mat作为参数,或者以cv::Mat作为返回值。
cv::Mat类用于表示任意维度的dense数组。大多数图像数据都是存储在dense数组中,当然与之对应的就是sparse数组。
- dense数组:对于数组中的每个元素,内存中都存储了一个与当前元素对应的数据值,即使该元素值为零。
- sparse数组:存储的是非零项。如果许多元素值实际上为零,那么这会节省大量存储空间。(Opencv中永cv::SpareseMat类表述sparse数组。)
Note:若你对opencv2.1之前的版本较熟悉,你可能记得 , 。但在之后的版本中,这些类都没了,都被 取代。
2.cv::Mat类中数据存储规则
cv::Mat类数据可以被用来表示各种维度的数组。数据存储在数组中,可以看作是一个n维的“光栅扫描顺序(raster scan order)”。
raster scan order解释:
在一维数组中,元素是顺序排列的。
在二维数组中,元素是被组织成行,每一行依次排列
在三维数组中,每一个平面可以认为二维数组,且每一个平面(二维数组)按行排列,且平面是一个接着一个。
每个数组都有一个标志元素,用来指示数组的内容。
- dim:指定数组维度
- rows/cols:指定数组的行数和列数(当dim>2时,这两个参数无效)
- 指针:指向数组数据存储位置的数据指针
- refcount:引用计数器,类似于利用cv::Ptr<>的引用计数器。该类成员的存在使得cv::Mat类能像数据智能指针一样被数据包含。
数据中的内存布局由数组step[]描述。cv::Mat类中,当下标为(i0,i1,’’’,iNd-1,iNd)时,数组的存储地址为:
对于二维数组而言:
cv::Mat中的每个数据元素本身可以是单个数字,也可以是多个数字。在多个数字的情况下,这个库称为多通道数组。事实上,n维数组和(n-1)维多通道数组实际上是非常相似的对象,但是由于在许多情况下,将数组看作向量值数组是有用的,比如图像而言通道为3表示彩色图像,通道为1表示灰度图像,所以库中对这种结构有特殊的规定。
在使用opencv2.1之前的版本中的 时,有一个表征通道的成员变量: 。但在之后的版本中用cv::Mat类后,没有这个成员变量了,要查看通道数则需要调用cv::channels()。
要设置这种通道的区别的主要原因是内存访问。根据定义,数组的元素可以是向量值的部分。例如,一个数组可以被称为32位浮点数的二维三通道数组;在这种情况下,数组的元素是三个大小为12字节的32位浮点数。
当然在实际情况下,数据元素在内存中布局时,数组的行不是绝对按照顺序连续存放的;可能在行与行之间会有小的间隔。
这种填充的目的是提高内存访问速度。
n维单通道阵列与(n-1)维多通道阵列的区别在于,这种填充总是发生在完整行的末尾(也就是说,同一个通道中的元素总是顺序连续的)。
3.创建数组
cv::Mat m;
// Create data area for 3 rows and 10 columns of 3-channel 32-bit floats
m.create( 3, 10, CV_32FC3 );
// Set the values in the 1st channel to 1.0, the 2nd to 0.0, and the 3rd to 1.0
m.setTo( cv::Scalar( 1.0f, 0.0f, 1.0f ) );
可以利用cv::Mat类实例化一个变量来创建数组。当然利用这种方式创建的数组没有大小,也没有数据类型。在用这种方式创建数组后,之后可使用诸如create()这样的成员函数来分配数组大小,数据类型等参数。数组类型这一个参数决定了它具有哪种元素。
- 第一个位置参数:传入的是数组的行数,此例中表示数组3行
- 第二个位置参数:传入的是数组的列数,此例中表示数组10列
- 第三个位置参数:传入的是数组元素的类型,此例中32bit浮点数,3通道
第三个位置参数,即指定元素的基本类型,也指定通道数。所有可能的类型都在头文件中定义了,一共有6*3=18种可选方式(从如下组合中选取):
所以按照上述的例子CV_32FC3表示32bit的浮点数,3通道。
cv::Mat m( 3, 10, CV_32FC3, cv::Scalar( 1.0f, 0.0f, 1.0f ) );
如上述代码所示,前一段介绍的代码可以整合构造过程中,这是另一种构造函数(带参数的)。
理解数组中的数据没有严格地附加到数组对象上是至关重要的。Mat对象实际上是一个数据区域的标头,原则上是一个完全独立的东西。例如,可以将一个矩阵n赋值给另一个矩阵m(即m = n)。在这种情况下,m内部的数据指针将被改变为指向与n相同的数据,之前m的数据指针(如果有的话)所指向的数据将被释放。同时,它们现在共享的数据区域的引用计数器将增加。最后,将更新m的成员(例如行,列和标志),以准确描述m中数据现在指向的数据。 这一切使得这些行为变得很方便,其中数组可以彼此分配,并且在幕后自动执行此操作以提供正确的结果。
4.cv::Mat对象的构造函数
下表是cv::Mat类可用的构造函数的完整列表。当然大部分情况下,我们只会用到下表中的某几个。
操作 | 例子 |
---|---|
默认构造函数 | cv::Mat; |
按类型的2维矩阵构造函数 | cv::Mat( int rows, int cols, int type ); |
带初始化值的按类型的2维矩阵构造函数 | cv::Mat(int rows, int cols, int type,const Scalar& s); |
具有预先存在的数据的2维矩阵构造函数 | cv::Mat(int rows, int cols, int type,void* data, size_t step=AUTO_STEP); |
按类型的2维数组构造函数(大小以sz为单位) | cv::Mat( cv::Size sz, int type ); |
带初始化值的按类型的2维矩阵构造函数(大小以sz为单位) | cv::Mat(cv::Size sz,int type, const Scalar& s); |
具有预先存在的数据的2维矩阵构造函数(大小以sz为单位) | cv::Mat( cv::Size sz, int type,void* data, size_t step=AUTO_STEP) |
按类型的多维矩阵构造函数 | cv::Mat(int ndims, const int* sizes,int type); |
带初始化值的按类型的多维矩阵构造函数 | cv::Mat(int ndims, const int* sizes,int type, const Scalar& s); |
具有预先存在的数据的多维矩阵构造函数 | cv::Mat(int ndims, const int* sizes,int type, void* data,size_t step=AUTO_STEP); |
上表列出了cv::Mat类对象的基本构造函数,下表将列出拷贝构成函数,即如何从一个已知数组创建数组。除了基本的复制构造函数之外,还有三种方法用于从现有数组的子区域构造数组,以及一种使用某种矩阵表达式的结果初始化新矩阵的构造函数。
操作 | 例子 |
---|---|
拷贝构造函数 | cv::Mat( const Mat& mat ); |
仅拷贝指定行列的构造函数 | cv::Mat(const Mat& mat,const cv::Range&rows,const cv::Range&cols); |
仅拷贝指定区域的构造函数 | cv::Mat(const Mat& mat,const cv::Rect& roi); |
广义感兴趣区域复制构造函数,它使用范围数组从n维数组中进行选择 | cv::Mat(const Mat& mat,const cv::Range*ranges); |
复制构造函数,用其他矩阵的代数表达式的结果初始化m | cv::Mat( const cv::MatExpr&expr ); |
上述拷贝构造函数中,利用子区域(也称为“感兴趣区域”)构造函数有三种形式:一种采用一系列行和一系列列(这仅适用于二维矩阵),一种使用cv :: Rect指定矩形子区域(也仅适用于二维矩阵),最后一个采用范围数组。 在后一种情况下,指针参数范围指向的有效范围的数量必须等于数组mat的维数。 如果mat是ndim大于2的多维数组,则必须使用第三个选项。
5.与旧版本之间的兼容问题
如果您正在维护包含C样式数据结构的opencv2.1版本之前的代码,您可能希望从现有的CvMat或IplImage结构创建新的C ++样式的cv :: Mat结构。 在这种情况下,我们有两个选择:
- 在现有的数据上构建一个标记头m(通过将copyData设置为false)
- 重新分配内存,并将老数据复制到m中(通过将copyData设置为true)
cv::Mat(
const CvMat* old,
bool copyData=false
);
cv::Mat(
const IplImage*
old,
bool copyData=false
);
6.模板构造函数
模板构造函数之所以被称之为模板构造函数,不是因为它们从cv::Mat构造出了一个模板类,而是因为它们从本身就是模板的东西(cv::Vec<>或cv::Matx<>),来创建cv::Mat类实例。所以模板构造函数能够用任意cv::Vec<>,cv::Matx<>类的实例来创建cv::Mat数组。或者将任意类型的STL库中的vector<>实例来构建相同类型的cv::Mat数组。
操作 | 例子 |
---|---|
从相同类型的cv :: Vec构造类型为T和大小为n的一维数组 | cv::Mat(const cv::Vec<T,n>&vec,bool copyData=true); |
从相同类型的cv :: Matx构造类型为T且大小为m×n的二维数组 | cv::Mat(const cv::Matx<T,m,n>&vec,bool copyData=true); |
从包含相同类型元素的STL向量构造类型为T的一维数组 | cv::Mat(const std::vector&vec,bool copyData=true); |
7.Mat类的静态成员函数
cv::Mat类提供一些能够创建常用类的静态成员函数。
//创建一个大小为rows×cols的cv :: Mat,其中所有的值为类型为type(CV_32F等)的零
cv::Mat::zeros( rows, cols, type );
//创建一个大小为rows×cols的cv :: Mat,其中所有的值为类型为type(CV_32F等)的一
cv::Mat::ones( rows, cols, type);
//单位矩阵
cv::Mat::eye( rows, cols, type);