一、前言
OpenCV的API库在最开始时一直是基于C接口构建的,使用C语言结构体指针IplImage*存储图像,需要繁琐的内存管理,如果创建了IplImage指针,使用完成后不release掉的话就会造成内存泄漏,使用起来极为不便!辛运的是,OpenCV2.0版本中引入了新的C++接口,通过类进行自动内存管理,并定义了一种新的数据类型Mat来存储图像矩阵。
二、概述
Mat是一个容器类,支持C++中一般容器对元素的操作,同时作为一种矩阵数据存储类,由两个数据部分构成:1、矩阵头:包含矩阵尺寸、存储方式、存储地址等信息;2、指向存储所有像素值的矩阵的指针。矩阵头的尺寸是常数,但尺寸本身的尺寸会因图像的不同而不同。
通常创建和传递矩阵时会造成很大开销,在进行较大矩阵复制时,OpenCV引入了计数机制,让每个Mat对象都有自己的信息头,但共享同一个矩阵,者通过让矩阵指针指向同一地址实现,所以拷贝构造函数只复制信息头和矩阵指针,而不复制矩阵,避免了大量的空间时间开销。
然而某些时候你还是会复制矩阵本身,(不只是信息头和矩阵指针),这时可以使用函数clone()和copyTo()函数。
构造Mat矩阵时:
- 使用赋值运算符和拷贝构造函数只复制信息头,改变任何一个矩阵都会影响到其他矩阵。
- 使用函数Clone()和copyTo()来复制一幅图像的矩阵,新创建的矩阵和原矩阵相互独立,改变其中一个矩阵不会影响到其他矩阵。
OpenCV中Mat容器类的定义有很多行,下面列出来一些关键属性如下所示:
class CV_EXPORTS Mat
{
public:
//********Mat类的构造函数********
Mat(); //图像容器类Mat的默认构造函数
//......其他构造函数定义并初始化,在此省略 【下面讨论】
~Mat(); //Mat的析构函数
...
//********图像矩阵的属性********
int flags; //图像矩阵是否连续
int dims; //矩阵的维数,取值应该大于或等于2
int rows,cols; //矩阵的行列数
uchar* data; //指向数据的指针
int* refcount; //指向引用计数的指针,如果数据由用户分配则为NULL
//......
//......其他的很多函数和数据结构
};
三、Mat对象创建
1、使用Mat( )构造函数
Mat::Mat() //无参数构造Mat方法
Mat::Mat(int rows, int cols, int type) //【1】创建行数为rows,列数为cols,类型为type的图像矩阵
Mat::Mat(Size size, int type) //【2】创建大小为size,类型为type的图像
Mat::Mat(int rows, int cols, int type, const Scalar& s) //【3】创建行数为rows,列数为cols,类型为type的图像,并将所有元素初始化为s
Mat::Mat(Size size, int type, const Scalar& s) //【4】创建大小为size,类型为type,初始元素为s
Mat::Mat(const Mat& m) //【5】将m赋值给新创建的对象:数据共享
Mat::Mat(int rows, int cols, int type, void* data, size_t step = AUTO_STEP)
//【6】创建行数为rows,列数为cols,类型为type的图像,构造函数不创建图像数据所需内存而是直接使用data所指内存图像的步长由step指定
Mat::Mat(Size size, int type, void* data, size_t step = AUTO_STEP)
//【7】创建大小为size,类型为type的图像,构造函数不创建图像数据所需内存而是直接使用data所指内存图像的步长由step指定
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
//【8】创建新的图像为m数据的一部分,其具体的范围由rowRange和colRange指定:数据共享
Mat::Mat(const Mat& m, const Rect& roi) //【9】创建新的矩阵为m的一部分,具体的范围由roi指定:数据共享
在构造函数中很多地方都用到Type,Type可以是CV_8UC1、CV_16UC3、CV_64FC4等等,这些写法都用具体的定义,如下:
C
V
_
[
位
数
]
[
带
符
号
与
否
]
[
数
据
类
型
]
C
[
通
道
数
]
CV\_~[位数][带符号与否][数据类型]C[通道数]
CV_ [位数][带符号与否][数据类型]C[通道数]
比如,CV_8UC3表示8位3通道unsigned整型。
Mat M(5, 5, CV_8UC3); //创建5行5列,8位3通道无符号整型的图像矩阵
数据共享:指不同Mat图像矩阵矩阵指针指向同一个矩阵地址,当改变一个矩阵时其他矩阵也会跟着改变。
2、使用Create( )函数创建Mat类
Mat M1.create(4,4,CV_8UC1); //创建一个尺寸为4×4,type为8UC1的图像矩阵
【注意】使用create( )函数无法初始化Mat类,只是在改变尺寸时重新为矩阵数据开辟内存而已。也就是说,如果create()函数指定的参数与图像之前的尺寸相同,则不进行实质的内存申请操作,如果尺寸不同,则减少原始数据内存的索引并重新申请内存。
3、使用MATLAB风格创建Mat类
OpenCV也可以使用Matlab的风格创建函数如:zeros(),ones()和eyes(),在使用这些函数时只需要指定图像的大小和类型。
Mat M1=Mat::zeros(3,3,CV_8UC1); //创建尺寸为3×3,类型为8UC1的全0矩阵
Mat M2=Mat::ones(4,4,CV_32FC3); //创建尺寸为4×4,类型为8UC3的全1矩阵
Mat M3=Mat::eye(5,5,CV_64FC1); //创建尺寸为5×5,类型为64FC1的单位矩阵
4、使用子类Mat_创建Mat矩阵
OpenCV定义了一个Mat的模板子类为Mat_,使用逗号分隔式初始化小型矩阵,如:
Mat M=(Mat_<double>(3,3) << 0,-1,0,
-1,5,-1,
0,-1,0);
5、使用clone( )或copyTo方法创建Mat矩阵
copyTo( )是深拷贝,但是否申请新的内存空间,取决于dst矩阵头中的大小信息是否与src一至,若一致则只深拷贝并不申请新的空间,否则先申请空间后再进行拷贝.
clone( )是完全的深拷贝,在内存中申请新的空间。
Mat A = Mat::ones(4,5,CV_32F);
Mat B = A.clone() //clone 是完全的深拷贝,在内存中申请新的空间,与A独立
Mat C;
A.copyTo(C) //此处的C矩阵大小与A大小不一致,则申请新的内存空间,并完成拷贝,等同于clone()
Mat D = A.col(1);
A.col(0).copyTo(D) //此处D矩阵大小与A不一致,因此不会申请空间,而是直接进行拷贝,相当于把A的第1列赋值给D
关于clone()、copyTo()、和”=”(重赋值运算符重载)三种实现矩阵赋值的方式的比较:
~~~~~~
”=”是使用重载的方式将矩阵值赋值给新的矩阵,而这种方式下,被赋值的矩阵和赋值矩阵之间共享数据,改变任何一个矩阵的值会影响到另外一个矩阵。而clone()和copyTo()两种方法在赋值后,两个矩阵的存储空间是独立的,不存在共享数据的情况。
6、IplImage指针转Mat矩阵
IPLImage指针创建信息头可实现IPLImage*到Mat矩阵转换,代码如下:
IplImage* img=cvLoadImage("1.jpg",1);
Mat mtx(img); //IplImage* -> Mat
四、C++实现Mat图像矩阵创建
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat M1(3,3,CV_8UC3,Scalar(0, 0, 255));
cout << "M1 = " << endl << " " << M1 << endl;
Mat M2(Size(3, 2), CV_8UC3, Scalar(1,2,3));
cout << "M2 = " << endl << " " << M2 << endl;
Mat M3(M2);
cout << "M3 = " << endl << " " << M3 << endl;
Mat M4(M2, Range(1,2), Range(1,2));
cout << "M4 = " << endl << " " << M4 << endl;
waitKey(0);
return 0;
}
参考书籍、Blog:
【1】《OpenCV3编程入门》
【2】opencv学习(一)之Mat类