OpenCv2 学习笔记(5) Mat类详解

Mat的内容非常多,大致可以分类 构造函数、成员函数2个大板块。

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
image
 
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图像显示

 
 
image

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()函数的返回为引用。结果为:
image
 
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列

image image

通过上图,发现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。通过代码打印出下面常用的几种类型

image

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);

image

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);

猜你喜欢

转载自blog.csdn.net/wanggao_1990/article/details/53264753