2.1 图像存储容器Mat类介绍
- 1.Mat类介绍
- 2.Mat类构造与赋值
- 2.1Mat类的构造
- 2.1.1 采用默认构造函数
- 2.1.2 根据输入矩阵尺寸和类型构造
- 2.1.3 利用已有矩阵构造Mat类
- 2.2Mat类的赋值
- 2.2.1 构造时赋值
- 2.2.2 枚举法赋值
- 2.2.3 循环法赋值
- 2.2.4 类方法赋值
- 2.2.5 利用数组进行赋值
- 2.3Mat类支持的运算
- 2.3.1Mat类加减乘除运算
- 2.3.2Mat类矩阵之间的乘法、点积和内积
- 2.4 Mat类元素的访问
- 2.4.1 通过at方法读取Mat类矩阵中的元素
- 2.4.2 通过指针ptr读取Mat类矩阵中的元素
- 2.4.3 通过迭代器访问Mat类矩阵中的元素
1.Mat类介绍
Mat类分为【矩阵头】和指向存储数据的【矩阵指针】两部分。【矩阵头】中包含矩阵的尺寸、存储方法、地址和引用次数等。【矩阵头】的大小是一个常数,不会随着矩阵尺寸的大小而改变。图像复制和传递过程中主要的开销是存放矩阵数据。为了解决这个问题,在OpenCV中复制和传递图像时,只是复制了【矩阵头】和指向存储数据的【矩阵指针】。因此在创建Mat类时可以先创建矩阵头后赋值数据,其方法如下:
cv::Mat a;//创建一个名为a的矩阵头
a=cv::imread("../test.jpg");//向a中赋值图像数据,矩阵指针指向像素数据
cv::Mat b=a;//复制矩阵头,并命名为b
上述代码首先创建了一个名字为a的矩阵头,之后读入一张图像,并将a中矩阵指针指向该图像的像素数据,最后将a矩阵头中的内容复制到b矩阵头中。虽然a、b有各自的矩阵头,但是其矩阵指针指向的是同一个矩阵数据,通过任意一个矩阵头修改矩阵中的数据,另一个矩阵头指向的数据会跟着发生变化。但是当删除a变量时,b变量不会指向一个空数据,只有当两个变量都删除后,才会释放矩阵数据。因为矩阵头中引用次数标记了某个矩阵数据的次数,只有当矩阵数据引用次数为0的时候,才会释放矩阵数据。
接下来讲解Mat类里可以存储的数据类型。根据官方给出的Mat类集成关系图,我们可以发现Mat类可以存储的数据类型包含double、float、uchar、unsigned char,以及自定义的模板等。
cv::Mat A=cv::Mat_<double>(3,3);//创建一个3*3的矩阵用于存放double类型数据
如下表是OpenCV中的数据类型及取值范围。
仅有数据类型是不够的,还需要定义图像数据的通道(Channel)数,例如灰度图图像数据是单通道数据,彩色图像数据是3通道或者3通道数据。用C1、C2、C3、C4分别表示单通道、双通道、3通道和4通道。如下代码的方式创建一个生命通道数和数据类型的Mat类:
cv::Mat a(640, 480, CV_8UC3); // 创建一个640*480的3通道矩阵用于存放彩色图像
cv::Mat b(3, 3, CV_8UC1); // 创建一个3*3的8位无符号证书的单通道矩阵
cv::Mat c(3, 3, CV_8U); // 同b,C1可以省略
2.Mat类构造与赋值
前一节介绍了3中构造Mat类变量的方法,如下:
//方法1
cv::Mat a;//创建一个名为a的矩阵头
a=cv::imread("../test.jpg");//向a中赋值图像数据,矩阵指针指向像素数据
//方法2
cv::Mat A=cv::Mat_<double>(3,3);//创建一个3*3的矩阵用于存放double类型数据
//方法3
cv::Mat a(640, 480, CV_8UC3); // 创建一个640*480的3通道矩阵用于存放彩色图像
后两种没有给变量初始化赋值,本小姐介绍如何构造并赋值Mat类变量。
2.1Mat类的构造
2.1.1 采用默认构造函数
cv::Mat::Mat();
这种构造方式不需要输入任何的参数,在后续给变量赋值的时候会自动判断矩阵的类型大小,实现灵活的存储。
2.1.2 根据输入矩阵尺寸和类型构造
cv::Mat::Mat(int rows,int cols, int type);
rows是行数,cols是列数,type是数据类型。数据类型的通道数是可以设置为多个的,如CV_8UC(n),n最大可以为512.
也可以用Size()结构构造Mat类
cv::Mat::Mat(Size size(), int type);
size()是二维数组变量尺寸,通过Size(cols,rows)进行赋值。在Size()结构里,矩阵的行和列与之前是相反的,不要弄错。实例如下:
cv::Mat a(cv::Size(480, 640), CV_8UC1); // 构造一个行为640、列为480的单通道矩阵
cv::Mat b(cv::Size(480, 640), CV_32FC3); // 构造一个行为640、列为480的3通道矩阵
2.1.3 利用已有矩阵构造Mat类
cv::Mat::Mat(const Mat& m);
m是已经构建完成的Mat类型矩阵。这种方式只是复制了Mat类的矩阵头,矩阵指针指向的是同一个地址。因此,如果通过某一个Mat类变量修改了矩阵中的数据,那么另一个变量中的数据也会发生变化。如果希望复制两个一模一样的Mat类而彼此之间不会收到影响,那么可以使用m=a.clone()
实现。
构造已有Mat类的子类
cv::Mat::Mat(
const Mat& m,
const Range& rowRange,
const Range& colRange=Range::all()
);
m是已经构建完成的Mat类矩阵数据。rowRange是需要截取的行数范围,colRange是需要截取的列数范围。注意:这种方式构造的子类与原图像享有共同的数据,原Mat类数据发生变化后,子类也会发生变化。实例如下:
cv::Mat a(cv::Size(480, 640), CV_8UC1); // 构造一个行为640、列为480的单通道矩阵
cv::Mat b(a, cv::Range(2, 5), cv::Range(2, 6)); // 从a中截取2-5行,2-6列到b
cv::Mat c(a, cv::Range(2, 5), cv::Range::all()); // 从a中截取2-5行,所有列到c
2.2Mat类的赋值
构造Mat类后需要为其赋值,OpenCV4.1提供了多种赋值方式。
2.2.1 构造时赋值
利用Scalar在构造Mat类的时候为其赋值
cv::Mat a(2, 2, CV_8UC3, cv::Scalar(0, 0, 255)); // 创建一个3通道矩阵,每个像素都是0,0,255
cv::Mat b(2, 2, CV_8UC2, cv::Scalar(0, 255)); // 创建一个2通道矩阵,每个像素都是0,255
cv::Mat c(2, 2, CV_8UC1, cv::Scalar(255)); // 创建一个1通道矩阵,每个像素都是255
std::cout << "a:\n"
<< a << std::endl;
std::cout << "b:\n"
<< b << std::endl;
std::cout << "c:\n"
<< c << std::endl;
a:
[ 0, 0, 255, 0, 0, 255;
0, 0, 255, 0, 0, 255]
b:
[ 0, 255, 0, 255;
0, 255, 0, 255]
c:
[255, 255;
255, 255]
注意Scalar中数据的数量要和Mat类的通道数对应,否则大于通道数之后的Scaler中的数据不会被读取,例如:
cv::Mat d(2, 2, CV_8UC2, cv::Scalar(0, 0, 255));
std::cout << "d:\n"
<< d << std::endl;
最终的单个元素输出时0,0,而不是0,0,255。
d:
[ 0, 0, 0, 0;
0, 0, 0, 0]
2.2.2 枚举法赋值
如下逐个元素赋值的方式,先填充第一行,再依次填充第二行,第三行。
cv::Mat a = (cv::Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat b = (cv::Mat_<double>(2, 3) << 1.0, 2.1, 3.2, 4.3, 5.8, 6.1);
std::cout << "a:\n"
<< a << std::endl;
std::cout << "b:\n"
<< b << std::endl;
***test06***
a:
[1, 2, 3;
4, 5, 6;
7, 8, 9]
b:
[1, 2.1, 3.2;
4.3, 5.8, 6.1]
2.2.3 循环法赋值
int count=0;
cv::Mat a = cv::Mat_<int>(3, 3); //定义一个3*3的矩阵
for (int i = 0; i < a.rows; i++)
{
for (int j = 0; j < a.cols; j++)
{
a.at<int>(i, j) = count++;
}
}
std::cout<<"a:\n"<<a<<std::endl;
***test07***
a:
[0, 1, 2;
3, 4, 5;
6, 7, 8]
2.2.4 类方法赋值
快速生成单位矩阵(eye),对角矩阵(diag),全1和全0矩阵(ones,zeros)。
cv::Mat a = cv::Mat::eye(3, 3, CV_8UC1);
cv::Mat b = (cv::Mat_<int>(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);
std::cout << "a:\n"
<< a << std::endl;
std::cout << "b:\n"
<< b << std::endl;
std::cout << "c:\n"
<< c << std::endl;
std::cout << "d:\n"
<< d << std::endl;
std::cout << "e:\n"
<< e << std::endl;
***test08***
a:
[ 1, 0, 0;
0, 1, 0;
0, 0, 1]
b:
[1, 2, 3]
c:
[1, 0, 0;
0, 2, 0;
0, 0, 3]
d:
[ 1, 1, 1;
1, 1, 1;
1, 1, 1]
e:
[ 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0]
2.2.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);
std::cout << "b:\n"
<< b << std::endl;
std::cout << "c:\n"
<< c << std::endl;
依次赋值,优先为通道数赋值,例如5和6放到了b中第一个元素的两个通道中。
***test09***
b:
[5, 6, 7, 8;
1, 2, 3, 4]
c:
[5, 6, 7, 8;
1, 2, 3, 4]
2.3Mat类支持的运算
2.3.1Mat类加减乘除运算
cv::Mat a = (cv::Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat b = (cv::Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat c = (cv::Mat_<double>(3, 3) << 1.0, 2.1, 3.2, 4.3, 5.4, 6.5, 7.6, 8.9, 9.1);
cv::Mat d = (cv::Mat_<double>(3, 3) << 1.0, 2.1, 3.2, 4.3, 5.4, 6.5, 7.6, 8.9, 9.1);
cv::Mat e,f,g,h,i;
e=a+b;
f=c-d;
g=2*a;
h=d/2.0;
i=a-1;
std::cout << "e:\n"
<< e << std::endl;
std::cout << "f:\n"
<< f << std::endl;
std::cout << "g:\n"
<< g << std::endl;
std::cout << "h:\n"
<< h << std::endl;
std::cout << "i:\n"
<< i << std::endl;
***test10***
e:
[2, 4, 6;
8, 10, 12;
14, 16, 18]
f:
[0, 0, 0;
0, 0, 0;
0, 0, 0]
g:
[2, 4, 6;
8, 10, 12;
14, 16, 18]
h:
[0.5, 1.05, 1.6;
2.15, 2.7, 3.25;
3.8, 4.45, 4.55]
i:
[0, 1, 2;
3, 4, 5;
6, 7, 8]
2.3.2Mat类矩阵之间的乘法、点积和内积
cv::Mat a = (cv::Mat_<double>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat b = (cv::Mat_<double>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat c, e;
double d;
c = a * b;
d = a.dot(b);
e = a.mul(b);
std::cout << "c:\n"
<< c << std::endl;
std::cout << "d:\n"
<< d << std::endl;
std::cout << "e:\n"
<< e << std::endl;
***test11***
c:
[30, 36, 42;
66, 81, 96;
102, 126, 150]
d:
285
e:
[1, 4, 9;
16, 25, 36;
49, 64, 81]
2.4 Mat类元素的访问
2.4.1 通过at方法读取Mat类矩阵中的元素
单通道
cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
int b=(int)a.at<uchar>(0,0);//行,列
int c=(int)a.at<uchar>(2,0);
std::cout << "a:\n"
<< a << std::endl;
std::cout << "b:\n"
<< b << std::endl;
std::cout << "c:\n"
<< c << std::endl;
***test12***
a:
[ 1, 2, 3;
4, 5, 6;
7, 8, 9]
b:
1
c:
7
多通道
cv::Mat b(3, 4, CV_8UC3, cv::Scalar(0, 0, 1));
cv::Vec3b vc3 = b.at<cv::Vec3b>(0, 0);
int first = (int)vc3.val[0];
int second = (int)vc3.val[1];
int third = (int)vc3.val[2];
std::cout<<first<<" "<<second<<" "<<third<<std::endl;
***test13***
0 0 1
2.4.2 通过指针ptr读取Mat类矩阵中的元素
cv::Mat b(3,4,CV_8UC3,cv::Scalar(1,2,3));
for (int i = 0; i < b.rows; i++)
{
uchar* ptr=b.ptr<uchar>(i);//读取i行
for (int j = 0; j < b.cols*b.channels(); j++)
{
std::cout<<(int)ptr[j]<<" ";
}
// 可以直接输出b.ptr<uchar>(i)[j]来输出第i行第j列的数据
std::cout<<std::endl;
}
***test14***
1 2 3 1 2 3 1 2 3 1 2 3
1 2 3 1 2 3 1 2 3 1 2 3
1 2 3 1 2 3 1 2 3 1 2 3
2.4.3 通过迭代器访问Mat类矩阵中的元素
cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::MatIterator_<uchar> it = a.begin<uchar>();
cv::MatIterator_<uchar> it_end = a.end<uchar>();
for (int i = 0; it != it_end; it++)
{
std::cout<<(int)(*it)<<" ";
if ((++i % a.cols) == 0)
{
std::cout<<std::endl;
}
}
***test15***
1 2 3
4 5 6
7 8 9