1.直接赋值法
Mat 矩阵比较小时,可以使用直接赋值法。直接赋值法就是利用 Mat_。Mat_也是一个类,该类是对 Mat 类的一个包装,其定义如下:
template<typename_Tp>classMat_:publicMat{public://只定义了几个方法//没有定义新的属性};如果要让每个像素取不同的值,可以直接用 Mat_赋值,代码如下:
Mat r8=(Mat_<double>(3,3)<<1,2,3,4,5,6,7,8,9);cout<<"r8 total matrix:\n"<<r1<<endl;输出结果如下:
[1,2,3;4,5,6;7,8,9]2.数组法
这个方法就是使用数组或指针传入 Mat 构造函数。这个构造函数是:
Mat(introws,intcols,inttype,void*data,size_t step=AUTO_STEP)创建行数为 rows、列数为 cols(像素的列数,对于多通道,一列像素可能对应多列矩阵元素)、类型为 type 的图像,并且用数组 data 初始化元素值。
Mat(Size size,inttype,void*data,size_t step=AUTO_STEP)创建大小为 size、类型为 type 的图像,并且用数组 data 初始化元素值。data 就是要传入数据的指针,比如:
inta[2][3]={1,2,3,4,5,6};//定义 2 行 3 列二维数组Matm1(2,3,CV_32S,a);//float 对应的是 CV_32F,double 对应的是 CV_64F,若不带通道数,则默认通道数是 1cout<<m1<<endl;输出结果如下:
[1,2,3;4,5,6]数组适合操作数据量大的情况,比如可以通过 for 循环来构造二维数组,然后给 Mat 赋值。
3.create 函数法
成员函数 create 可以分配新的矩阵数据,即重新创建矩阵元素数据,函数声明如下:
voidcreate(introws,intcols,inttype);其中 rows 表示要创建的矩阵行数;cols 表示要创建的矩阵列数;type 表示图像矩阵元素的类型,比如 CV_8UC3。以下代码实现 4×4 的二维单通道矩阵,矩阵中的数据为乱值:
Mat M3;M3.create(4,4,CV_8UC1);std::cout<<"M3 = "<<std::endl<<M3<<std::endl;输出结果如下:
[205,205,205,205;205,205,205,205;205,205,205,205;205,205,205,205]4. 定义特殊矩阵
为了方便起见,Mat 类提供了几个静态成员函数用于快速定义几种特殊矩阵,比如全 0 矩阵、全 1 矩阵和对角线为 1 的对角矩阵等。下面介绍这几个静态成员函数。
(1)定义全 0 矩阵,函数声明如下:
staticMatExprzeros(introws,intcols,inttype);staticMatExprzeros(Size size,inttype);staticMatExprzeros(intndims,constint*sz,inttype);比如以下代码定义了 h 行 w 列的全 0 矩阵:
cv::Mat mz=cv::Mat::zeros(cv::Size(w,h),CV_8UC1);Mat tmpdata=Mat::zeros(h,w,CV_8UC1);(2)定义第一通道全为 1 的矩阵,函数声明如下:
staticMatExprones(introws,intcols,inttype);staticMatExprones(Size size,inttype);staticMatExprones(intndims,constint*sz,inttype);比如以下代码定义了 h 行 w 列的全 1 矩阵:
cv::Mat mo=cv::Mat::ones(cv::Size(w,h),CV_8UC1);Mat tmpdata=Mat::ones(h,w,CV_8UC1);值得注意的是,如果是单通道,ones 能把全矩阵置为 1。如果是多通道矩阵,ones 只会把每个像素的第一个通道元素置为 1,其余两个通道则置为 0,比如:
Mat mymat=cv::Mat::ones(cv::Size(3,2),CV_8UC3);//定义 2 行 3 列 3 通道矩阵,实际矩阵共有 9 列cout<<mymat<<endl;输出结果是:
[1,0,0,1,0,0,1,0,0;1,0,0,1,0,0,1,0,0]另外,也可以用构造函数来达到 ones 效果,比如以下两句效果一样:
Mat m=Mat::ones(2,2,CV_8UC3);Mat m=Mat(2,2,CV_8UC3,1);//利用构造函数(3)对角线为 1 的矩阵,函数声明如下:
staticMatExpreye(introws,intcols,inttype);staticMatExpreye(Size size,inttype);比如以下代码定义了 h 行 w 列的对角线为 1 的对角矩阵:
cv::Mat me=cv::Mat::eye(cv::Size(w,h),CV_32FC1);Mat tmpdata=Mat::eye(h,w,CV_32FC1);MatExpr 是矩阵表达式类,用于对矩阵进行某种计算。
5. 得到矩阵的行数、列数和维数
Mat 类提供了如下公有成员变量来获得行数、列数和维数:
(1)rows:矩阵的行数。
(2)cols:矩阵的列数。
(3)dims:矩阵的维度,例如二维矩阵 dims=2,如果是三维矩阵,则 dims=3。
【例 】打印矩阵的像素行数、像素列数和维数
#include<iostream>#include"opencv2/imgcodecs.hpp"usingnamespacecv;//所有 OpenCV 类都在命名空间 cv 下usingnamespacestd;#pragmacomment(lib,"opencv_world4140d.lib")//引用引入库intmain(){Matr3(Size(3,3),CV_8UC3);cout<<"矩阵像素行数="<<r3.rows<<",矩阵像素列数="<<r3.cols<<endl;cout<<"矩阵维数="<<r3.dims<<endl;}6.矩阵的数据指针及其打印
矩阵类 Mat 的数据结构分为两部分:矩阵头和指向矩阵数据部分的指针。Mat 类提供了公有成员变量(属性)data 指针,用于指向 Mat 数据矩阵的数据部分的首地址,其定义如下:
uchar*cv::Mat::data;当我们只定义一个 Mat 对象时,此时 data 指针指向 NULL,打印的结果是一对方括号。当我们定义了 Mat 对象的同时又指定了大小,则 data 指针指向一段有效内存,但内存中的数据是乱的,比如 cdcdcdcdcd(如果单步调试可以看到),此时打印的结果就是矩阵每个元素都是 205。当我们定义 Mat 对象的同时指定了大小并赋了值,则 data 指向的内存中的数据就是所赋值的数据,此时就可以打印出所赋值的数据了。
我们可以用 cout 来打印一个矩阵的数据。
#include<iostream>#include<opencv2/core.hpp >usingnamespacecv;//所有 OpenCV 类都在命名空间 cv 下usingnamespacestd;#pragmacomment(lib,"opencv_world4140d.lib")//引用引入库intmain(){inta[3][3]={1,2,3,4,5,6,7,8,9};Matr1(3,3,CV_32S,a);//Mat r1 = (Mat_<double>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);cout<<"r1 total matrix:\n"<<r1<<endl;Mat r2;//目前 r2 的 data 是 NULLif(r2.data==NULL)cout<<"r2.data==NULL\n";cout<<"r2="<<r2<<endl;cout<<"r3=\n";Matr3(Size(3,3),CV_8UC3);cout<<r3;return0;}7. 创建新的矩阵头
矩阵类 Mat 的数据结构分为两部分:矩阵头和指向矩阵数据部分(数据区)的指针。矩阵头的内容主要包含矩阵尺寸、存储方法、存储地址和引用次数等。矩阵头的大小是一个常数,不会随着图像的大小而改变,但是保存图像像素数据的矩阵会随着图像的大小而改变,通常数据量会很大,比矩阵头大几个数量级。这样,在图像复制和传递过程中,主要的开销是由存放图像像素的矩阵而引起的。因此,OpenCV 使用了引用次数,当进行图像复制和传递时,不再复制整个 Mat 数据,而只是复制矩阵头和指向像素矩阵的指针。
Mat 类提供了下列成员函数来创建新的矩阵头,并共享原矩阵的数据。注意,矩阵头的内存空间是新开辟的。
(1)Mat row(int y);
该函数根据指定行索引(基于 0 开始,最大值是原矩阵行数-1)创建一个数的矩阵头,并返回Mat 矩阵对象,该矩阵对象的数据部分共享原来矩阵的数据部分。其中 y 是指定的行数,返回的是Mat 对象。
(2)Mat col (int x);
创建一个指定了列索引(基于 0 开始,最大值是原矩阵列数-1)的矩阵头,并返回 Mat 矩阵对象,该矩阵对象的数据部分共享原来矩阵的数据部分。其中 x 是指定的列数,返回值是包含新矩阵头的 Mat 对象。
(3)Mat rowRange(int startrow, int endrow);
该函数创建一个指定行区间的新的矩阵头,并返回 Mat 矩阵对象,该矩阵对象的数据部分共享原来矩阵的数据部分。其中 startrow 是开头行(从 0 开始的行间距起始索引),endrow 是结尾行。函数取的实际行数 endrowstartrow,只取到范围的右边界(endrow 包括在内),而不取左边界(不包括 startrow 行)。
(4)Mat colRange(int startcol,int endcol);
创建一个指定列区间的新的矩阵头,可取指定列区间元素。其中 startcol 是开头列,endcol 是结尾列。函数取的实际列数 endcolstartcol,只取到范围的右边界(endcol 包括在内),而不取左边界(不包括 startcol 行)。
【例 】创建矩阵头
#include<iostream>#include<opencv2/core.hpp >#include"opencv2/imgcodecs.hpp"usingnamespacecv;//所有 OpenCV 类都在命名空间 cv 下usingnamespacestd;#pragmacomment(lib,"opencv_world4140d.lib")//引用引入库intmain(){inti,j;//Mat r1 = (Mat_<double>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);inta[3][3]={1,2,3,4,5,6,7,8,9};Matr1(3,3,CV_32S,a);cout<<"r1 total matrix:\n"<<r1<<endl;Mat r2;for(i=0;i<r1.rows;i++){r2=r1.row(i);//用 r1 的第 i 行作为新矩阵头,并返回新矩阵头的矩阵对象if(r1.data==r2.data)//由于共享数据,因此第 0 行的指针应该是相同的cout<<"when i="<<i<<",r1.data==r2.data\n";cout<<"r1.row("<<i<<"):\n"<<r2<<endl;}Mat r3;for(j=0;j<r1.cols;j++){r3=r1.col(j);//用 r1 的第 j 列作为新矩阵头,并返回新矩阵头的矩阵对象if(r1.data==r3.data)//由于共享数据,因此第 0 列的指针应该是相同的cout<<"when j="<<j<<",r1.data==r3.data\n";cout<<"r1.col("<<j<<"):\n"<<r3<<endl;}Mat r4=r1.rowRange(1,2);//创建新矩阵头,并共享 r1 的第 1 列数据(基于 0)cout<<"r1.rowRange(1, 2):\n"<<r4<<endl;//创建新矩阵头,并共享 r1 的第 1 列和第 2 列的数据cout<<"r1.colRange(1, 3):\n"<<r1.colRange(1,3)<<endl;}8.得到矩阵通道数
成员函数 channels 返回矩阵通道的数目,函数声明如下:
intchannels();函数比较简单,直接返回矩阵的通道数,比如以下代码:
Mat r1=(Mat_<double>(3,3)<<1,2,3,4,5,6,7,8,9);Matr2(576,5*768,CV_8UC3);Mat r3;cout<<"r1 channel number:"<<r1.channels()<<endl;cout<<"r2 channel number:"<<r2.channels()<<endl;cout<<"r3 channel number:"<<r3.channels()<<endl;运行结果是:
r1 channel number:1r2 channel number:3r3 channel number:1r1 定义了大小为 3×3,并且赋了 9 个元素,一个像素占某行的一列,说明是单通道,如果是多通道,则一个像素要占多列。r2 因为指定了是 3 通道(CV_8UC3),所以输出的是 3。r3 默认是单通道。