OpenCv2 Mat类详解
1、Mat构造函数
Mat::Mat
C++: Mat::Mat()
C++: Mat::Mat(int rows, int cols, int type)
C++: Mat::Mat(Size size, int type)
C++: Mat::Mat(int rows, int cols, int type, const Scalar& s)
2.1. Basic Structures 25The OpenCV Reference Manual, Release 2.4.9.0
C++: Mat::Mat(Size size, int type, const Scalar& s)
C++: Mat::Mat(const Mat& m)
C++: Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
C++: Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
C++: Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all() )
C++: Mat::Mat(const Mat& m, const Rect& roi)
C++: Mat::Mat(const CvMat* m, bool copyData=false)
C++: Mat::Mat(const IplImage* img, bool copyData=false)
C++: template<typename T, int n> explicit Mat::Mat(const Vec<T, n>& vec, bool copyData=true)
C++: template<typename T, int m, int n> explicit Mat::Mat(const Matx<T, m, n>& vec, bool copyData=true)
C++: template<typename T> explicit Mat::Mat(const vector<T>& vec, bool copyData=false)
C++: Mat::Mat(int ndims, const int* sizes, int type)
C++: Mat::Mat(int ndims, const int* sizes, int type, const Scalar& s)
C++: Mat::Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0)
C++: Mat::Mat(const Mat& m, const Range* ranges)
部分参数:
-
ndims - 矩阵维数
-
rows,cols - 矩阵的行数和列数
-
size - 二维矩阵的尺寸 size: Size(cols, rows),通常描述矩阵尺寸为行、列,构造反过来。
- sizes - 描述n维矩阵尺寸的整形数组指针,int a= [100,100,100], sizes = a;
- type – 用CV_8UC1, ..., CV_64FC4 创建1~4维的矩阵,或者CV_8UC(n), ..., CV_64FC(n)的n维矩阵。
- s – 可选参数,用Scalar给矩阵每个元素赋初值。也可以在构造后 用“=”赋初值。
- data – 用户数据指针,可以用自定义的数组给矩阵赋值。
- step - 矩阵一行占用的字节数,由step = cols*elemSize()得到,elemSize()是一个元素占用字节数。
- m - 被引用的矩阵,以获取m的子矩阵。如 Mat a=Mat(m,Range(1,10),Range(5,8));
- vec – 元素为列向量的容器,以将vec转化为矩阵,vec行数为矩阵的行数,vec列数为矩阵的维数。
- copyData – bool变量,true时深复制,false时引用即浅复制。
2、成员函数
1) Mat & Mat::operator =
Mat A = (Mat_<float>(2, 2) << 1, 2, 3, 4);
Mat B = (Mat_<float>(1, 4) << 3, 2, 1, 4);
Mat C = A;
C = B;
C = A + B;
2) Mat Mat::row(int y) const Mat::col(int x) const 返回基于0的某一列或某一行为新的矩阵,深复制
Mat A;
...
A.row(i) = A.row(j); // not works
A.row(i) = A.row(j) + 0; // works, but looks a bit obscure.
A.row(j).copyTo(A.row(i));
3) Mat Mat::rowRange(int startrow, int endrow) const
Mat Mat::rowRange(const Range& r) const
子矩阵,连续某些行或者连续某些列构成的矩阵, 深复制。(类似还有colRange()方法)
Mat A = (Mat_<float>(3, 3) << 0, 1, 2, 3,4,5,6,7,8);
Mat B = (Mat_<float>(3,3) << 1, 2, 3, 4,5,6,7,8,9);
Mat c = A(Range(0, 2), Range::all());
// 行中使用Range(n1,n2),就是选择n1到n2-1行,同理
// 列中使用Range(n1,n2),就是选择n1到n2-1列;
// all()是选择对为位置所有的行或者列
4) Mat Mat::diag(int d=0 ) const 取矩阵对角线元素为列向量
Matx44f m( 1, 2, 3, 4,
5, 6, 7, 8,
9,10,11,12,
13,14,15,16);
Mat dia = Mat(m).diag(); //[1,6,11,16]'
dia = Mat(m).diag(1); //[2,7,12]'
dia = Mat(m).diag(1); //[3,8]'
dia = Mat(m).diag(-1); //[5,10,15]'
5) Mat::clone , Mat::copyTo 深复制; copyTo第二个参数可选mask
6) void Mat::convertTo(OutputArray m, int rtype, double alpha=1, double beta=0 ) const
两种方式, (1) 改变数据类型 (2)数据类型和数值两个变换
m(x; y) = saturate_cast < rType > (α(*this)(x; y) + β) ; saturate_cast<uchay>,小于0为0,大于255为255
例子和结果如下
int data[] = { 1, 2, 3, 4, 5, 6 };
Mat A(2, 3, CV_32SC1, data); //整形
Mat B1,B2;
A.convertTo(B1, CV_8UC1);
A.convertTo(B2, CV_32FC1, 0.1, 1);
coutMat("A = ", A);
coutMat("convertTo(B, CV_8UC1) = ", B1); //uchar
coutMat("convertTo(B, CV_32FC1, 0.1, 1) = ", B2); //float
7) void Mat::resize(size_t sz), void Mat::resize(size_t sz, const Scalar& s)
改变矩阵行数,当新的行数小于原矩阵行数,那么会释放新的行数后面的元素;大于时,可以指定后面新增的行数的元素初值为s。下面给出一个例子,和对应两次resize的结果图
Mat A = (Mat_<float>(3, 3) << 0, 1, 2, 3, 4, 5, 6, 7, 8);
A.resize(4, 9); //改变行数为4行,新增1行,并指定新的一行元素全为9
A.resize(2); //由原来4行,变为2行
(第一二张放反了,囧... 调试工具查看Mat数据参考OpenCv2 学习笔记(2) Mat图像显示)
8) Mat& Mat::setTo(InputArray value, InputArray mask=noArray() )
矩阵(可选mask区域)赋值,如
int data[] = { 1, 2, 3, 4, 5, 6 };
Mat A(2, 3, CV_32SC1, data);
Mat mask = (Mat_<uchar>(2, 3) << 0, 0, 1, 1, 0, 0);
coutMat("A =", A);
Mat B = A.setTo(Scalar(2), mask);
coutMat("A' =", A);
coutMat("B =", B);
Mat C = A.setTo(Scalar(2), Matx<uchar, 2, 3>(0, 0, 0, 0, 1, 1)) + 0;
coutMat("A'' =", A);
coutMat("B' =", B);
coutMat("C =", C);
A.setTo(Scalar(0), Matx<uchar, 2, 3>(0, 0, 0, 0, 1, 1));
coutMat("C' =", C);
上例中Matx<uchar, 2, 3>(0, 0, 0, 0, 1, 1)是构建一个自定义类型的小矩阵,mask必须为uchar类型。注意setTo()函数的返回为引用。结果为:
9) Mat Mat::reshape(int cn, int rows=0) const
改变二维矩阵的size或/和通道数,浅复制(但是返回值不是引用)。变换后,必须保证 rows*cols*channels()保持相等。例子和结果
vector<Point2f> vec; // vec包含3个浮点二维点。(可以理解为2通道的3个元素)
int i = 0;
while (i++ < 3)
vec.push_back(Point2f(i, i+1)); // 压入的元素和vec类型一致
//vec.pop_back(); // 移除最后一个元素
Mat matpoint = Mat(vec); // 操作符 vec转成Mat 结构, 二维矩阵,3行1列
//Mat res = matpoint.reshape(1,3); // *浅复制* 和matpoint一样。
Mat res = matpoint.reshape(1,3) + 0; // 通过运算,改变通道数。二维矩阵,3行1列 =》 一维矩阵,3行2列
通过上图,发现2通道的matpoint矩阵的3个元素,都是按照列向量摆放,正好反映了matx、vet、scalar是以列向量访问和复制矩阵。
10) Mat::t , Mat::inv, Mat::mul, Mat::cross, Mat::dot
分别为:转置,矩阵的逆(LU、CHOLESKY、SVD三种解法),数乘(2个矩阵对应位置元素相乘或一个矩阵和一个标量相乘),叉乘(含有三个元素的2个向量运算),点乘(内积,2个向量(一维矩阵)运算)。
11) Mat::zeros,Mat::ones,Mat::eye 0矩阵,1矩阵, 单位(主对角线为1)矩阵
12) Mat::create
C++: void Mat::create(int rows, int cols, int type)
C++: void Mat::create(Size size, int type)
C++: void Mat::create(int ndims, const int* sizes, int type)
若需要,则为矩阵分配内存,字节数为total()*elemSize(),即 元素个数*一个元素占用字节数。OpenCv中绝大多数函数(reshape()就不是)有返回值且为Mat 时,都会自动调用该create()函数。
13) Mat::addref, Mat::release
前者用于给矩阵的内部成员变量refcount计数,后者用于释放矩阵。在Mat里面通常不用关心矩阵的内存分配和释放,矩阵会在有需要的时候自行分配,同样由于矩阵的引用计数机制使得在refcount为0时会自动释放矩阵内存。
14) void Mat::locateROI(Size& wholeSize, Point& ofs) const
确定子矩阵在被引用矩阵的顶点位置和被引用矩阵的size。
Mat a = Mat(100, 100, CV_32F);
Size sz; Point ofs;
Mat b = a(Range(20, 51), Range(50, 92)); //31行42列 b.size = [42,31] -> a ~ [20,51; 50,92]
b.locateROI(sz, ofs); // b/ [100x100], [50,20]
Mat c = b(Range(5, 17), Range(10, 21)); //12行11列 b.size = [11,12] -> a ~ [25,42; 60,81]
c.locateROI(sz, ofs); // c/ [100x100], [60,25]
15) size_t Mat::total() const
矩阵元素个数 total = cols*rows
16) bool Mat::isContinuous() const
确定矩阵元素在内存中是否为连续存储。显然,只有一个元素的矩阵、只有一行的矩阵的所有元素在内存中都是连续存储的。通过create()创建的矩阵的元素也总是连续的。但是由矩阵部分元素得到的子矩阵,如某一列col()、diag()等肯定是不连续的。通常矩阵为连续时,可以将这个矩阵的数据存储区域看成一个具有大量元素的行向量(访问时可以变得简单)。
17) size_t Mat::elemSize() const , size_t Mat::elemSize1()
elemSize() 矩阵一个元素占用字节数,elemSize1() 矩阵元素的一个通道占用的字节数。如矩阵数据类型为CV_8UC3时,elemSize()=1byte*3,elemSize1() =8bit = 1byte;矩阵数据类型为CV_16SC2时,elemSize()=2byte*2,elemSize1() =16bit = 8 byte.
18) int Mat::channels() const
矩阵通道数。如类型CV16SC2,通道数2(即类型中字母C后的数值)。
19) int Mat::depth() const
矩阵数据的类型,宏定义,其取值范围为0~7
// Mat::depth()
#define CV_8U 0 // uchar
#define CV_8S 1 // char
#define CV_16U 2 // ushort
#define CV_16S 3 // short
#define CV_32S 4 // int
#define CV_32F 5 // float
#define CV_64F 6 // double
#define CV_USRTYPE1 7
20) int Mat::type() const
矩阵的类型,描述矩阵的数据类型和通道数目。例如矩阵类型为CV_32FC2时,表示该矩阵的数据类型为32位浮点数、通道数为2。其返回值为下述宏定义
#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT))
其中CV_MAT_DEPTH(depth)是另外一个宏定义,直接可以当做depth(),cn的通道数为channels(),CV_CN_SHIFT = 3。因此,type() = depth() + (channels() - 1) << 3。通过代码打印出下面常用的几种类型
21) size_t Mat::step1(int i=0 ) const,Size Mat::size() const
对于二维矩阵,size()返回Size(cols, rows),step1(0)为一行元素的字节数,step1(1)为一个元素的字节数elemSize()。
对于多维,还有一个step的成员变量,是一个MStep结构体,step[i]是第i维占用的字节数。对于二维矩阵,step[0]是一行占用的字节数,step[1]是一个元素占用的字节数。
有关step,size,step1,elemSize,elemSize1的详细介绍,可以点击参考网站。
在OpenCv1.0中对CvMat和IplImage的元素遍历,会用到一行的字节数找到某个元素的指针。然而,这些个参数其实在OpenCv中使用的不多,用成员函数Mat::at可以方便的遍历,在debug下效率和指针有区别,但是在release下效率相同,再后续学习笔记中会详细介绍。
22) Mat::ptr, Mat::data, Mat::empty
两个数据区指针,data和ptr<_Tp>(0)都指向数据区的首地址,默认都是uchar *类型。ptr<_Tp>(i)指向第i行元素的首地址,那么第i行第j列元素的地址为addrptr<_Tp>(i)[j]。若知道每行的字节数和单个元素字节数,可以通过data得到第i行第j列元素的地址addr(Mi0,…,iM.dims-1) = M.data + M:step[0] * i0 + M.step[1] * i1 + … + M.step[M.dims - 1] * iM.dims-1, 对于二维矩阵 addr(Mi,j) = M.data + M.step[0] * i + M.step[1] * j。
bool Mat::empty()当矩阵没有元素即Mat::total() ==0时为true。当矩阵仅创建信息头,没有分配内存时,Mat::data == NULL,那么Mat::total() ==0,因而 empty() == true。但是Mat::total() ==0 并不能说明Mat::data == NULL,是因为矩阵分配内存后使用函数pop_back和resize后元素没有了,但是数据指针还在但不为NULL。
23) Mat::push_back, Mat::pop_back
这两个方法使得Mat有了STL容器的功能,可以在矩阵最后一行压入或者弹出一行元素。当矩阵为空,可以压入任意一个矩阵,当矩阵不为空时只能压入行元素个数相同的矩阵,并且type相同。
那么,当要在矩阵最右侧加入一列,要怎么做呢?既然只能按照行添加,那么仅需将转置,加入某一列矩阵的转置或者行矩阵,最后将结果矩阵再转置一次,就得到结果了(可以用函数hconcat、vconcat拼接函数实现)。例子和结果如下
Mat A = (Mat_<uchar>(3, 2) << 0, 1, 2, 3, 4, 5); //3行2列
Mat B = (Mat_<uchar>(3, 1) << 7, 8, 9); //3行1列 列向量
coutMat("A = ", A); coutMat("B = ", B);
Mat C = A.t();
Mat D = B.t();
C.push_back(D);
Mat E = C.t();
coutMat("E = ", E);
24) Mat::at
返回指定元素的一个引用,有以下几种方法:
C++: template<typename T> T& Mat::at(int i) const
C++: template<typename T> const T& Mat::at(int i) const
C++: template<typename T> T& Mat::at(int i, int j)
C++: template<typename T> const T& Mat::at(int i, int j) const
C++: template<typename T> T& Mat::at(Point pt)
C++: template<typename T> const T& Mat::at(Point pt) const
C++: template<typename T> T& Mat::at(int i, int j, int k)
C++: template<typename T> const T& Mat::at(int i, int j, int k) const
C++: template<typename T> T& Mat::at(const int* idx)
C++: template<typename T> const T& Mat::at(const int* idx) const
对于size为1*n或者n*1的矩阵,也就是单行或者单列矩阵,可以直接用一个(index)取代(i,j)去引用某个指定元素,如用A.at<float>(k+4)代替A.at<float>(0,k+4) ,B.at<int>(2*i+1)代替B.at<int>(2*i+1,0)等。
下面给出一个Hilbert矩阵初始化的例子
Mat H(100, 100, CV_64F);
for(int i = 0; i < H.rows; i++)
for(int j = 0; j < H.cols; j++)
H.at<double>(i,j)=1./(i+j+1);
附:类Mat_
由Mmat派生的一个模板类,在定义时给出数据类型,用Mat::at访问时可以不用给出类型直接通过index访问。下例可以和上面的24)Mat::at进行比较:
Mat_<double> M(20,20);
for(int i = 0; i < M.rows; i++)
for(int j = 0; j < M.cols; j++)
M(i,j) = 1./(i+j+1);
对于多维Mat_矩阵,可以用Vec传递参数(同样适用于Mat),如
Mat_<Vec3b> img(240, 320, Vec3b(0,255,0));
for(int i = 0; i < 100; i++)
img(i,i)=Vec3b(255,255,255);
for(int i = 0; i < img.rows; i++)
for(int j = 0; j < img.cols; j++)
img(i,j)[2] ^= (uchar)(i ^ j);