学习图像处理首先要学会如何操作矩阵信息,在OpenCV中提供了一个Mat类用于存储矩阵数据。
Mat类用来保存矩阵类型的数据信息,包括向量,矩阵,灰度或彩色图像等数据。Mat类分为矩阵头和指向存储数据的矩阵指针两部分。其中矩阵头中包含矩阵的尺寸、存储方法、地址和引用次数。矩阵头的大小是一个常数,不会随着矩阵尺寸的大小二改变。在绝大多数情况下,矩阵头大小远小于矩阵中数据量的大小,因此图像复制和传递过程中的主要开销是存放矩阵数据。
cv::Mat a; //创建一个名为a的矩阵头
a = cv::imread("test.jpg") //向a中赋值图像数据,矩阵指针指向像素数据
cv::Mat b = a //复制矩阵头
上述代码,首先创建了名为a的矩阵头,之后读入一张图像并将中的矩阵指针指向该图的像素数据,最后将a矩阵头中的内容复制到b矩阵头中。虽然a、b有各自的矩阵头,但是矩阵指针所指向的是同一矩阵数据,所以通过任意矩阵头修改矩阵中的数据时,另一个矩阵头指向的数据也会发生改变。但是当删除变量a时,b变量并不会指向一个空数据,只用当两个变量同时删除时,才会释放矩阵数据。因为矩阵头中引用次数标记了引用某个矩阵的次数,只有当矩阵的引用次数为0的时候才会释放矩阵数据。
cv::Mat A = Mat_(3,3); //创建一个3*3的矩阵用于存放double数据类型
Mat可以存储的数据类型包括double,float,uchar,unsigned char,以及自定义模板。
数据类型 | 具体类型 | 取值范围 |
CV_8U | 8位无符号整数 | 0~255 |
CV_8S | 8位符号整数 | -128~127 |
CV_16U | 16位无符号整数 | 0~65535 |
CV_16S | 16位符号整数 | -32768~32767 |
CV_32S | 32位符号整数 | -2147483~2147483647 |
CV_32F | 32位浮点小数 | 略 |
CV_64F |
64位浮点小数 | 略 |
cv::Mat a(640,480,CV_8UC3) //创建一个640*480 的三通道矩阵用于存放彩色图像
cv::Mat a(3,3,CV_8UC1) //创建一个3*3的8位无符号整数的单通道矩阵
cv::Mat a(3,3,CV_8U) //创建单通道矩阵,c1标识可以省略
注意:虽然在64位编译器里,uchar和CV_8U都表示8位无符号整数,但是两者有着严格的定义,CV_8U只能用于Mat类内部构建方法,如果用Mat_
(1)利用默认构造函数
cv::Mat::Mat();
这种构造方式不需要输入任何的参数,在后续给变量赋值的时候会自动判断矩阵的类型大小,实现灵活的存储,常用于存储读取的图像数据和某个函数的运算输出结果。
(2)根据输入矩阵尺寸和类型构造
cv::Mat::Mat( int rows,
int cols,
int type,
)
//rows:构造矩阵的行数
//cols:构造矩阵的列数
//type:矩阵中存储的数据类型
(3)用Size()结构构造Mat类
cv::Mat::Mat(Size size(),
int type
)
//size: 二维数组变量尺寸,通过Size(cols,rows)来进行赋值,注意列在前,行在后
//type:略
(4)利用已有矩阵构造Mat实例
cv::Mat()::Mat(const Mat &m)
//m:以构建完成的Mat类矩阵变量
希望复制两个一模一样的Mat类而批次之间不会影响,那么可以使用m=a.clone()
(5)创建已有Mat类的子类
cv::Mat::Mat(const Mat &m,
const Range &rowRange,
const Range &colRange = Range::all()
)
//m: 已经构建完成的Mat类矩阵数据
//rowRange:在以有矩阵中需要截取的行数范围,是一个Range变量,例如Range(2,5)表示从第二行, //到第五行
//colRange:截取列的范围
//这种方式主要用于在原图中截图使用。不过需要注意的是,通过这种方式构造的Mat类与已有Mat类享有共同的数据。
(1)在构造时赋值
cv::Mat::Mat(int rows,
int cols,
int type,
const Scalar &s
)
//rows:略
//cols:略
//type:略
//s: 给矩阵中没个像素赋值的参数变量
//实例:
cv::Mat::Mat(2, 2, CV_8UC3, CV::Scalar(0,0,255))
(2)枚举法赋值
cv::Mat::Mat(cv::Mat_(3,3)<<1, 2, 3, 4, 5, 6, 7, 8, 9);
(3)利用循环赋值
cv::Mat c = cv::Mat_(3, 3); //定义一个3*3的矩阵
for (int i = 0; i < c.rows; i++)
{
for (int j = 0; j < c.cols; j++)
{
c.at(i, j) = i + j;
{
}
(4)利用类方法赋值
cv::Mat a = cv::Mat::eye(3, 3, CV_8UC1);
cv::Mat b = (cv::Mat_(1, 3) <<1, 2, 3);
cv::Mat c = cv::Mat::diag(b);
cv::Mat d = cv::Mat::ones(3, 3, CV_8UC1);
cv::Mat e = cv::Mat::zeros(4, 2, CV_8UC3);
//eye:创建一个单位矩阵,如果行和列不相等,在(1,1),(2,2),等主对角线出为1
//diag:创建对角矩阵,其参数必须是Mat类型的一维变量,用来存放对角元素的数值
//ones:创建一个全为1的矩阵
//zeros:创建一个全为0的矩阵
(5)利用数组进行赋值
float a[8] = {5, 6, 7, 8, 1, 2, 3, 4, };
cv::Mat b = cv::Mat(2, 2, CV_32FC2, a);
CV::Mat c = cv::Mat(2, 4, CV_32FC1, a);
//当矩阵中的元素数目大于数组中的数据时,将用-1.0737418e+08填充给矩阵;当矩阵中元素的数目小于数组中的数据时,将矩阵赋值完成后,数组中剩余数据将不再赋值。
Mat支持加减乘除的运算,在对图像进行卷积运算时,需要两个矩阵进行乘法运算,OpenCV提供了两个Mat类矩阵的乘法运算,而且定义了两个矩阵的内积和对应位的乘法运算。
cv::Mat() j,m;
double k;
j = c*d;
k = a.dot(b);
m = a.mul(b);
属性 | 作用 |
cols | 矩阵的列数 |
rows | 矩阵的行数 |
step | 以字节为单位的矩阵有效宽度 |
elemSize() | 每个元素的字节数 |
total() | 矩阵中元素的个数 |
channels() | 矩阵的通道数 |
(1)通过at方法读取Mat类矩阵中的元素
//读取单通道矩阵元素
cv::Mat a = (cv::Mat_(3, 3) <<1, 2, 3, 4, 5, 6, 7, 8, 9);
int value = (int)a.at(0, 0);
//读取多通道矩阵元素
cv::Mat b(3, 4, CV_8UC3, CV::Scalar(0, 0, 1));
cv::Vec3b vc3 = b.at(0, 0);
int first = (int)vc3.val[0];
int second = (int)vc3.val[1];
int third = (int)vc3.val[2];
//在使用多通道变量类型是,同样需要注意at方法中数据变量类型与矩阵的变量类型相对应,
//并且cv::Vec3b类型在输入每个通道数据是需要将其变量类型强制转化成int类型。
针对三通道矩阵,定义了cv::Vec3b,cv::Vec3s,cv::Vec3w,cv::Vec3d,cv::Vec3f,cv::Vec3i。b是uchar类型的缩写,s是short类型的缩写,w是ushort类型的缩写,d是double类型的缩写,f是float类型的缩写, i是int类型的缩写。
(2)通过指针ptr读取Mat类矩阵中的元素
Mat类矩阵中每一行中的每个元素都是挨着存放的,所以给出了通道指针ptr读取Mat类矩阵元素。
cv::Mat b(3, 4, CV_8UC3, cv::Scalar(0, 0, 1));
for (int i = 0; i < b.rows; i++)
{
uchar* ptr = b.ptr(i);
for(int j = 0; j < b.cols*b.channels(); j++)
{
cout << (int)ptr[j] <
(3)通过迭代器访问Mat类矩阵中的元素
cv::MatIterator_ it = a.begin();
cv::MatIterator_ it_end = a.end();
for(int i = 0; it ! = it_end; it++)
{
cout << (int)(*it) <<"";
if((++i%a.cols) == 0)
{
cout << endl;
}
}
Mat类的迭代器变量类型是cv::MatIterator_<>,在定义时同样需要在尖括号中声明数据的变量类型。Mat类迭代器的起始是Mat.begin<>(),借宿是Mat.end<>(),与其他迭代器用法相同,通过“++”运算实现指针位置向下迭代,数据的读取方式是先读取第一个元素的每一个通道,之后再读取第二个元素的每一个通道,直到最后一个元素的最后一个通道。
(4)通过矩阵元素的地址定位方式访问元素
(int)(*(b.data + b.step[0] * row + b.step[1] * col + channel));