Mat是OpenCV中最重要的数据结构,不仅能存放各种数据,而且提供各种数据操作接口,几乎包含了所有上层算法或程序对数据操作的需求。
Mat的功能需求:
1. 存放数据及数据的属性,包括大小,维度,数据类型及step
2. 初始化,构造、析构
3. 数据之间的转换
4. 基本元素数据的获取和修改
5. 数据的复制、部分引用、分割、融合
6. 单个或多个矩阵的基本数学操作,要能满足图像操作里对数据块的计算需求
数学、逻辑、统计及傅里叶变换
Mat的声明在core.hpp中,成员函数实现在mat.hpp中
Mat的数据成员有哪些?
常用属性:
(row, col,size),(step,dim), data
不常用内部属性:
refcount,可选的custom allocator, 用于POI的datastart, dataend, datalimit
Mat 四大成员函数(构造、析构、复制、赋值)
从简单的开始:
析构函数:
release(); --> refcount为1时, deallocate(); --> fastFree或allocator->deallocate(refcount, datastart, data)
Mat实际类似于一个引用计数智能指针,封装着数据。
复制构造函数
Mat(const Mat& m)
并不是复制数据,而仅仅是增加引用计数,图像数据的改变,会影响其他的引用Mat
赋值函数
release自身,然后增加对参数矩阵的引用计数,dims <= 2 && m.dims <= 2时并不复制数据。引用计数智能指针的方式,Mat就不用太在意内存泄露或野指针的问题。
构造
创建新的Mat头部信息,或者创建新的数据区,或者与其他数据区想关联。
1. 默认构造函数mat()
所有数据成员都为0
2. 创建新的数据区,包含大小、维度和类型的构造函数
都会调用create函数,而create函数会根据参数初始化mat头数据,并在setsize中计算出所需要开辟的字节数。
然后,mat调用custom allocator或fastMalloc分配内存。如下fastMalloc的分配方式:
size_t total = alignSize(step.p[0]*size.p[0], (int)sizeof(*refcount));
data = datastart = (uchar*)fastMalloc(total + (int)sizeof(*refcount));
refcount = (int*)(data + total);
*refcount = 1;
create是一种释放旧数据,重新分配内存的方式。
初始化指定值:
Mat A33(3, 3, CV 32F, Scalar(5));
Mat B33(3, 3, CV 32F); B33 = Scalar(5);
Mat C33 = Mat::ones(3, 3, CV 32F)*5.;
Mat D33 = Mat::zeros(3, 3, CV 32F) + 5.;
类似matlab的初始化方式,static成员函数:
static MatExpr zeros(int rows, int cols, int type);
static MatExpr ones(int ndims, const int* sz, int type);
static MatExpr eye(Size size, int type);
3. 不创建数据区,引用其他数据
由CvMat、CvMatND、IplImage转化来,仅仅创建mat头信息,不创建新的数据。或者从数组中转化来
float B22data[] = {cos(a), -sin(a), sin(a), cos(a)};
Mat B22 = Mat(2, 2, CV 32F, B22data);
vector<Point> iptvec(10);
Mat iP(iptvec); // iP { 10x1 CV 32SC2 matrix
4. 深度copy数据,创建mat
clone()是deep copy 创建mat的函数。
Mat newC2 = cvarrToMat(oldC0).clone();
vector<Point2f> ptvec = Mat <Point2f>(iP);
Size、Point、Mat之间的关系
Size是Mat属性的一部分。
而Point也应该是Mat的基本元素的位置信息属性,但point并不是Mat的之间属性。所以访问mat的基本元素,必须应用at,ptr函数或Mat_::iterator,访问某个point的值。而不能直接使用point。如
A33.at<float>(i,j)
Vec3b* prevRow = image.ptr<Vec3b>(y-1);
Mat <Vec3b>::iterator it = image.begin<Vec3b>();
Mat的复制
clone() --> copyTo() --> convertTo()都是深度copy数据,实现方式都是一样的。
Part Access部分引用
reshape(),不创建新的mat,不改变数据区,仅仅改变mat头信息。
其他range的方式获取部分引用,用于ROI是非常好用的,这部分在另外一篇文章中有介绍
Mat其他接口:
1. 转换为CvMat、CvMatND、IplImage、vector<_Tp>、Vec<_Tp, n>、Matx<_Tp, m, n>类型,显示定义类型转换函数,用于隐式类型转换调用。方便类型间转换。
//! converts header to CvMat; no data is copied
operator CvMat() const;
//! converts header to CvMatND; no data is copied
operator CvMatND() const;
//! converts header to IplImage; no data is copied
operator IplImage() const;
template<typename _Tp, int m, int n> operator Matx<_Tp, m, n>() const;
2. 访问自身属性的接口:
虽然能直接访问Mat里的数据信息,但提供封装的接口是类型设计的要素之一。const成员函数,inline实现,封装频繁调用且又简要的操作。
isContinuous()、isSubmatrix()、elemSize()、step1(int i=0)
常用接口
type()、depth()、channels()、empty()、total()
3. 更改数据
resize(), push_back(),pop_back(),reserve()
扩展时,当Mat大小不足时,重新分配内存。当缩减时,只需修改mat头信息size和dataend就行了。
4. 矩阵运算、线性代数里常用操作:
转置、求逆、矩阵相乘
t()、inv()、mul()
不重载其他操作符运算,effective c++中item23,item24应用非成员函数non-member的方式更合适。
Matop.cpp的操作,基本上都以非成员函数的方式对mat操作,mat的运算
包括数学
逻辑
统计
代数+SVD
傅里叶变换
与Mat关系紧密的几个类MatOp、MatExpr、Mat_.
1. 与Mat相关的重载操作符的函数设计,参数都是输入,返回值是MatExpr类
2. 与Mat相关的显示命名函数设计,参数列表是输入Mat+ 输出Mat+其他输入参数,返回值基本是void类型。而不是返回值是MatExpr的形式
这样设计的好处:
1. 减少开销
--------------------------
在imshow视频或图像时,需要刷新窗口。waitKey()函数不仅仅是等待处理键盘消息,而且是刷新窗口的唯一接口。
http://opencv.willowgarage.com/documentation/python/highgui_user_interface.html#waitkey